From c0f05614c67d7755a751d6ec082be9614bc589e0 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Tue, 12 Nov 2024 17:20:29 +0100 Subject: [PATCH] [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 --- Dalamud/Game/ChatHandlers.cs | 91 +------- Dalamud/Game/ClientState/Buddy/BuddyList.cs | 18 +- Dalamud/Game/ClientState/ClientState.cs | 5 +- .../ClientState/ClientStateAddressResolver.cs | 44 ---- Dalamud/Game/ClientState/Fates/FateTable.cs | 44 +--- .../Game/ClientState/JobGauge/JobGauges.cs | 12 +- .../Game/ClientState/Objects/ObjectTable.cs | 65 +++--- .../ClientState/Objects/Types/Character.cs | 2 +- .../ClientState/Objects/Types/GameObject.cs | 2 +- Dalamud/Game/ClientState/Party/PartyList.cs | 10 +- Dalamud/Game/ClientState/Party/PartyMember.cs | 2 +- Dalamud/Game/Framework.cs | 36 +-- Dalamud/Game/FrameworkAddressResolver.cs | 40 ---- Dalamud/Game/Gui/ChatGui.cs | 182 +++++++-------- Dalamud/Game/Gui/ChatGuiAddressResolver.cs | 28 --- Dalamud/Game/Gui/FlyText/FlyTextGui.cs | 104 ++++----- Dalamud/Game/Gui/GameGui.cs | 147 +++---------- Dalamud/Game/Gui/GameGuiAddressResolver.cs | 18 -- Dalamud/Game/Gui/NamePlate/NamePlateGui.cs | 27 +-- .../Gui/NamePlate/NamePlateUpdateContext.cs | 4 +- Dalamud/Game/Gui/Toast/ToastGui.cs | 208 ++++++------------ .../Game/Gui/Toast/ToastGuiAddressResolver.cs | 30 --- Dalamud/Game/Internal/DalamudAtkTweaks.cs | 45 ++-- .../Windows/Data/Widgets/BuddyListWidget.cs | 4 +- Dalamud/Plugin/Services/IChatGui.cs | 2 +- 25 files changed, 343 insertions(+), 827 deletions(-) delete mode 100644 Dalamud/Game/FrameworkAddressResolver.cs delete mode 100644 Dalamud/Game/Gui/ChatGuiAddressResolver.cs delete mode 100644 Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs diff --git a/Dalamud/Game/ChatHandlers.cs b/Dalamud/Game/ChatHandlers.cs index a41c6ff7a..c40744ca4 100644 --- a/Dalamud/Game/ChatHandlers.cs +++ b/Dalamud/Game/ChatHandlers.cs @@ -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. /// [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 retainerSaleRegexes = new() - { - { - ClientLanguage.Japanese, - new Regex[] - { - new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)×(?[\d,.]+)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - new Regex(@"^(?:.+)マーケットに(?[\d,.]+)ギルで出品した(?.*)が売れ、(?[\d,.]+)ギルを入手しました。$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.English, - new Regex[] - { - new Regex(@"^(?.+) you put up for sale in the (?:.+) markets (?:have|has) sold for (?[\d,.]+) gil \(after fees\)\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.German, - new Regex[] - { - new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) für (?[\d,.]+) Gil verkauft\.$", RegexOptions.Compiled), - new Regex(@"^Dein Gehilfe hat (?.+) auf dem Markt von (?:.+) verkauft und (?[\d,.]+) Gil erhalten\.$", RegexOptions.Compiled), - } - }, - { - ClientLanguage.French, - new Regex[] - { - new Regex(@"^Un servant a vendu (?.+) pour (?[\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.Get(); - [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -92,6 +45,9 @@ internal class ChatHandlers : IServiceType /// 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; } diff --git a/Dalamud/Game/ClientState/Buddy/BuddyList.cs b/Dalamud/Game/ClientState/Buddy/BuddyList.cs index db9b8e562..84cfd24a3 100644 --- a/Dalamud/Game/ClientState/Buddy/BuddyList.cs +++ b/Dalamud/Game/ClientState/Buddy/BuddyList.cs @@ -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.Get(); - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] private BuddyList() { - this.address = this.clientState.AddressResolver; - - Log.Verbose($"Buddy list address {Util.DescribeAddress(this.address.BuddyList)}"); } /// @@ -76,14 +69,7 @@ internal sealed partial class BuddyList : IServiceType, IBuddyList } } - /// - /// Gets the address of the buddy list. - /// - internal IntPtr BuddyListAddress => this.address.BuddyList; - - private static int BuddyMemberSize { get; } = Marshal.SizeOf(); - - 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; /// public IBuddyMember? this[int index] diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index 51f25fb5c..750ba34c5 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -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.GetNullable()?[0] as IPlayerCharacter; /// - public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId); + public unsafe ulong LocalContentId => PlayerState.Instance()->ContentId; /// public bool IsLoggedIn { get; private set; } diff --git a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs index 6b46ffc0d..cc635f784 100644 --- a/Dalamud/Game/ClientState/ClientStateAddressResolver.cs +++ b/Dalamud/Game/ClientState/ClientStateAddressResolver.cs @@ -7,39 +7,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver { // Static offsets - /// - /// Gets the address of the actor table. - /// - public IntPtr ObjectTable { get; private set; } - - /// - /// Gets the address of the buddy list. - /// - public IntPtr BuddyList { get; private set; } - - /// - /// Gets the address of a pointer to the fate table. - /// - /// - /// This is a static address to a pointer, not the address of the table itself. - /// - public IntPtr FateTablePtr { get; private set; } - - /// - /// Gets the address of the Group Manager. - /// - public IntPtr GroupManager { get; private set; } - - /// - /// Gets the address of the local content id. - /// - public IntPtr LocalContentId { get; private set; } - - /// - /// Gets the address of job gauge data. - /// - public IntPtr JobGaugeData { get; private set; } - /// /// Gets the address of the keyboard state. /// @@ -74,17 +41,6 @@ internal sealed class ClientStateAddressResolver : BaseAddressResolver /// The signature scanner to facilitate setup. 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"); diff --git a/Dalamud/Game/ClientState/Fates/FateTable.cs b/Dalamud/Game/ClientState/Fates/FateTable.cs index abd5bac41..1bf557ad5 100644 --- a/Dalamud/Game/ClientState/Fates/FateTable.cs +++ b/Dalamud/Game/ClientState/Fates/FateTable.cs @@ -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)}"); } /// - public IntPtr Address => this.address.FateTablePtr; + public unsafe IntPtr Address => (nint)CSFateManager.Instance(); /// 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; } } - /// - /// Gets the address of the Fate table. - /// - 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; - /// 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; } /// diff --git a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs index e0ae986c5..5a734ed87 100644 --- a/Dalamud/Game/ClientState/JobGauge/JobGauges.cs +++ b/Dalamud/Game/ClientState/JobGauge/JobGauges.cs @@ -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 cache = new(); + private Dictionary cache = []; [ServiceManager.ServiceConstructor] - private JobGauges(ClientState clientState) + private JobGauges() { - this.Address = clientState.AddressResolver.JobGaugeData; - - Log.Verbose($"JobGaugeData address {Util.DescribeAddress(this.Address)}"); } /// - public IntPtr Address { get; } + public unsafe IntPtr Address => (nint)(&CSJobGaugeManager.Instance()->CurrentGauge); /// public T Get() where T : JobGaugeBase diff --git a/Dalamud/Game/ClientState/Objects/ObjectTable.cs b/Dalamud/Game/ClientState/Objects/ObjectTable.cs index c0b9c6e12..8ea1b582f 100644 --- a/Dalamud/Game/ClientState/Objects/ObjectTable.cs +++ b/Dalamud/Game/ClientState/Objects/ObjectTable.cs @@ -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 multiThreadedEnumerators = new DefaultObjectPoolProvider().Create(); @@ -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)}"); } /// - public nint Address + public unsafe nint Address { get { _ = this.WarnMultithreadedUsage(); - return this.clientState.AddressResolver.ObjectTable; + return (nint)(&CSGameObjectManager.Instance()->Objects); } } /// - public int Length => ObjectTableLength; + public int Length => objectTableLength; /// 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; } /// @@ -172,33 +177,21 @@ internal sealed partial class ObjectTable : IServiceType, IObjectTable } /// Stores an object table entry, with preallocated concrete types. - internal readonly unsafe struct CachedEntry + /// Initializes a new instance of the struct. + /// A pointer to the object table entry this entry should be pointing to. + internal readonly unsafe struct CachedEntry(Pointer* 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; - - /// Initializes a new instance of the struct. - /// The object table that this entry should be pointing to. - /// The slot index inside the table. - 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); /// Gets the address of the underlying native object. May be null. public CSGameObject* Address { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => *this.gameObjectPtrPtr; + get => gameObjectPtr->Value; } /// Updates and gets the wrapped game object pointed by this struct. @@ -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) { diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index b04b54301..a91ecc230 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -161,7 +161,7 @@ internal unsafe class Character : GameObject, ICharacter public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray(); /// - public SeString CompanyTag => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->FreeCompanyTag[0]), 6); + public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag); /// /// Gets the target object ID of the character. diff --git a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs index f9fd87bf4..4209100f0 100644 --- a/Dalamud/Game/ClientState/Objects/Types/GameObject.cs +++ b/Dalamud/Game/ClientState/Objects/Types/GameObject.cs @@ -197,7 +197,7 @@ internal partial class GameObject internal unsafe partial class GameObject : IGameObject { /// - public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref this.Struct->Name[0]), 64); + public SeString Name => SeString.Parse(this.Struct->Name); /// public ulong GameObjectId => this.Struct->GetGameObjectId(); diff --git a/Dalamud/Game/ClientState/Party/PartyList.cs b/Dalamud/Game/ClientState/Party/PartyList.cs index 4fbd8fdfb..a016a8211 100644 --- a/Dalamud/Game/ClientState/Party/PartyList.cs +++ b/Dalamud/Game/ClientState/Party/PartyList.cs @@ -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.Get(); - private readonly ClientStateAddressResolver address; - [ServiceManager.ServiceConstructor] private PartyList() { - this.address = this.clientState.AddressResolver; - - Log.Verbose($"Group manager address {Util.DescribeAddress(this.address.GroupManager)}"); } /// @@ -48,7 +42,7 @@ internal sealed unsafe partial class PartyList : IServiceType, IPartyList public bool IsAlliance => this.GroupManagerStruct->MainGroup.AllianceFlags > 0; /// - public IntPtr GroupManagerAddress => this.address.GroupManager; + public unsafe IntPtr GroupManagerAddress => (nint)CSGroupManager.Instance(); /// public IntPtr GroupListAddress => (IntPtr)Unsafe.AsPointer(ref GroupManagerStruct->MainGroup.PartyMembers[0]); diff --git a/Dalamud/Game/ClientState/Party/PartyMember.cs b/Dalamud/Game/ClientState/Party/PartyMember.cs index b764431f5..cf620a7ef 100644 --- a/Dalamud/Game/ClientState/Party/PartyMember.cs +++ b/Dalamud/Game/ClientState/Party/PartyMember.cs @@ -181,7 +181,7 @@ internal unsafe class PartyMember : IPartyMember /// /// Gets the displayname of this party member. /// - public SeString Name => MemoryHelper.ReadSeString((nint)Unsafe.AsPointer(ref Struct->Name[0]), 0x40); + public SeString Name => SeString.Parse(this.Struct->Name); /// /// Gets the sex of this party member. diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs index 07942f780..82c7f5f6c 100644 --- a/Dalamud/Game/Framework.cs +++ b/Dalamud/Game/Framework.cs @@ -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; /// @@ -31,11 +32,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework private readonly Stopwatch updateStopwatch = new(); private readonly HitchDetector hitchDetector; - private readonly Hook updateHook; - private readonly Hook destroyHook; + private readonly Hook updateHook; + private readonly Hook destroyHook; - private readonly FrameworkAddressResolver addressResolver; - [ServiceManager.ServiceDependency] private readonly GameLifecycle lifecycle = Service.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.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate); - this.destroyHook = Hook.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy); + this.updateHook = Hook.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Tick, this.HandleFrameworkUpdate); + this.destroyHook = Hook.FromAddress((nint)CSFramework.StaticVirtualTablePointer->Destroy, this.HandleFrameworkDestroy); this.updateHook.Enable(); this.destroyHook.Enable(); } - /// - /// A delegate type used during the native Framework::destroy. - /// - /// The native Framework address. - /// A value indicating if the call was successful. - public delegate bool OnRealDestroyDelegate(IntPtr framework); - - [UnmanagedFunctionPointer(CallingConvention.ThisCall)] - private delegate bool OnUpdateDetour(IntPtr framework); - /// 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); } } diff --git a/Dalamud/Game/FrameworkAddressResolver.cs b/Dalamud/Game/FrameworkAddressResolver.cs deleted file mode 100644 index 8ea371f2c..000000000 --- a/Dalamud/Game/FrameworkAddressResolver.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace Dalamud.Game; - -/// -/// The address resolver for the class. -/// -internal sealed class FrameworkAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address for the function that is called once the Framework is destroyed. - /// - public IntPtr DestroyAddress { get; private set; } - - /// - /// Gets the address for the function that is called once the Framework is free'd. - /// - public IntPtr FreeAddress { get; private set; } - - /// - /// Gets the function that is called every tick. - /// - public IntPtr TickAddress { get; private set; } - - /// - 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 ??"); - } -} diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs index 6779f32a8..2482c36b4 100644 --- a/Dalamud/Game/Gui/ChatGui.cs +++ b/Dalamud/Game/Gui/ChatGui.cs @@ -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 chatQueue = new(); private readonly Dictionary<(string PluginName, uint CommandId), Action> dalamudLinkHandlers = new(); private readonly Hook printMessageHook; - private readonly Hook populateItemLinkHook; - private readonly Hook interactableLinkClickedHook; + private readonly Hook inventoryItemCopyHook; + private readonly Hook handleLinkClickHook; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.Get(); @@ -42,29 +48,20 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui private ImmutableDictionary<(string PluginName, uint CommandId), Action>? dalamudLinkHandlersCopy; [ServiceManager.ServiceConstructor] - private ChatGui(TargetSigScanner sigScanner) + private ChatGui() { - this.address = new ChatGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.printMessageHook = Hook.FromAddress((nint)RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); - this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour); - this.interactableLinkClickedHook = Hook.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour); + this.printMessageHook = Hook.FromAddress(RaptureLogModule.Addresses.PrintMessage.Value, this.HandlePrintMessageDetour); + this.inventoryItemCopyHook = Hook.FromAddress(InventoryItem.Addresses.Copy.Value, this.InventoryItemCopyDetour); + this.handleLinkClickHook = Hook.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); - /// public event IChatGui.OnMessageDelegate? ChatMessage; @@ -78,7 +75,7 @@ internal sealed unsafe class ChatGui : IInternalDisposableService, IChatGui public event IChatGui.OnMessageUnhandledDelegate? ChatMessageUnhandled; /// - public int LastLinkedItemId { get; private set; } + public uint LastLinkedItemId { get; private set; } /// 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(); } /// @@ -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()) { 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()) + { + 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; /// - public int LastLinkedItemId => this.chatGuiService.LastLinkedItemId; + public uint LastLinkedItemId => this.chatGuiService.LastLinkedItemId; /// public byte LastLinkedItemFlags => this.chatGuiService.LastLinkedItemFlags; diff --git a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs b/Dalamud/Game/Gui/ChatGuiAddressResolver.cs deleted file mode 100644 index 366e79fc6..000000000 --- a/Dalamud/Game/Gui/ChatGuiAddressResolver.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Dalamud.Game.Gui; - -/// -/// The address resolver for the class. -/// -internal sealed class ChatGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native PopulateItemLinkObject method. - /// - public IntPtr PopulateItemLinkObject { get; private set; } - - /// - /// Gets the address of the native InteractableLinkClicked method. - /// - public IntPtr InteractableLinkClicked { get; private set; } - - /// - 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"); - } -} diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs index 623bc51b3..b01f8c244 100644 --- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs +++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs @@ -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 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 /// /// Private delegate for the native CreateFlyText function's hook. /// - 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); /// /// Private delegate for the native AddFlyText function pointer. /// - 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.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."); diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs index 80463a119..74e0a8df3 100644 --- a/Dalamud/Game/Gui/GameGui.cs +++ b/Dalamud/Game/Gui/GameGui.cs @@ -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.Get(); + private readonly GameGuiAddressResolver address; private readonly Hook setGlobalBgmHook; - private readonly Hook handleItemHoverHook; - private readonly Hook handleItemOutHook; private readonly Hook handleActionHoverHook; private readonly Hook handleActionOutHook; private readonly Hook handleImmHook; - private readonly Hook toggleUiHideHook; + private readonly Hook setUiVisibilityHook; private readonly Hook 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.FromAddress(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour); - this.handleItemHoverHook = Hook.FromAddress(this.address.HandleItemHover, this.HandleItemHoverDetour); - this.handleItemOutHook = Hook.FromAddress(this.address.HandleItemOut, this.HandleItemOutDetour); - this.handleActionHoverHook = Hook.FromAddress(this.address.HandleActionHover, this.HandleActionHoverDetour); this.handleActionOutHook = Hook.FromAddress(this.address.HandleActionOut, this.HandleActionOutDetour); this.handleImmHook = Hook.FromAddress(this.address.HandleImm, this.HandleImmDetour); - this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour); + this.setUiVisibilityHook = Hook.FromAddress((nint)RaptureAtkModule.StaticVirtualTablePointer->SetUiVisibility, this.SetUiVisibilityDetour); this.utf8StringFromSequenceHook = Hook.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); /// public event EventHandler? UiHideToggled; @@ -137,33 +113,7 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// 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(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(uiMapObjectPtr, 0, 63); - - var mapLinkString = mapLink.DataString; - - Log.Debug($"OpenMapWithMapLink: Opening Map Link: {mapLinkString}"); - - return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); - } + => RaptureAtkModule.Instance()->OpenMapWithMapLink(mapLink.DataString); /// public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos) @@ -311,11 +261,11 @@ internal sealed unsafe class GameGui : IInternalDisposableService, IGameGui /// 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); + } + } + } } /// diff --git a/Dalamud/Game/Gui/GameGuiAddressResolver.cs b/Dalamud/Game/Gui/GameGuiAddressResolver.cs index 5b02a2d09..a742541ea 100644 --- a/Dalamud/Game/Gui/GameGuiAddressResolver.cs +++ b/Dalamud/Game/Gui/GameGuiAddressResolver.cs @@ -15,16 +15,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// public IntPtr SetGlobalBgm { get; private set; } - /// - /// Gets the address of the native HandleItemHover method. - /// - public IntPtr HandleItemHover { get; private set; } - - /// - /// Gets the address of the native HandleItemOut method. - /// - public IntPtr HandleItemOut { get; private set; } - /// /// Gets the address of the native HandleActionHover method. /// @@ -40,11 +30,6 @@ internal sealed class GameGuiAddressResolver : BaseAddressResolver /// public IntPtr HandleImm { get; private set; } - /// - /// Gets the address of the native ToggleUiHide method. - /// - public IntPtr ToggleUiHide { get; private set; } - /// /// Gets the address of the native Utf8StringFromSequence method. /// @@ -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"); } } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs index 2def0ea00..32192ad21 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateGui.cs @@ -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 { - /// - /// The index for the number array used by the NamePlate addon. - /// - public const int NumberArrayIndex = 5; - - /// - /// The index for the string array used by the NamePlate addon. - /// - public const int StringArrayIndex = 4; - /// /// The index for of the FullUpdate entry in the NamePlate number array. /// @@ -81,18 +71,11 @@ internal sealed class NamePlateGui : IInternalDisposableService, INamePlateGui /// 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); } } diff --git a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs index 876c4c2e0..fef3f9a86 100644 --- a/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs +++ b/Dalamud/Game/Gui/NamePlate/NamePlateUpdateContext.cs @@ -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; diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs index 0895d1b6b..6b55b3408 100644 --- a/Dalamud/Game/Gui/Toast/ToastGui.cs +++ b/Dalamud/Game/Gui/Toast/ToastGui.cs @@ -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; /// @@ -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 errorQueue = new(); @@ -30,16 +31,12 @@ internal sealed partial class ToastGui : IInternalDisposableService, IToastGui /// /// Initializes a new instance of the class. /// - /// Sig scanner to use. [ServiceManager.ServiceConstructor] - private ToastGui(TargetSigScanner sigScanner) + private unsafe ToastGui() { - this.address = new ToastGuiAddressResolver(); - this.address.Setup(sigScanner); - - this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour); - this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour); - this.showErrorToastHook = Hook.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour); + this.showNormalToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowWideText, this.HandleNormalToastDetour); + this.showQuestToastHook = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ShowText, this.HandleQuestToastDetour); + this.showErrorToastHook = Hook.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 - + /// 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(); - unsafe - { - var ptr = (byte*)text; - while (*ptr != 0) - { - bytes.Add(*ptr); - ptr += 1; - } - } - - // call events - return SeString.Parse(bytes.ToArray()); - } } /// @@ -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.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.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.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); } } } diff --git a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs b/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs deleted file mode 100644 index 0a8775540..000000000 --- a/Dalamud/Game/Gui/Toast/ToastGuiAddressResolver.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Dalamud.Game.Gui.Toast; - -/// -/// An address resolver for the class. -/// -internal class ToastGuiAddressResolver : BaseAddressResolver -{ - /// - /// Gets the address of the native ShowNormalToast method. - /// - public IntPtr ShowNormalToast { get; private set; } - - /// - /// Gets the address of the native ShowQuestToast method. - /// - public IntPtr ShowQuestToast { get; private set; } - - /// - /// Gets the address of the ShowErrorToast method. - /// - public IntPtr ShowErrorToast { get; private set; } - - /// - 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"); - } -} diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs index 39ff7c241..c147f76e6 100644 --- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs +++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs @@ -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 hookAgentHudOpenSystemMenu; // TODO: Make this into events in Framework.Gui - private readonly Hook hookUiModuleRequestMainCommand; + private readonly Hook hookUiModuleExecuteMainCommand; - private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; + private readonly Hook hookAtkUnitBaseReceiveGlobalEvent; [ServiceManager.ServiceDependency] private readonly DalamudConfiguration configuration = Service.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.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.FromAddress(uiModuleRequestMainCommandAddress, this.UiModuleRequestMainCommandDetour); - - var atkUnitBaseReceiveGlobalEventAddress = sigScanner.ScanText("48 89 5C 24 ?? 48 89 7C 24 ?? 55 41 54 41 57"); - this.hookAtkUnitBaseReceiveGlobalEvent = Hook.FromAddress(atkUnitBaseReceiveGlobalEventAddress, this.AtkUnitBaseReceiveGlobalEventDetour); + this.hookUiModuleExecuteMainCommand = Hook.FromAddress((nint)UIModule.StaticVirtualTablePointer->ExecuteMainCommand, this.UiModuleExecuteMainCommandDetour); + this.hookAtkUnitBaseReceiveGlobalEvent = Hook.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(); } /// Finalizes an instance of the class. ~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); /// 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.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; } } diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs index c35280f92..961d3c3c0 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/BuddyListWidget.cs @@ -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.Get(); ImGui.Checkbox("Resolve GameData", ref this.resolveGameData); - - ImGui.Text($"BuddyList: {buddyList.BuddyListAddress.ToInt64():X}"); { var member = buddyList.CompanionBuddy; if (member == null) diff --git a/Dalamud/Plugin/Services/IChatGui.cs b/Dalamud/Plugin/Services/IChatGui.cs index 09f485ac2..42bbd6b06 100644 --- a/Dalamud/Plugin/Services/IChatGui.cs +++ b/Dalamud/Plugin/Services/IChatGui.cs @@ -72,7 +72,7 @@ public interface IChatGui /// /// Gets the ID of the last linked item. /// - public int LastLinkedItemId { get; } + public uint LastLinkedItemId { get; } /// /// Gets the flags of the last linked item.