mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
[api11] Some code cleanup and signature replacements (#2066)
* Remove unused code from ChatHandlers * Replace sigs in DalamudAtkTweaks * Resolve LocalContentId by using PlayerState.ContentId * Resolve BuddyList address via UIState.Buddy * Resolve ObjectTable address via GameObjectManager * Resolve FateTable address via FateManager * Resolve GroupManager address via GroupManager * Resolve JobGauges address via JobGaugeManager.CurrentGauge * Simplify ItemHover/Out event * Resolve ToggleUiHide address via RaptureAtkModule.SetUiVisibility * Resolve PopulateItemLinkObject via InventoryItem.Copy * Add byte[].AsPointer extension * Resolve addresses used by ToastGui via UIModule functions * Use Length from Span as ObjectTableLength * Replace OpenMapWithMapLink with CS call * Resolve FrameworkAddressResolver with CS vtable * Drop unnecessary ToArray in HandlePrintMessage * Clean up event calls in HandlePrintMessageDetour * Simplify LocalContentId further This pointer can't be null, because it's part of the .data section. * Compare SeStrings in FlyTextGui with SequenceEqual * Use CS types in FlyTextGuis internal code * Simplify reading SeStrings internally * Remove AsPointer again * Resolve Number/StringArray by type in NamePlateGui * Fix crashes in HandlePrintMessageDetour * Resolve InteractableLinkClicked with LogViewer.HandleLinkClick
This commit is contained in:
parent
084f8b55e7
commit
c0f05614c6
25 changed files with 343 additions and 827 deletions
|
|
@ -1,22 +1,14 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using CheapLoc;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.Gui;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.ImGuiNotification.Internal;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Internal.Windows;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -27,49 +19,10 @@ namespace Dalamud.Game;
|
|||
/// Chat events and public helper functions.
|
||||
/// </summary>
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class ChatHandlers : IServiceType
|
||||
internal partial class ChatHandlers : IServiceType
|
||||
{
|
||||
private static readonly ModuleLog Log = new("CHATHANDLER");
|
||||
private static readonly ModuleLog Log = new("ChatHandlers");
|
||||
|
||||
private readonly Dictionary<ClientLanguage, Regex[]> retainerSaleRegexes = new()
|
||||
{
|
||||
{
|
||||
ClientLanguage.Japanese,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)×(?<count>[\d,.]+)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
new Regex(@"^(?:.+)マーケットに(?<origValue>[\d,.]+)ギルで出品した(?<item>.*)が売れ、(?<value>[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.English,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^(?<item>.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?<value>[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.German,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) für (?<value>[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled),
|
||||
new Regex(@"^Dein Gehilfe hat (?<item>.+) auf dem Markt von (?:.+) verkauft und (?<value>[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
{
|
||||
ClientLanguage.French,
|
||||
new Regex[]
|
||||
{
|
||||
new Regex(@"^Un servant a vendu (?<item>.+) pour (?<value>[\d,.]+) gil à (?:.+)\.$", RegexOptions.Compiled),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private readonly Regex urlRegex = new(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Dalamud dalamud = Service<Dalamud>.Get();
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
||||
|
|
@ -92,6 +45,9 @@ internal class ChatHandlers : IServiceType
|
|||
/// </summary>
|
||||
public bool IsAutoUpdateComplete { get; private set; }
|
||||
|
||||
[GeneratedRegex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?", RegexOptions.Compiled)]
|
||||
private static partial Regex CompiledUrlRegex();
|
||||
|
||||
private void OnCheckMessageHandled(XivChatType type, int timestamp, ref SeString sender, ref SeString message, ref bool isHandled)
|
||||
{
|
||||
var textVal = message.TextValue;
|
||||
|
|
@ -100,7 +56,7 @@ internal class ChatHandlers : IServiceType
|
|||
this.configuration.BadWords.Any(x => !string.IsNullOrEmpty(x) && textVal.Contains(x)))
|
||||
{
|
||||
// This seems to be in the user block list - let's not show it
|
||||
Log.Debug("Blocklist triggered");
|
||||
Log.Debug("Filtered a message that contained a muted word");
|
||||
isHandled = true;
|
||||
return;
|
||||
}
|
||||
|
|
@ -127,41 +83,10 @@ internal class ChatHandlers : IServiceType
|
|||
return;
|
||||
#endif
|
||||
|
||||
if (type == XivChatType.RetainerSale)
|
||||
{
|
||||
foreach (var regex in this.retainerSaleRegexes[(ClientLanguage)this.dalamud.StartInfo.Language])
|
||||
{
|
||||
var matchInfo = regex.Match(message.TextValue);
|
||||
|
||||
// we no longer really need to do/validate the item matching since we read the id from the byte array
|
||||
// but we'd be checking the main match anyway
|
||||
var itemInfo = matchInfo.Groups["item"];
|
||||
if (!itemInfo.Success)
|
||||
continue;
|
||||
|
||||
var itemLink = message.Payloads.FirstOrDefault(x => x.Type == PayloadType.Item) as ItemPayload;
|
||||
if (itemLink == default)
|
||||
{
|
||||
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
|
||||
break;
|
||||
}
|
||||
|
||||
Log.Debug($"Probable retainer sale: {message}, decoded item {itemLink.Item.RowId}, HQ {itemLink.IsHQ}");
|
||||
|
||||
var valueInfo = matchInfo.Groups["value"];
|
||||
// not sure if using a culture here would work correctly, so just strip symbols instead
|
||||
if (!valueInfo.Success || !int.TryParse(valueInfo.Value.Replace(",", string.Empty).Replace(".", string.Empty), out var itemValue))
|
||||
continue;
|
||||
|
||||
// Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemLink.Item.RowId, itemValue, itemLink.IsHQ));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var messageCopy = message;
|
||||
var senderCopy = sender;
|
||||
|
||||
var linkMatch = this.urlRegex.Match(message.TextValue);
|
||||
var linkMatch = CompiledUrlRegex().Match(message.TextValue);
|
||||
if (linkMatch.Value.Length > 0)
|
||||
this.LastLink = linkMatch.Value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Buddy;
|
||||
|
||||
|
|
@ -28,14 +26,9 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private BuddyList()
|
||||
{
|
||||
this.address = this.clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Buddy list address {Util.DescribeAddress(this.address.BuddyList)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -76,14 +69,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
internal IntPtr BuddyListAddress => this.address.BuddyList;
|
||||
|
||||
private static int BuddyMemberSize { get; } = Marshal.SizeOf<FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy.BuddyMember>();
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => (FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy*)this.BuddyListAddress;
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.UI.Buddy* BuddyListStruct => &UIState.Instance()->Buddy;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IBuddyMember? this[int index]
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using Dalamud.Utility;
|
|||
using FFXIVClientStructs.FFXIV.Application.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Event;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
|
||||
|
|
@ -111,7 +112,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
get
|
||||
{
|
||||
var agentMap = AgentMap.Instance();
|
||||
return agentMap != null ? AgentMap.Instance()->CurrentMapId : 0;
|
||||
return agentMap != null ? agentMap->CurrentMapId : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ internal sealed class ClientState : IInternalDisposableService, IClientState
|
|||
public IPlayerCharacter? LocalPlayer => Service<ObjectTable>.GetNullable()?[0] as IPlayerCharacter;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
|
||||
public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsLoggedIn { get; private set; }
|
||||
|
|
|
|||
|
|
@ -7,39 +7,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
{
|
||||
// Static offsets
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the actor table.
|
||||
/// </summary>
|
||||
public IntPtr ObjectTable { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the buddy list.
|
||||
/// </summary>
|
||||
public IntPtr BuddyList { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of a pointer to the fate table.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a static address to a pointer, not the address of the table itself.
|
||||
/// </remarks>
|
||||
public IntPtr FateTablePtr { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Group Manager.
|
||||
/// </summary>
|
||||
public IntPtr GroupManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the local content id.
|
||||
/// </summary>
|
||||
public IntPtr LocalContentId { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of job gauge data.
|
||||
/// </summary>
|
||||
public IntPtr JobGaugeData { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the keyboard state.
|
||||
/// </summary>
|
||||
|
|
@ -74,17 +41,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver
|
|||
/// <param name="sig">The signature scanner to facilitate setup.</param>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.ObjectTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83 ?? ?? ?? ?? C6 83 ?? ?? ?? ?? ??");
|
||||
|
||||
this.BuddyList = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 45 84 E4 75 1A F6 45 12 04");
|
||||
|
||||
this.FateTablePtr = sig.GetStaticAddressFromSig("48 8B 15 ?? ?? ?? ?? 48 8B F1 44 0F B7 41");
|
||||
|
||||
this.GroupManager = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 80 B8 ?? ?? ?? ?? ?? 77 71");
|
||||
|
||||
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 0D ?? ?? ?? ?? 48 8D 57 08");
|
||||
this.JobGaugeData = sig.GetStaticAddressFromSig("48 8B 3D ?? ?? ?? ?? 33 ED") + 0x8;
|
||||
|
||||
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 57 48 83 EC 20 0F B7 DA");
|
||||
|
||||
this.ProcessPacketPlayerSetup = sig.ScanText("40 53 48 83 EC 20 48 8D 0D ?? ?? ?? ?? 48 8B DA E8 ?? ?? ?? ?? 48 8B D3");
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ using System.Collections.Generic;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using CSFateManager = FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Fates;
|
||||
|
||||
|
|
@ -20,55 +19,34 @@ namespace Dalamud.Game.ClientState.Fates;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class FateTable : IServiceType, IFateTable
|
||||
{
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FateTable(ClientState clientState)
|
||||
private FateTable()
|
||||
{
|
||||
this.address = clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Fate table address {Util.DescribeAddress(this.address.FateTablePtr)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address => this.address.FateTablePtr;
|
||||
public unsafe IntPtr Address => (nint)CSFateManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe int Length
|
||||
{
|
||||
get
|
||||
{
|
||||
var fateTable = this.FateTableAddress;
|
||||
if (fateTable == IntPtr.Zero)
|
||||
var fateManager = CSFateManager.Instance();
|
||||
if (fateManager == null)
|
||||
return 0;
|
||||
|
||||
// Sonar used this to check if the table was safe to read
|
||||
if (Struct->FateDirector == null)
|
||||
if (fateManager->FateDirector == null)
|
||||
return 0;
|
||||
|
||||
if (Struct->Fates.First == null || Struct->Fates.Last == null)
|
||||
if (fateManager->Fates.First == null || fateManager->Fates.Last == null)
|
||||
return 0;
|
||||
|
||||
return Struct->Fates.Count;
|
||||
return fateManager->Fates.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the Fate table.
|
||||
/// </summary>
|
||||
internal unsafe IntPtr FateTableAddress
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.address.FateTablePtr == IntPtr.Zero)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return *(IntPtr*)this.address.FateTablePtr;
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager* Struct => (FFXIVClientStructs.FFXIV.Client.Game.Fate.FateManager*)this.FateTableAddress;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IFate? this[int index]
|
||||
{
|
||||
|
|
@ -99,11 +77,11 @@ internal sealed partial class FateTable : IServiceType, IFateTable
|
|||
if (index >= this.Length)
|
||||
return IntPtr.Zero;
|
||||
|
||||
var fateTable = this.FateTableAddress;
|
||||
if (fateTable == IntPtr.Zero)
|
||||
var fateManager = CSFateManager.Instance();
|
||||
if (fateManager == null)
|
||||
return IntPtr.Zero;
|
||||
|
||||
return (IntPtr)this.Struct->Fates[index].Value;
|
||||
return (IntPtr)fateManager->Fates[index].Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@ using Dalamud.Game.ClientState.JobGauge.Types;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using CSJobGaugeManager = FFXIVClientStructs.FFXIV.Client.Game.JobGaugeManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.JobGauge;
|
||||
|
||||
|
|
@ -21,18 +20,15 @@ namespace Dalamud.Game.ClientState.JobGauge;
|
|||
#pragma warning restore SA1015
|
||||
internal class JobGauges : IServiceType, IJobGauges
|
||||
{
|
||||
private Dictionary<Type, JobGaugeBase> cache = new();
|
||||
private Dictionary<Type, JobGaugeBase> cache = [];
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private JobGauges(ClientState clientState)
|
||||
private JobGauges()
|
||||
{
|
||||
this.Address = clientState.AddressResolver.JobGaugeData;
|
||||
|
||||
Log.Verbose($"JobGaugeData address {Util.DescribeAddress(this.Address)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr Address { get; }
|
||||
public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->CurrentGauge);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T Get<T>() where T : JobGaugeBase
|
||||
|
|
|
|||
|
|
@ -7,15 +7,17 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.Interop;
|
||||
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using CSGameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||
using CSGameObjectManager = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObjectManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Objects;
|
||||
|
||||
|
|
@ -29,10 +31,12 @@ namespace Dalamud.Game.ClientState.Objects;
|
|||
#pragma warning restore SA1015
|
||||
internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
||||
{
|
||||
private const int ObjectTableLength = 599;
|
||||
private static readonly ModuleLog Log = new("ObjectTable");
|
||||
|
||||
private static int objectTableLength;
|
||||
|
||||
private readonly ClientState clientState;
|
||||
private readonly CachedEntry[] cachedObjectTable = new CachedEntry[ObjectTableLength];
|
||||
private readonly CachedEntry[] cachedObjectTable;
|
||||
|
||||
private readonly ObjectPool<Enumerator> multiThreadedEnumerators =
|
||||
new DefaultObjectPoolProvider().Create<Enumerator>();
|
||||
|
|
@ -46,29 +50,30 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
this.clientState = clientState;
|
||||
|
||||
var nativeObjectTableAddress = (CSGameObject**)this.clientState.AddressResolver.ObjectTable;
|
||||
var nativeObjectTable = CSGameObjectManager.Instance()->Objects.IndexSorted;
|
||||
objectTableLength = nativeObjectTable.Length;
|
||||
|
||||
this.cachedObjectTable = new CachedEntry[objectTableLength];
|
||||
for (var i = 0; i < this.cachedObjectTable.Length; i++)
|
||||
this.cachedObjectTable[i] = new(nativeObjectTableAddress, i);
|
||||
this.cachedObjectTable[i] = new(nativeObjectTable.GetPointer(i));
|
||||
|
||||
for (var i = 0; i < this.frameworkThreadEnumerators.Length; i++)
|
||||
this.frameworkThreadEnumerators[i] = new(this, i);
|
||||
|
||||
Log.Verbose($"Object table address {Util.DescribeAddress(this.clientState.AddressResolver.ObjectTable)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public nint Address
|
||||
public unsafe nint Address
|
||||
{
|
||||
get
|
||||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
|
||||
return this.clientState.AddressResolver.ObjectTable;
|
||||
return (nint)(&CSGameObjectManager.Instance()->Objects);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => ObjectTableLength;
|
||||
public int Length => objectTableLength;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IGameObject? this[int index]
|
||||
|
|
@ -77,7 +82,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
|
||||
return index is >= ObjectTableLength or < 0 ? null : this.cachedObjectTable[index].Update();
|
||||
return (index >= objectTableLength || index < 0) ? null : this.cachedObjectTable[index].Update();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +125,7 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
{
|
||||
_ = this.WarnMultithreadedUsage();
|
||||
|
||||
return index is < 0 or >= ObjectTableLength ? nint.Zero : (nint)this.cachedObjectTable[index].Address;
|
||||
return (index >= objectTableLength || index < 0) ? nint.Zero : (nint)this.cachedObjectTable[index].Address;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -172,33 +177,21 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable
|
|||
}
|
||||
|
||||
/// <summary>Stores an object table entry, with preallocated concrete types.</summary>
|
||||
internal readonly unsafe struct CachedEntry
|
||||
/// <remarks>Initializes a new instance of the <see cref="CachedEntry"/> struct.</remarks>
|
||||
/// <param name="gameObjectPtr">A pointer to the object table entry this entry should be pointing to.</param>
|
||||
internal readonly unsafe struct CachedEntry(Pointer<CSGameObject>* gameObjectPtr)
|
||||
{
|
||||
private readonly CSGameObject** gameObjectPtrPtr;
|
||||
private readonly PlayerCharacter playerCharacter;
|
||||
private readonly BattleNpc battleNpc;
|
||||
private readonly Npc npc;
|
||||
private readonly EventObj eventObj;
|
||||
private readonly GameObject gameObject;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="CachedEntry"/> struct.</summary>
|
||||
/// <param name="ownerTable">The object table that this entry should be pointing to.</param>
|
||||
/// <param name="slot">The slot index inside the table.</param>
|
||||
public CachedEntry(CSGameObject** ownerTable, int slot)
|
||||
{
|
||||
this.gameObjectPtrPtr = ownerTable + slot;
|
||||
this.playerCharacter = new(nint.Zero);
|
||||
this.battleNpc = new(nint.Zero);
|
||||
this.npc = new(nint.Zero);
|
||||
this.eventObj = new(nint.Zero);
|
||||
this.gameObject = new(nint.Zero);
|
||||
}
|
||||
private readonly PlayerCharacter playerCharacter = new(nint.Zero);
|
||||
private readonly BattleNpc battleNpc = new(nint.Zero);
|
||||
private readonly Npc npc = new(nint.Zero);
|
||||
private readonly EventObj eventObj = new(nint.Zero);
|
||||
private readonly GameObject gameObject = new(nint.Zero);
|
||||
|
||||
/// <summary>Gets the address of the underlying native object. May be null.</summary>
|
||||
public CSGameObject* Address
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => *this.gameObjectPtrPtr;
|
||||
get => gameObjectPtr->Value;
|
||||
}
|
||||
|
||||
/// <summary>Updates and gets the wrapped game object pointed by this struct.</summary>
|
||||
|
|
@ -284,11 +277,11 @@ internal sealed partial class ObjectTable
|
|||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.index == ObjectTableLength)
|
||||
if (this.index == objectTableLength)
|
||||
return false;
|
||||
|
||||
var cache = this.owner!.cachedObjectTable.AsSpan();
|
||||
for (this.index++; this.index < ObjectTableLength; this.index++)
|
||||
for (this.index++; this.index < objectTableLength; this.index++)
|
||||
{
|
||||
if (cache[this.index].Update() is { } ao)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ internal unsafe class Character : GameObject, ICharacter
|
|||
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6);
|
||||
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object ID of the character.
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ internal partial class GameObject
|
|||
internal unsafe partial class GameObject : IGameObject
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64);
|
||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong GameObjectId => this.Struct->GetGameObjectId();
|
||||
|
|
|
|||
|
|
@ -6,9 +6,8 @@ using System.Runtime.InteropServices;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using Serilog;
|
||||
using CSGroupManager = FFXIVClientStructs.FFXIV.Client.Game.Group.GroupManager;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Party;
|
||||
|
||||
|
|
@ -28,14 +27,9 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
[ServiceManager.ServiceDependency]
|
||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||
|
||||
private readonly ClientStateAddressResolver address;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private PartyList()
|
||||
{
|
||||
this.address = this.clientState.AddressResolver;
|
||||
|
||||
Log.Verbose($"Group manager address {Util.DescribeAddress(this.address.GroupManager)}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -48,7 +42,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList
|
|||
public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GroupManagerAddress => this.address.GroupManager;
|
||||
public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]);
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ internal unsafe class PartyMember : IPartyMember
|
|||
/// <summary>
|
||||
/// Gets the displayname of this party member.
|
||||
/// </summary>
|
||||
public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref Struct->Name[0]), 0x40);
|
||||
public SeString Name => SeString.Parse(this.Struct->Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sex of this party member.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -16,6 +15,8 @@ using Dalamud.Logging.Internal;
|
|||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using CSFramework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework;
|
||||
|
||||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -31,11 +32,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
private readonly Stopwatch updateStopwatch = new();
|
||||
private readonly HitchDetector hitchDetector;
|
||||
|
||||
private readonly Hook<OnUpdateDetour> updateHook;
|
||||
private readonly Hook<OnRealDestroyDelegate> destroyHook;
|
||||
private readonly Hook<CSFramework.Delegates.Tick> updateHook;
|
||||
private readonly Hook<CSFramework.Delegates.Destroy> destroyHook;
|
||||
|
||||
private readonly FrameworkAddressResolver addressResolver;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly GameLifecycle lifecycle = Service<GameLifecycle>.Get();
|
||||
|
||||
|
|
@ -51,13 +50,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
private ulong tickCounter;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private Framework(TargetSigScanner sigScanner)
|
||||
private unsafe Framework()
|
||||
{
|
||||
this.hitchDetector = new HitchDetector("FrameworkUpdate", this.configuration.FrameworkUpdateHitch);
|
||||
|
||||
this.addressResolver = new FrameworkAddressResolver();
|
||||
this.addressResolver.Setup(sigScanner);
|
||||
|
||||
this.frameworkDestroy = new();
|
||||
this.frameworkThreadTaskScheduler = new();
|
||||
this.FrameworkThreadTaskFactory = new(
|
||||
|
|
@ -66,23 +62,13 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
TaskContinuationOptions.None,
|
||||
this.frameworkThreadTaskScheduler);
|
||||
|
||||
this.updateHook = Hook<OnUpdateDetour>.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<OnRealDestroyDelegate>.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy);
|
||||
this.updateHook = Hook<CSFramework.Delegates.Tick>.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Tick, this.HandleFrameworkUpdate);
|
||||
this.destroyHook = Hook<CSFramework.Delegates.Destroy>.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Destroy, this.HandleFrameworkDestroy);
|
||||
|
||||
this.updateHook.Enable();
|
||||
this.destroyHook.Enable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used during the native Framework::destroy.
|
||||
/// </summary>
|
||||
/// <param name="framework">The native Framework address.</param>
|
||||
/// <returns>A value indicating if the call was successful.</returns>
|
||||
public delegate bool OnRealDestroyDelegate(IntPtr framework);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IFramework.OnUpdateDelegate? Update;
|
||||
|
||||
|
|
@ -390,7 +376,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
}
|
||||
}
|
||||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework)
|
||||
private unsafe bool HandleFrameworkUpdate(CSFramework* thisPtr)
|
||||
{
|
||||
this.frameworkThreadTaskScheduler.BoundThread ??= Thread.CurrentThread;
|
||||
|
||||
|
|
@ -483,10 +469,10 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
this.hitchDetector.Stop();
|
||||
|
||||
original:
|
||||
return this.updateHook.OriginalDisposeSafe(framework);
|
||||
return this.updateHook.OriginalDisposeSafe(thisPtr);
|
||||
}
|
||||
|
||||
private bool HandleFrameworkDestroy(IntPtr framework)
|
||||
private unsafe bool HandleFrameworkDestroy(CSFramework* thisPtr)
|
||||
{
|
||||
this.frameworkDestroy.Cancel();
|
||||
this.DispatchUpdateEvents = false;
|
||||
|
|
@ -504,7 +490,7 @@ internal sealed class Framework : IInternalDisposableService, IFramework
|
|||
ServiceManager.WaitForServiceUnload();
|
||||
Log.Information("Framework::Destroy OK!");
|
||||
|
||||
return this.destroyHook.OriginalDisposeSafe(framework);
|
||||
return this.destroyHook.OriginalDisposeSafe(thisPtr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
namespace Dalamud.Game;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="Framework"/> class.
|
||||
/// </summary>
|
||||
internal sealed class FrameworkAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address for the function that is called once the Framework is destroyed.
|
||||
/// </summary>
|
||||
public IntPtr DestroyAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address for the function that is called once the Framework is free'd.
|
||||
/// </summary>
|
||||
public IntPtr FreeAddress { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function that is called every tick.
|
||||
/// </summary>
|
||||
public IntPtr TickAddress { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.SetupFramework(sig);
|
||||
}
|
||||
|
||||
private void SetupFramework(ISigScanner scanner)
|
||||
{
|
||||
this.DestroyAddress =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B 3D ?? ?? ?? ?? 48 8B D9 48 85 FF");
|
||||
|
||||
this.FreeAddress =
|
||||
scanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B D9 48 8B 0D ?? ?? ?? ?? 48 85 C9");
|
||||
|
||||
this.TickAddress =
|
||||
scanner.ScanText("40 53 48 83 EC 20 FF 81 ?? ?? ?? ?? 48 8B D9 48 8D 4C 24 ??");
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,19 @@ using Dalamud.Hooking;
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using LinkMacroPayloadType = Lumina.Text.Payloads.LinkMacroPayloadType;
|
||||
using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder;
|
||||
using ReadOnlySePayloadType = Lumina.Text.ReadOnly.ReadOnlySePayloadType;
|
||||
using ReadOnlySeStringSpan = Lumina.Text.ReadOnly.ReadOnlySeStringSpan;
|
||||
|
||||
namespace Dalamud.Game.Gui;
|
||||
|
||||
|
|
@ -27,14 +35,12 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
{
|
||||
private static readonly ModuleLog Log = new("ChatGui");
|
||||
|
||||
private readonly ChatGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new();
|
||||
private readonly Dictionary<(string PluginName, uint CommandId), Action<uint, SeString>> dalamudLinkHandlers = new();
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
private readonly Hook<InteractableLinkClickedDelegate> interactableLinkClickedHook;
|
||||
private readonly Hook<InventoryItem.Delegates.Copy> inventoryItemCopyHook;
|
||||
private readonly Hook<LogViewer.Delegates.HandleLinkClick> handleLinkClickHook;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -42,29 +48,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
private ImmutableDictionary<(string PluginName, uint CommandId), Action<uint, SeString>>? dalamudLinkHandlersCopy;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ChatGui(TargetSigScanner sigScanner)
|
||||
private ChatGui()
|
||||
{
|
||||
this.address = new ChatGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress((nint)RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
||||
this.populateItemLinkHook = Hook<PopulateItemLinkDelegate>.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
|
||||
this.interactableLinkClickedHook = Hook<InteractableLinkClickedDelegate>.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
|
||||
this.printMessageHook = Hook<PrintMessageDelegate>.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour);
|
||||
this.inventoryItemCopyHook = Hook<InventoryItem.Delegates.Copy>.FromAddress(InventoryItem.Addresses.Copy.Value, this.InventoryItemCopyDetour);
|
||||
this.handleLinkClickHook = Hook<LogViewer.Delegates.HandleLinkClick>.FromAddress(LogViewer.Addresses.HandleLinkClick.Value, this.HandleLinkClickDetour);
|
||||
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
this.interactableLinkClickedHook.Enable();
|
||||
this.inventoryItemCopyHook.Enable();
|
||||
this.handleLinkClickHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate uint PrintMessageDelegate(RaptureLogModule* manager, XivChatType chatType, Utf8String* sender, Utf8String* message, int timestamp, byte silent);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void InteractableLinkClickedDelegate(IntPtr managerPtr, IntPtr messagePtr);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IChatGui.OnMessageDelegate? ChatMessage;
|
||||
|
||||
|
|
@ -78,7 +75,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
public uint LastLinkedItemId { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
|
@ -106,8 +103,8 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
this.interactableLinkClickedHook.Dispose();
|
||||
this.inventoryItemCopyHook.Dispose();
|
||||
this.handleLinkClickHook.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
|
@ -275,21 +272,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
});
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr)
|
||||
private void InventoryItemCopyDetour(InventoryItem* thisPtr, InventoryItem* otherPtr)
|
||||
{
|
||||
this.inventoryItemCopyHook.Original(thisPtr, otherPtr);
|
||||
|
||||
try
|
||||
{
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
this.LastLinkedItemId = otherPtr->ItemId;
|
||||
this.LastLinkedItemFlags = (byte)otherPtr->Flags;
|
||||
|
||||
this.LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
this.LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
// Log.Verbose($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{this.LastLinkedItemId}");
|
||||
// Log.Verbose($"InventoryItemCopyDetour {thisPtr} {otherPtr} - linked:{this.LastLinkedItemId}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
Log.Error(ex, "Exception in InventoryItemCopyHook");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -299,58 +295,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
|
||||
try
|
||||
{
|
||||
var originalSenderData = sender->AsSpan().ToArray();
|
||||
var originalMessageData = message->AsSpan().ToArray();
|
||||
var parsedSender = SeString.Parse(sender->AsSpan());
|
||||
var parsedMessage = SeString.Parse(message->AsSpan());
|
||||
|
||||
var parsedSender = SeString.Parse(originalSenderData);
|
||||
var parsedMessage = SeString.Parse(originalMessageData);
|
||||
var terminatedSender = parsedSender.EncodeWithNullTerminator();
|
||||
var terminatedMessage = parsedMessage.EncodeWithNullTerminator();
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
|
||||
var invocationList = this.CheckMessageHandled!.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
if (this.CheckMessageHandled is { } handledCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as IChatGui.OnCheckMessageHandledDelegate;
|
||||
messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", @delegate.Method.Name);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isHandled)
|
||||
{
|
||||
invocationList = this.ChatMessage!.GetInvocationList();
|
||||
foreach (var @delegate in invocationList)
|
||||
foreach (var action in handledCallback.GetInvocationList().Cast<IChatGui.OnCheckMessageHandledDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageHandledDelegate = @delegate as IChatGui.OnMessageDelegate;
|
||||
messageHandledDelegate!.Invoke(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", @delegate.Method.Name);
|
||||
Log.Error(e, "Could not invoke registered OnCheckMessageHandledDelegate for {Name}", action.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var possiblyModifiedSenderData = parsedSender.Encode();
|
||||
var possiblyModifiedMessageData = parsedMessage.Encode();
|
||||
|
||||
if (!Util.FastByteArrayCompare(originalSenderData, possiblyModifiedSenderData))
|
||||
if (!isHandled && this.ChatMessage is { } callback)
|
||||
{
|
||||
Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(originalSenderData)} -> {parsedSender}");
|
||||
foreach (var action in callback.GetInvocationList().Cast<IChatGui.OnMessageDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
action(chatType, timestamp, ref parsedSender, ref parsedMessage, ref isHandled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not invoke registered OnMessageDelegate for {Name}", action.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var possiblyModifiedSenderData = parsedSender.EncodeWithNullTerminator();
|
||||
var possiblyModifiedMessageData = parsedMessage.EncodeWithNullTerminator();
|
||||
|
||||
if (!terminatedSender.SequenceEqual(possiblyModifiedSenderData))
|
||||
{
|
||||
Log.Verbose($"HandlePrintMessageDetour Sender modified: {SeString.Parse(terminatedSender)} -> {parsedSender}");
|
||||
sender->SetString(possiblyModifiedSenderData);
|
||||
}
|
||||
|
||||
if (!Util.FastByteArrayCompare(originalMessageData, possiblyModifiedMessageData))
|
||||
if (!terminatedMessage.SequenceEqual(possiblyModifiedMessageData))
|
||||
{
|
||||
Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(originalMessageData)} -> {parsedMessage}");
|
||||
Log.Verbose($"HandlePrintMessageDetour Message modified: {SeString.Parse(terminatedMessage)} -> {parsedMessage}");
|
||||
message->SetString(possiblyModifiedMessageData);
|
||||
}
|
||||
|
||||
|
|
@ -374,42 +369,57 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui
|
|||
return messageId;
|
||||
}
|
||||
|
||||
private void InteractableLinkClickedDetour(IntPtr managerPtr, IntPtr messagePtr)
|
||||
private void HandleLinkClickDetour(LogViewer* thisPtr, LinkData* linkData)
|
||||
{
|
||||
if ((Payload.EmbeddedInfoType)(linkData->LinkType + 1) != Payload.EmbeddedInfoType.DalamudLink)
|
||||
{
|
||||
this.handleLinkClickHook.Original(thisPtr, linkData);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
|
||||
var sb = LuminaSeStringBuilder.SharedPool.Get();
|
||||
try
|
||||
{
|
||||
var interactableType = (Payload.EmbeddedInfoType)(Marshal.ReadByte(messagePtr, 0x1B) + 1);
|
||||
var seStringSpan = new ReadOnlySeStringSpan(linkData->Payload);
|
||||
|
||||
if (interactableType != Payload.EmbeddedInfoType.DalamudLink)
|
||||
// read until link terminator
|
||||
foreach (var payload in seStringSpan)
|
||||
{
|
||||
this.interactableLinkClickedHook.Original(managerPtr, messagePtr);
|
||||
return;
|
||||
sb.Append(payload);
|
||||
|
||||
if (payload.Type == ReadOnlySePayloadType.Macro &&
|
||||
payload.MacroCode == Lumina.Text.Payloads.MacroCode.Link &&
|
||||
payload.TryGetExpression(out var expr1) &&
|
||||
expr1.TryGetInt(out var expr1Val) &&
|
||||
expr1Val == (int)LinkMacroPayloadType.Terminator)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Verbose($"InteractableLinkClicked: {Payload.EmbeddedInfoType.DalamudLink}");
|
||||
var seStr = SeString.Parse(sb.ToArray());
|
||||
if (seStr.Payloads.Count == 0 || seStr.Payloads[0] is not DalamudLinkPayload link)
|
||||
return;
|
||||
|
||||
var payloadPtr = Marshal.ReadIntPtr(messagePtr, 0x10);
|
||||
var seStr = MemoryHelper.ReadSeStringNullTerminated(payloadPtr);
|
||||
var terminatorIndex = seStr.Payloads.IndexOf(RawPayload.LinkTerminator);
|
||||
var payloads = terminatorIndex >= 0 ? seStr.Payloads.Take(terminatorIndex + 1).ToList() : seStr.Payloads;
|
||||
if (payloads.Count == 0) return;
|
||||
var linkPayload = payloads[0];
|
||||
if (linkPayload is DalamudLinkPayload link)
|
||||
if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value))
|
||||
{
|
||||
if (this.RegisteredLinkHandlers.TryGetValue((link.Plugin, link.CommandId), out var value))
|
||||
{
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
value.Invoke(link.CommandId, new SeString(payloads));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
Log.Verbose($"Sending DalamudLink to {link.Plugin}: {link.CommandId}");
|
||||
value.Invoke(link.CommandId, seStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug($"No DalamudLink registered for {link.Plugin} with ID of {link.CommandId}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception on InteractableLinkClicked hook");
|
||||
Log.Error(ex, "Exception in HandleLinkClickDetour");
|
||||
}
|
||||
finally
|
||||
{
|
||||
LuminaSeStringBuilder.SharedPool.Return(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -451,7 +461,7 @@ internal class ChatGuiPluginScoped : IInternalDisposableService, IChatGui
|
|||
public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||
public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags;
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
namespace Dalamud.Game.Gui;
|
||||
|
||||
/// <summary>
|
||||
/// The address resolver for the <see cref="ChatGui"/> class.
|
||||
/// </summary>
|
||||
internal sealed class ChatGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native PopulateItemLinkObject method.
|
||||
/// </summary>
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native InteractableLinkClicked method.
|
||||
/// </summary>
|
||||
public IntPtr InteractableLinkClicked { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
// PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
|
||||
// PopulateItemLinkObject = sig.ScanText( "48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A"); 5.0
|
||||
this.PopulateItemLinkObject = sig.ScanText("E8 ?? ?? ?? ?? 8B 4E FC");
|
||||
|
||||
this.InteractableLinkClicked = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 4B ?? E8 ?? ?? ?? ?? 33 D2");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
|
@ -8,6 +9,9 @@ using Dalamud.IoC.Internal;
|
|||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui.FlyText;
|
||||
|
|
@ -29,7 +33,7 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
private readonly Hook<CreateFlyTextDelegate> createFlyTextHook;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private FlyTextGui(TargetSigScanner sigScanner)
|
||||
private unsafe FlyTextGui(TargetSigScanner sigScanner)
|
||||
{
|
||||
this.Address = new FlyTextGuiAddressResolver();
|
||||
this.Address.Setup(sigScanner);
|
||||
|
|
@ -43,29 +47,29 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
/// <summary>
|
||||
/// Private delegate for the native CreateFlyText function's hook.
|
||||
/// </summary>
|
||||
private delegate IntPtr CreateFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
private unsafe delegate nint CreateFlyTextDelegate(
|
||||
AtkUnitBase* thisPtr,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
byte* text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
uint damageTypeIcon,
|
||||
IntPtr text1,
|
||||
byte* text1,
|
||||
float yOffset);
|
||||
|
||||
/// <summary>
|
||||
/// Private delegate for the native AddFlyText function pointer.
|
||||
/// </summary>
|
||||
private delegate void AddFlyTextDelegate(
|
||||
IntPtr addonFlyText,
|
||||
private unsafe delegate void AddFlyTextDelegate(
|
||||
AtkUnitBase* thisPtr,
|
||||
uint actorIndex,
|
||||
uint messageMax,
|
||||
IntPtr numbers,
|
||||
NumberArrayData* numberArrayData,
|
||||
uint offsetNum,
|
||||
uint offsetNumMax,
|
||||
IntPtr strings,
|
||||
StringArrayData* stringArrayData,
|
||||
uint offsetStr,
|
||||
uint offsetStrMax,
|
||||
int unknown);
|
||||
|
|
@ -87,26 +91,16 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
public unsafe void AddFlyText(FlyTextKind kind, uint actorIndex, uint val1, uint val2, SeString text1, SeString text2, uint color, uint icon, uint damageTypeIcon)
|
||||
{
|
||||
// Known valid flytext region within the atk arrays
|
||||
var numIndex = 30;
|
||||
var strIndex = 27;
|
||||
var numOffset = 161u;
|
||||
var strOffset = 28u;
|
||||
|
||||
// Get the UI module and flytext addon pointers
|
||||
var gameGui = Service<GameGui>.GetNullable();
|
||||
if (gameGui == null)
|
||||
return;
|
||||
|
||||
var ui = (FFXIVClientStructs.FFXIV.Client.UI.UIModule*)gameGui.GetUIModule();
|
||||
var flytext = gameGui.GetAddonByName("_FlyText");
|
||||
|
||||
if (ui == null || flytext == IntPtr.Zero)
|
||||
var flytext = RaptureAtkUnitManager.Instance()->GetAddonByName("_FlyText");
|
||||
if (flytext == null)
|
||||
return;
|
||||
|
||||
// Get the number and string arrays we need
|
||||
var atkArrayDataHolder = ui->GetRaptureAtkModule()->AtkModule.AtkArrayDataHolder;
|
||||
var numArray = atkArrayDataHolder._NumberArrays[numIndex];
|
||||
var strArray = atkArrayDataHolder._StringArrays[strIndex];
|
||||
var numArray = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.FlyText);
|
||||
var strArray = AtkStage.Instance()->GetStringArrayData(StringArrayType.FlyText);
|
||||
|
||||
// Write the values to the arrays using a known valid flytext region
|
||||
numArray->IntArray[numOffset + 0] = 1; // Some kind of "Enabled" flag for this section
|
||||
|
|
@ -120,44 +114,35 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
numArray->IntArray[numOffset + 8] = 0; // Unknown
|
||||
numArray->IntArray[numOffset + 9] = 0; // Unknown, has something to do with yOffset
|
||||
|
||||
strArray->SetValue((int)strOffset + 0, text1.Encode(), false, true, false);
|
||||
strArray->SetValue((int)strOffset + 1, text2.Encode(), false, true, false);
|
||||
strArray->SetValue((int)strOffset + 0, text1.EncodeWithNullTerminator(), false, true, false);
|
||||
strArray->SetValue((int)strOffset + 1, text2.EncodeWithNullTerminator(), false, true, false);
|
||||
|
||||
this.addFlyTextNative(
|
||||
flytext,
|
||||
actorIndex,
|
||||
1,
|
||||
(IntPtr)numArray,
|
||||
numArray,
|
||||
numOffset,
|
||||
10,
|
||||
(IntPtr)strArray,
|
||||
strArray,
|
||||
strOffset,
|
||||
2,
|
||||
0);
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private IntPtr CreateFlyTextDetour(
|
||||
IntPtr addonFlyText,
|
||||
private unsafe nint CreateFlyTextDetour(
|
||||
AtkUnitBase* thisPtr,
|
||||
FlyTextKind kind,
|
||||
int val1,
|
||||
int val2,
|
||||
IntPtr text2,
|
||||
byte* text2,
|
||||
uint color,
|
||||
uint icon,
|
||||
uint damageTypeIcon,
|
||||
IntPtr text1,
|
||||
byte* text1,
|
||||
float yOffset)
|
||||
{
|
||||
var retVal = IntPtr.Zero;
|
||||
var retVal = nint.Zero;
|
||||
try
|
||||
{
|
||||
Log.Verbose("[FlyText] Enter CreateFlyText detour!");
|
||||
|
|
@ -167,19 +152,19 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
var tmpKind = kind;
|
||||
var tmpVal1 = val1;
|
||||
var tmpVal2 = val2;
|
||||
var tmpText1 = text1 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text1);
|
||||
var tmpText2 = text2 == IntPtr.Zero ? string.Empty : MemoryHelper.ReadSeStringNullTerminated(text2);
|
||||
var tmpText1 = text1 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text1);
|
||||
var tmpText2 = text2 == null ? string.Empty : MemoryHelper.ReadSeStringNullTerminated((nint)text2);
|
||||
var tmpColor = color;
|
||||
var tmpIcon = icon;
|
||||
var tmpDamageTypeIcon = damageTypeIcon;
|
||||
var tmpYOffset = yOffset;
|
||||
|
||||
var cmpText1 = tmpText1.ToString();
|
||||
var cmpText2 = tmpText2.ToString();
|
||||
var originalText1 = tmpText1.EncodeWithNullTerminator();
|
||||
var originalText2 = tmpText2.EncodeWithNullTerminator();
|
||||
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({addonFlyText.ToInt64():X}) " +
|
||||
Log.Verbose($"[FlyText] Called with addonFlyText({(nint)thisPtr:X}) " +
|
||||
$"kind({kind}) val1({val1}) val2({val2}) damageTypeIcon({damageTypeIcon}) " +
|
||||
$"text1({text1.ToInt64():X}, \"{tmpText1}\") text2({text2.ToInt64():X}, \"{tmpText2}\") " +
|
||||
$"text1({(nint)text1:X}, \"{tmpText1}\") text2({(nint)text2:X}, \"{tmpText2}\") " +
|
||||
$"color({color:X}) icon({icon}) yOffset({yOffset})");
|
||||
Log.Verbose("[FlyText] Calling flytext events!");
|
||||
this.FlyTextCreated?.Invoke(
|
||||
|
|
@ -204,12 +189,15 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var maybeModifiedText1 = tmpText1.EncodeWithNullTerminator();
|
||||
var maybeModifiedText2 = tmpText2.EncodeWithNullTerminator();
|
||||
|
||||
// Check if any values have changed
|
||||
var dirty = tmpKind != kind ||
|
||||
tmpVal1 != val1 ||
|
||||
tmpVal2 != val2 ||
|
||||
tmpText1.ToString() != cmpText1 ||
|
||||
tmpText2.ToString() != cmpText2 ||
|
||||
!maybeModifiedText1.SequenceEqual(originalText1) ||
|
||||
!maybeModifiedText2.SequenceEqual(originalText2) ||
|
||||
tmpDamageTypeIcon != damageTypeIcon ||
|
||||
tmpColor != color ||
|
||||
tmpIcon != icon ||
|
||||
|
|
@ -219,28 +207,26 @@ internal sealed class FlyTextGui : IInternalDisposableService, IFlyTextGui
|
|||
if (!dirty)
|
||||
{
|
||||
Log.Verbose("[FlyText] Calling flytext with original args.");
|
||||
return this.createFlyTextHook.Original(addonFlyText, kind, val1, val2, text2, color, icon,
|
||||
return this.createFlyTextHook.Original(thisPtr, kind, val1, val2, text2, color, icon,
|
||||
damageTypeIcon, text1, yOffset);
|
||||
}
|
||||
|
||||
var terminated1 = Terminate(tmpText1.Encode());
|
||||
var terminated2 = Terminate(tmpText2.Encode());
|
||||
var pText1 = Marshal.AllocHGlobal(terminated1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(terminated2.Length);
|
||||
Marshal.Copy(terminated1, 0, pText1, terminated1.Length);
|
||||
Marshal.Copy(terminated2, 0, pText2, terminated2.Length);
|
||||
var pText1 = Marshal.AllocHGlobal(maybeModifiedText1.Length);
|
||||
var pText2 = Marshal.AllocHGlobal(maybeModifiedText2.Length);
|
||||
Marshal.Copy(maybeModifiedText1, 0, pText1, maybeModifiedText1.Length);
|
||||
Marshal.Copy(maybeModifiedText2, 0, pText2, maybeModifiedText2.Length);
|
||||
Log.Verbose("[FlyText] Allocated and set strings.");
|
||||
|
||||
retVal = this.createFlyTextHook.Original(
|
||||
addonFlyText,
|
||||
thisPtr,
|
||||
tmpKind,
|
||||
tmpVal1,
|
||||
tmpVal2,
|
||||
pText2,
|
||||
(byte*)pText2,
|
||||
tmpColor,
|
||||
tmpIcon,
|
||||
tmpDamageTypeIcon,
|
||||
pText1,
|
||||
(byte*)pText1,
|
||||
tmpYOffset);
|
||||
|
||||
Log.Verbose("[FlyText] Returned from original. Delaying free task.");
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
|
|
@ -18,7 +17,6 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|||
using FFXIVClientStructs.FFXIV.Common.Component.BGCollision;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using ImGuiNET;
|
||||
using SharpDX;
|
||||
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
using Vector3 = System.Numerics.Vector3;
|
||||
|
|
@ -33,21 +31,19 @@ namespace Dalamud.Game.Gui;
|
|||
internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
||||
{
|
||||
private static readonly ModuleLog Log = new("GameGui");
|
||||
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly Framework framework = Service<Framework>.Get();
|
||||
|
||||
private readonly GameGuiAddressResolver address;
|
||||
|
||||
private readonly Hook<SetGlobalBgmDelegate> setGlobalBgmHook;
|
||||
private readonly Hook<HandleItemHoverDelegate> handleItemHoverHook;
|
||||
private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
|
||||
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
|
||||
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||
private readonly Hook<HandleImmDelegate> handleImmHook;
|
||||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||
private readonly Hook<RaptureAtkModule.Delegates.SetUiVisibility> setUiVisibilityHook;
|
||||
private readonly Hook<Utf8StringFromSequenceDelegate> utf8StringFromSequenceHook;
|
||||
|
||||
private GetUIMapObjectDelegate? getUIMapObject;
|
||||
private OpenMapWithFlagDelegate? openMapWithFlag;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private GameGui(TargetSigScanner sigScanner)
|
||||
{
|
||||
|
|
@ -57,32 +53,27 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
Log.Verbose("===== G A M E G U I =====");
|
||||
Log.Verbose($"GameGuiManager address {Util.DescribeAddress(this.address.BaseAddress)}");
|
||||
Log.Verbose($"SetGlobalBgm address {Util.DescribeAddress(this.address.SetGlobalBgm)}");
|
||||
Log.Verbose($"HandleItemHover address {Util.DescribeAddress(this.address.HandleItemHover)}");
|
||||
Log.Verbose($"HandleItemOut address {Util.DescribeAddress(this.address.HandleItemOut)}");
|
||||
Log.Verbose($"HandleImm address {Util.DescribeAddress(this.address.HandleImm)}");
|
||||
|
||||
this.setGlobalBgmHook = Hook<SetGlobalBgmDelegate>.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
|
||||
|
||||
this.handleItemHoverHook = Hook<HandleItemHoverDelegate>.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
||||
this.handleItemOutHook = Hook<HandleItemOutDelegate>.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour);
|
||||
|
||||
this.handleActionHoverHook = Hook<HandleActionHoverDelegate>.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour);
|
||||
this.handleActionOutHook = Hook<HandleActionOutDelegate>.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour);
|
||||
|
||||
this.handleImmHook = Hook<HandleImmDelegate>.FromAddress(this.address.HandleImm, this.HandleImmDetour);
|
||||
|
||||
this.toggleUiHideHook = Hook<ToggleUiHideDelegate>.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour);
|
||||
this.setUiVisibilityHook = Hook<RaptureAtkModule.Delegates.SetUiVisibility>.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour);
|
||||
|
||||
this.utf8StringFromSequenceHook = Hook<Utf8StringFromSequenceDelegate>.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
|
||||
|
||||
this.setGlobalBgmHook.Enable();
|
||||
this.handleItemHoverHook.Enable();
|
||||
this.handleItemOutHook.Enable();
|
||||
this.handleImmHook.Enable();
|
||||
this.toggleUiHideHook.Enable();
|
||||
this.setUiVisibilityHook.Enable();
|
||||
this.handleActionHoverHook.Enable();
|
||||
this.handleActionOutHook.Enable();
|
||||
this.utf8StringFromSequenceHook.Enable();
|
||||
|
||||
this.framework.Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
// Hooked delegates
|
||||
|
|
@ -90,21 +81,9 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate Utf8String* Utf8StringFromSequenceDelegate(Utf8String* thisPtr, byte* sourcePtr, nuint sourceLen);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetUIMapObjectDelegate(IntPtr uiObject);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall, CharSet = CharSet.Ansi)]
|
||||
private delegate bool OpenMapWithFlagDelegate(IntPtr uiMapObject, string flag);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr SetGlobalBgmDelegate(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleItemHoverDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleItemOutDelegate(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void HandleActionHoverDelegate(IntPtr hoverState, HoverActionKind a2, uint a3, int a4, byte a5);
|
||||
|
||||
|
|
@ -113,9 +92,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, bool uiVisible);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event EventHandler<bool>? UiHideToggled;
|
||||
|
|
@ -137,33 +113,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
|
||||
/// <inheritdoc/>
|
||||
public bool OpenMapWithMapLink(MapLinkPayload mapLink)
|
||||
{
|
||||
var uiModule = this.GetUIModule();
|
||||
|
||||
if (uiModule == IntPtr.Zero)
|
||||
{
|
||||
Log.Error("OpenMapWithMapLink: Null pointer returned from getUIObject()");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.getUIMapObject ??= this.address.GetVirtualFunction<GetUIMapObjectDelegate>(uiModule, 0, 8);
|
||||
|
||||
var uiMapObjectPtr = this.getUIMapObject(uiModule);
|
||||
|
||||
if (uiMapObjectPtr == IntPtr.Zero)
|
||||
{
|
||||
Log.Error("OpenMapWithMapLink: Null pointer returned from GetUIMapObject()");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.openMapWithFlag ??= this.address.GetVirtualFunction<OpenMapWithFlagDelegate>(uiMapObjectPtr, 0, 63);
|
||||
|
||||
var mapLinkString = mapLink.DataString;
|
||||
|
||||
Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}");
|
||||
|
||||
return this.openMapWithFlag(uiMapObjectPtr, mapLinkString);
|
||||
}
|
||||
=> RaptureAtkModule.Instance()->OpenMapWithMapLink(mapLink.DataString);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
|
||||
|
|
@ -311,11 +261,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
/// </summary>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.framework.Update -= this.FrameworkUpdate;
|
||||
|
||||
this.setGlobalBgmHook.Dispose();
|
||||
this.handleItemHoverHook.Dispose();
|
||||
this.handleItemOutHook.Dispose();
|
||||
this.handleImmHook.Dispose();
|
||||
this.toggleUiHideHook.Dispose();
|
||||
this.setUiVisibilityHook.Dispose();
|
||||
this.handleActionHoverHook.Dispose();
|
||||
this.handleActionOutHook.Dispose();
|
||||
this.utf8StringFromSequenceHook.Dispose();
|
||||
|
|
@ -359,51 +309,6 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemHoverDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemHoverHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (retVal.ToInt64() == 22)
|
||||
{
|
||||
var itemId = (ulong)Marshal.ReadInt32(hoverState, 0x138);
|
||||
this.HoveredItem = itemId;
|
||||
|
||||
this.HoveredItemChanged?.InvokeSafely(this, itemId);
|
||||
|
||||
Log.Verbose($"HoverItemId:{itemId} this:{hoverState.ToInt64()}");
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr HandleItemOutDetour(IntPtr hoverState, IntPtr a2, IntPtr a3, ulong a4)
|
||||
{
|
||||
var retVal = this.handleItemOutHook.Original(hoverState, a2, a3, a4);
|
||||
|
||||
if (a3 != IntPtr.Zero && a4 == 1)
|
||||
{
|
||||
var a3Val = Marshal.ReadByte(a3, 0x8);
|
||||
|
||||
if (a3Val == 255)
|
||||
{
|
||||
this.HoveredItem = 0ul;
|
||||
|
||||
try
|
||||
{
|
||||
this.HoveredItemChanged?.Invoke(this, 0ul);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Could not dispatch HoveredItemChanged event.");
|
||||
}
|
||||
|
||||
Log.Verbose("HoverItemId: 0");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void HandleActionHoverDetour(IntPtr hoverState, HoverActionKind actionKind, uint actionId, int a4, byte a5)
|
||||
{
|
||||
this.handleActionHoverHook.Original(hoverState, actionKind, actionId, a4, a5);
|
||||
|
|
@ -445,16 +350,14 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private IntPtr ToggleUiHideDetour(IntPtr thisPtr, bool unknownByte)
|
||||
private unsafe void SetUiVisibilityDetour(RaptureAtkModule* thisPtr, bool uiVisible)
|
||||
{
|
||||
var result = this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||
this.setUiVisibilityHook.Original(thisPtr, uiVisible);
|
||||
|
||||
this.GameUiHidden = !RaptureAtkModule.Instance()->IsUiVisible;
|
||||
this.UiHideToggled?.InvokeSafely(this, this.GameUiHidden);
|
||||
|
||||
Log.Debug("UiHide toggled: {0}", this.GameUiHidden);
|
||||
|
||||
return result;
|
||||
Log.Debug("GameUiHidden: {0}", this.GameUiHidden);
|
||||
}
|
||||
|
||||
private char HandleImmDetour(IntPtr framework, char a2, byte a3)
|
||||
|
|
@ -477,6 +380,24 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui
|
|||
|
||||
return thisPtr; // this function shouldn't need to return but the original asm moves this into rax before returning so be safe?
|
||||
}
|
||||
|
||||
private unsafe void FrameworkUpdate(IFramework framework)
|
||||
{
|
||||
var agentItemDetail = AgentItemDetail.Instance();
|
||||
if (agentItemDetail != null)
|
||||
{
|
||||
var itemId = agentItemDetail->ItemId;
|
||||
|
||||
if (this.HoveredItem != itemId)
|
||||
{
|
||||
Log.Verbose($"HoveredItem changed: {itemId}");
|
||||
|
||||
this.HoveredItem = itemId;
|
||||
|
||||
this.HoveredItemChanged?.InvokeSafely(this, itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -15,16 +15,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
|||
/// </summary>
|
||||
public IntPtr SetGlobalBgm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemHover method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemHover { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleItemOut method.
|
||||
/// </summary>
|
||||
public IntPtr HandleItemOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleActionHover method.
|
||||
/// </summary>
|
||||
|
|
@ -40,11 +30,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
|||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ToggleUiHide method.
|
||||
/// </summary>
|
||||
public IntPtr ToggleUiHide { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native Utf8StringFromSequence method.
|
||||
/// </summary>
|
||||
|
|
@ -54,13 +39,10 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver
|
|||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.SetGlobalBgm = sig.ScanText("E8 ?? ?? ?? ?? 8B 2F");
|
||||
this.HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 6C 24 48 48 8B 74 24 50 4C 89 B7 08 01 00 00");
|
||||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
|
||||
this.ToggleUiHide = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 44 0F B6 81");
|
||||
this.Utf8StringFromSequence = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8D 41 22 66 C7 41 ?? ?? ?? 48 89 01 49 8B D8");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
|
|
@ -20,16 +20,6 @@ namespace Dalamud.Game.Gui.NamePlate;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
||||
{
|
||||
/// <summary>
|
||||
/// The index for the number array used by the NamePlate addon.
|
||||
/// </summary>
|
||||
public const int NumberArrayIndex = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The index for the string array used by the NamePlate addon.
|
||||
/// </summary>
|
||||
public const int StringArrayIndex = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The index for of the FullUpdate entry in the NamePlate number array.
|
||||
/// </summary>
|
||||
|
|
@ -81,18 +71,11 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui
|
|||
/// <inheritdoc/>
|
||||
public unsafe void RequestRedraw()
|
||||
{
|
||||
var addon = this.gameGui.GetAddonByName("NamePlate");
|
||||
if (addon != 0)
|
||||
var addon = (AddonNamePlate*)this.gameGui.GetAddonByName("NamePlate");
|
||||
if (addon != null)
|
||||
{
|
||||
var raptureAtkModule = RaptureAtkModule.Instance();
|
||||
if (raptureAtkModule == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
((AddonNamePlate*)addon)->DoFullUpdate = 1;
|
||||
var namePlateNumberArrayData = raptureAtkModule->AtkArrayDataHolder.NumberArrays[NumberArrayIndex];
|
||||
namePlateNumberArrayData->SetValue(NumberArrayFullUpdateIndex, 1);
|
||||
addon->DoFullUpdate = 1;
|
||||
AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate)->SetValue(NumberArrayFullUpdateIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -140,9 +140,9 @@ internal unsafe class NamePlateUpdateContext : INamePlateUpdateContext
|
|||
public void ResetState(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
|
||||
{
|
||||
this.Addon = (AddonNamePlate*)addon;
|
||||
this.NumberData = numberArrayData[NamePlateGui.NumberArrayIndex];
|
||||
this.NumberData = AtkStage.Instance()->GetNumberArrayData(NumberArrayType.NamePlate);
|
||||
this.NumberStruct = (AddonNamePlate.AddonNamePlateNumberArray*)this.NumberData->IntArray;
|
||||
this.StringData = stringArrayData[NamePlateGui.StringArrayIndex];
|
||||
this.StringData = AtkStage.Instance()->GetStringArrayData(StringArrayType.NamePlate);
|
||||
this.HasParts = false;
|
||||
|
||||
this.ActiveNamePlateCount = this.NumberStruct->ActiveNamePlateCount;
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@ using Dalamud.Game.Text.SeStringHandling;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
|
||||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -17,8 +20,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
{
|
||||
private const uint QuestToastCheckmarkMagic = 60081;
|
||||
|
||||
private readonly ToastGuiAddressResolver address;
|
||||
|
||||
private readonly Queue<(byte[] Message, ToastOptions Options)> normalQueue = new();
|
||||
private readonly Queue<(byte[] Message, QuestToastOptions Options)> questQueue = new();
|
||||
private readonly Queue<byte[]> errorQueue = new();
|
||||
|
|
@ -30,16 +31,12 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
/// <param name="sigScanner">Sig scanner to use.</param>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private ToastGui(TargetSigScanner sigScanner)
|
||||
private unsafe ToastGui()
|
||||
{
|
||||
this.address = new ToastGuiAddressResolver();
|
||||
this.address.Setup(sigScanner);
|
||||
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour);
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour);
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour);
|
||||
this.showNormalToastHook = Hook<ShowNormalToastDelegate>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowWideText, this.HandleNormalToastDetour);
|
||||
this.showQuestToastHook = Hook<ShowQuestToastDelegate>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowText, this.HandleQuestToastDetour);
|
||||
this.showErrorToastHook = Hook<ShowErrorToastDelegate>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowErrorText, this.HandleErrorToastDetour);
|
||||
|
||||
this.showNormalToastHook.Enable();
|
||||
this.showQuestToastHook.Enable();
|
||||
|
|
@ -48,16 +45,16 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
|
||||
#region Marshal delegates
|
||||
|
||||
private delegate IntPtr ShowNormalToastDelegate(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId);
|
||||
private unsafe delegate void ShowNormalToastDelegate(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId);
|
||||
|
||||
private delegate byte ShowQuestToastDelegate(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||
private unsafe delegate void ShowQuestToastDelegate(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound);
|
||||
|
||||
private delegate byte ShowErrorToastDelegate(IntPtr manager, IntPtr text, byte respectsHidingMaybe);
|
||||
private unsafe delegate void ShowErrorToastDelegate(UIModule* thisPtr, byte* text, byte respectsHidingMaybe);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IToastGui.OnNormalToastDelegate? Toast;
|
||||
|
||||
|
|
@ -102,32 +99,6 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui
|
|||
this.ShowError(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Terminate(byte[] source)
|
||||
{
|
||||
var terminated = new byte[source.Length + 1];
|
||||
Array.Copy(source, 0, terminated, 0, source.Length);
|
||||
terminated[^1] = 0;
|
||||
|
||||
return terminated;
|
||||
}
|
||||
|
||||
private SeString ParseString(IntPtr text)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
unsafe
|
||||
{
|
||||
var ptr = (byte*)text;
|
||||
while (*ptr != 0)
|
||||
{
|
||||
bytes.Add(*ptr);
|
||||
ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// call events
|
||||
return SeString.Parse(bytes.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -149,36 +120,30 @@ internal sealed partial class ToastGui
|
|||
this.normalQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowNormal(byte[] bytes, ToastOptions? options = null)
|
||||
private unsafe void ShowNormal(byte[] bytes, ToastOptions? options = null)
|
||||
{
|
||||
options ??= new ToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = bytes.NullTerminate())
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleNormalToastDetour(manager!.Value, (IntPtr)ptr, 5, (byte)options.Position, (byte)options.Speed, 0);
|
||||
}
|
||||
this.HandleNormalToastDetour(
|
||||
UIModule.Instance(),
|
||||
ptr,
|
||||
5,
|
||||
(byte)options.Position,
|
||||
(byte)options.Speed,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
private IntPtr HandleNormalToastDetour(IntPtr manager, IntPtr text, int layer, byte isTop, byte isFast, int logMessageId)
|
||||
private unsafe void HandleNormalToastDetour(UIModule* thisPtr, byte* text, int layer, byte isTop, byte isFast, uint logMessageId)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var str = MemoryHelper.ReadSeStringNullTerminated((nint)text);
|
||||
var options = new ToastOptions
|
||||
{
|
||||
Position = (ToastPosition)isTop,
|
||||
|
|
@ -189,18 +154,17 @@ internal sealed partial class ToastGui
|
|||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
return;
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = str.EncodeWithNullTerminator())
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showNormalToastHook.Original(manager, (IntPtr)message, layer, (byte)options.Position, (byte)options.Speed, logMessageId);
|
||||
}
|
||||
this.showNormalToastHook.Original(
|
||||
thisPtr,
|
||||
ptr,
|
||||
layer,
|
||||
(byte)(options.Position == ToastPosition.Top ? 1 : 0),
|
||||
(byte)(options.Speed == ToastSpeed.Fast ? 1 : 0),
|
||||
logMessageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -224,45 +188,33 @@ internal sealed partial class ToastGui
|
|||
this.questQueue.Enqueue((message.Encode(), options));
|
||||
}
|
||||
|
||||
private void ShowQuest(byte[] bytes, QuestToastOptions? options = null)
|
||||
private unsafe void ShowQuest(byte[] bytes, QuestToastOptions? options = null)
|
||||
{
|
||||
options ??= new QuestToastOptions();
|
||||
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = bytes.NullTerminate())
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleQuestToastDetour(
|
||||
manager!.Value,
|
||||
(int)options.Position,
|
||||
(IntPtr)ptr,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
this.HandleQuestToastDetour(
|
||||
UIModule.Instance(),
|
||||
(int)options.Position,
|
||||
ptr,
|
||||
ioc1,
|
||||
(byte)(options.PlaySound ? 1 : 0),
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleQuestToastDetour(IntPtr manager, int position, IntPtr text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound)
|
||||
private unsafe void HandleQuestToastDetour(UIModule* thisPtr, int position, byte* text, uint iconOrCheck1, byte playSound, uint iconOrCheck2, byte alsoPlaySound)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var str = SeString.Parse(text);
|
||||
var options = new QuestToastOptions
|
||||
{
|
||||
Position = (QuestToastPosition)position,
|
||||
|
|
@ -275,27 +227,20 @@ internal sealed partial class ToastGui
|
|||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
return;
|
||||
|
||||
var (ioc1, ioc2) = this.DetermineParameterOrder(options);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = str.EncodeWithNullTerminator())
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showQuestToastHook.Original(
|
||||
manager,
|
||||
(int)options.Position,
|
||||
(IntPtr)message,
|
||||
ioc1,
|
||||
options.PlaySound ? (byte)1 : (byte)0,
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
this.showQuestToastHook.Original(
|
||||
UIModule.Instance(),
|
||||
(int)options.Position,
|
||||
ptr,
|
||||
ioc1,
|
||||
(byte)(options.PlaySound ? 1 : 0),
|
||||
ioc2,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,51 +269,32 @@ internal sealed partial class ToastGui
|
|||
this.errorQueue.Enqueue(message.Encode());
|
||||
}
|
||||
|
||||
private void ShowError(byte[] bytes)
|
||||
private unsafe void ShowError(byte[] bytes)
|
||||
{
|
||||
var manager = Service<GameGui>.GetNullable()?.GetUIModule();
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
// terminate the string
|
||||
var terminated = Terminate(bytes);
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = bytes.NullTerminate())
|
||||
{
|
||||
fixed (byte* ptr = terminated)
|
||||
{
|
||||
this.HandleErrorToastDetour(manager!.Value, (IntPtr)ptr, 0);
|
||||
}
|
||||
this.HandleErrorToastDetour(UIModule.Instance(), ptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private byte HandleErrorToastDetour(IntPtr manager, IntPtr text, byte respectsHidingMaybe)
|
||||
private unsafe void HandleErrorToastDetour(UIModule* thisPtr, byte* text, byte respectsHidingMaybe)
|
||||
{
|
||||
if (text == IntPtr.Zero)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
// call events
|
||||
var isHandled = false;
|
||||
var str = this.ParseString(text);
|
||||
var str = SeString.Parse(text);
|
||||
|
||||
this.ErrorToast?.Invoke(ref str, ref isHandled);
|
||||
|
||||
// do nothing if handled
|
||||
if (isHandled)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return;
|
||||
|
||||
var terminated = Terminate(str.Encode());
|
||||
|
||||
unsafe
|
||||
fixed (byte* ptr = str.EncodeWithNullTerminator())
|
||||
{
|
||||
fixed (byte* message = terminated)
|
||||
{
|
||||
return this.showErrorToastHook.Original(manager, (IntPtr)message, respectsHidingMaybe);
|
||||
}
|
||||
this.showErrorToastHook.Original(thisPtr, ptr, respectsHidingMaybe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
namespace Dalamud.Game.Gui.Toast;
|
||||
|
||||
/// <summary>
|
||||
/// An address resolver for the <see cref="ToastGui"/> class.
|
||||
/// </summary>
|
||||
internal class ToastGuiAddressResolver : BaseAddressResolver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowNormalToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowNormalToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native ShowQuestToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowQuestToast { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the ShowErrorToast method.
|
||||
/// </summary>
|
||||
public IntPtr ShowErrorToast { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void Setup64Bit(ISigScanner sig)
|
||||
{
|
||||
this.ShowNormalToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowQuestToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 48 89 7C 24 ?? 41 56 48 83 EC 40 83 3D ?? ?? ?? ?? ??");
|
||||
this.ShowErrorToast = sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 83 3D ?? ?? ?? ?? ?? 41 0F B6 F0");
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,11 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||
using Serilog;
|
||||
|
||||
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||
|
||||
|
|
@ -20,12 +22,14 @@ namespace Dalamud.Game.Internal;
|
|||
[ServiceManager.EarlyLoadedService]
|
||||
internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
||||
{
|
||||
private static readonly ModuleLog Log = new("DalamudAtkTweaks");
|
||||
|
||||
private readonly Hook<AgentHudOpenSystemMenuPrototype> hookAgentHudOpenSystemMenu;
|
||||
|
||||
// TODO: Make this into events in Framework.Gui
|
||||
private readonly Hook<UiModuleRequestMainCommand> hookUiModuleRequestMainCommand;
|
||||
private readonly Hook<UIModule.Delegates.ExecuteMainCommand> hookUiModuleExecuteMainCommand;
|
||||
|
||||
private readonly Hook<AtkUnitBaseReceiveGlobalEvent> hookAtkUnitBaseReceiveGlobalEvent;
|
||||
private readonly Hook<AtkUnitBase.Delegates.ReceiveGlobalEvent> hookAtkUnitBaseReceiveGlobalEvent;
|
||||
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly DalamudConfiguration configuration = Service<DalamudConfiguration>.Get();
|
||||
|
|
@ -44,12 +48,8 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
var openSystemMenuAddress = sigScanner.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 48 8B CF 4C 89 B4 24 B8 08 00 00");
|
||||
|
||||
this.hookAgentHudOpenSystemMenu = Hook<AgentHudOpenSystemMenuPrototype>.FromAddress(openSystemMenuAddress, this.AgentHudOpenSystemMenuDetour);
|
||||
|
||||
var uiModuleRequestMainCommandAddress = sigScanner.ScanText("40 53 56 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 8B 01 8B DA 48 8B F1 FF 90 ?? ?? ?? ??");
|
||||
this.hookUiModuleRequestMainCommand = Hook<UiModuleRequestMainCommand>.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour);
|
||||
|
||||
var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 54 41 57");
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBaseReceiveGlobalEvent>.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour);
|
||||
this.hookUiModuleExecuteMainCommand = Hook<UIModule.Delegates.ExecuteMainCommand>.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour);
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent = Hook<AtkUnitBase.Delegates.ReceiveGlobalEvent>.FromAddress((nint)AtkUnitBase.StaticVirtualTablePointer->ReceiveGlobalEvent, this.AtkUnitBaseReceiveGlobalEventDetour);
|
||||
|
||||
this.locDalamudPlugins = Loc.Localize("SystemMenuPlugins", "Dalamud Plugins");
|
||||
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
|
||||
|
|
@ -57,18 +57,14 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
// this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
|
||||
|
||||
this.hookAgentHudOpenSystemMenu.Enable();
|
||||
this.hookUiModuleRequestMainCommand.Enable();
|
||||
this.hookUiModuleExecuteMainCommand.Enable();
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
|
||||
}
|
||||
|
||||
/// <summary>Finalizes an instance of the <see cref="DalamudAtkTweaks"/> class.</summary>
|
||||
~DalamudAtkTweaks() => this.Dispose(false);
|
||||
|
||||
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||
|
||||
private delegate void UiModuleRequestMainCommand(void* thisPtr, int commandId);
|
||||
|
||||
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
|
||||
private delegate void AgentHudOpenSystemMenuPrototype(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize);
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService() => this.Dispose(true);
|
||||
|
|
@ -81,7 +77,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
if (disposing)
|
||||
{
|
||||
this.hookAgentHudOpenSystemMenu.Dispose();
|
||||
this.hookUiModuleRequestMainCommand.Dispose();
|
||||
this.hookUiModuleExecuteMainCommand.Dispose();
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Dispose();
|
||||
|
||||
// this.contextMenu.ContextMenuOpened -= this.ContextMenuOnContextMenuOpened;
|
||||
|
|
@ -116,22 +112,19 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
}
|
||||
*/
|
||||
|
||||
private IntPtr AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* arg)
|
||||
private void AtkUnitBaseReceiveGlobalEventDetour(AtkUnitBase* thisPtr, AtkEventType eventType, int eventParam, AtkEvent* atkEvent, AtkEventData* atkEventData)
|
||||
{
|
||||
// Log.Information("{0}: cmd#{1} a3#{2} - HasAnyFocus:{3}", MemoryHelper.ReadSeStringAsString(out _, new IntPtr(thisPtr->Name)), cmd, a3, WindowSystem.HasAnyWindowSystemFocus);
|
||||
|
||||
// "SendHotkey"
|
||||
// 3 == Close
|
||||
if (cmd == 12 && WindowSystem.HasAnyWindowSystemFocus && *arg == 3 && this.configuration.IsFocusManagementEnabled)
|
||||
if (eventType == AtkEventType.InputReceived && WindowSystem.HasAnyWindowSystemFocus && atkEventData != null && *(int*)atkEventData == 3 && this.configuration.IsFocusManagementEnabled)
|
||||
{
|
||||
Log.Verbose($"Cancelling global event SendHotkey command due to WindowSystem {WindowSystem.FocusedWindowSystemNamespace}");
|
||||
return IntPtr.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
return this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, cmd, a3, a4, arg);
|
||||
this.hookAtkUnitBaseReceiveGlobalEvent.Original(thisPtr, eventType, eventParam, atkEvent, atkEventData);
|
||||
}
|
||||
|
||||
private void AgentHudOpenSystemMenuDetour(void* thisPtr, AtkValue* atkValueArgs, uint menuSize)
|
||||
private void AgentHudOpenSystemMenuDetour(AgentHUD* thisPtr, AtkValue* atkValueArgs, uint menuSize)
|
||||
{
|
||||
if (WindowSystem.HasAnyWindowSystemFocus && this.configuration.IsFocusManagementEnabled)
|
||||
{
|
||||
|
|
@ -213,7 +206,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
this.hookAgentHudOpenSystemMenu.Original(thisPtr, atkValueArgs, menuSize + 2);
|
||||
}
|
||||
|
||||
private void UiModuleRequestMainCommandDetour(void* thisPtr, int commandId)
|
||||
private unsafe void UiModuleExecuteMainCommandDetour(UIModule* thisPtr, uint commandId)
|
||||
{
|
||||
var dalamudInterface = Service<DalamudInterface>.GetNullable();
|
||||
|
||||
|
|
@ -226,7 +219,7 @@ internal sealed unsafe class DalamudAtkTweaks : IInternalDisposableService
|
|||
dalamudInterface?.OpenSettings();
|
||||
break;
|
||||
default:
|
||||
this.hookUiModuleRequestMainCommand.Original(thisPtr, commandId);
|
||||
this.hookUiModuleExecuteMainCommand.Original(thisPtr, commandId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Dalamud.Game.ClientState.Buddy;
|
||||
using Dalamud.Game.ClientState.Buddy;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -32,8 +32,6 @@ internal class BuddyListWidget : IDataWindowWidget
|
|||
var buddyList = Service<BuddyList>.Get();
|
||||
|
||||
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
|
||||
|
||||
ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}");
|
||||
{
|
||||
var member = buddyList.CompanionBuddy;
|
||||
if (member == null)
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ public interface IChatGui
|
|||
/// <summary>
|
||||
/// Gets the ID of the last linked item.
|
||||
/// </summary>
|
||||
public int LastLinkedItemId { get; }
|
||||
public uint LastLinkedItemId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flags of the last linked item.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue