From 5ae1d9c17870e47e7850e5c5470b7f63563364bc Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Wed, 14 Jul 2021 03:35:00 +0200 Subject: [PATCH] feat: add new-style actor table --- Dalamud/Game/ClientState/Actors/ActorTable.cs | 249 +++++++----------- Dalamud/Game/ClientState/Actors/Targets.cs | 4 +- Dalamud/Game/ClientState/ClientState.cs | 1 - 3 files changed, 96 insertions(+), 158 deletions(-) diff --git a/Dalamud/Game/ClientState/Actors/ActorTable.cs b/Dalamud/Game/ClientState/Actors/ActorTable.cs index f950418b3..610841114 100644 --- a/Dalamud/Game/ClientState/Actors/ActorTable.cs +++ b/Dalamud/Game/ClientState/Actors/ActorTable.cs @@ -1,217 +1,126 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types.NonPlayer; +using Dalamud.Game.ClientState.Structs; using JetBrains.Annotations; using Serilog; +using Actor = Dalamud.Game.ClientState.Actors.Types.Actor; + namespace Dalamud.Game.ClientState.Actors { /// - /// This collection represents the currently spawned FFXIV actors. + /// This collection represents the currently spawned FFXIV actors. /// - public sealed partial class ActorTable : IReadOnlyCollection, ICollection, IDisposable + public class ActorTable : IReadOnlyCollection, ICollection { private const int ActorTableLength = 424; - #region ReadProcessMemory Hack - private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor)); - private static readonly IntPtr ActorMem = Marshal.AllocHGlobal(ActorMemSize); - private static readonly IntPtr CurrentProcessHandle = new(-1); - #endregion - - private Dalamud dalamud; - private ClientStateAddressResolver address; - private List actorsCache; + private readonly Dalamud dalamud; /// /// Initializes a new instance of the class. - /// Set up the actor table collection. /// - /// The Dalamud instance. - /// The ClientStateAddressResolver instance. + /// The instance. + /// Client state address resolver. internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver) { - this.address = addressResolver; + this.Address = addressResolver; this.dalamud = dalamud; - dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent; - - Log.Verbose($"Actor table address 0x{this.address.ActorTable.ToInt64():X}"); + Log.Verbose("Actor table address {ActorTable}", this.Address.ActorTable); } /// /// Gets the amount of currently spawned actors. /// - public int Length => this.ActorsCache.Count; + public int Length + { + get + { + var count = 0; + for (var i = 0; i < ActorTableLength; i++) + { + var ptr = this.GetActorAddress(i); + if (ptr != IntPtr.Zero) + { + count++; + } + } - private List ActorsCache => this.actorsCache ??= this.GetActorTable(); + return count; + } + } + + /// + int IReadOnlyCollection.Count => this.Length; + + /// + int ICollection.Count => this.Length; + + /// + bool ICollection.IsSynchronized => false; + + /// + object ICollection.SyncRoot => this; + + private ClientStateAddressResolver Address { get; } /// - /// Get an actor at the specified spawn index. + /// Get an actor at the specified spawn index. /// /// Spawn index. /// at the specified spawn index. [CanBeNull] - public Actor this[int index] => this.ActorsCache[index]; - - /// - /// Read an actor struct from memory and create the appropriate type of actor. - /// - /// Offset of the actor in the actor table. - /// An instantiated actor. - internal Actor ReadActorFromMemory(IntPtr offset) + public Actor this[int index] { - try + get { - // FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted - if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _)) + var ptr = this.GetActorAddress(index); + if (ptr != IntPtr.Zero) { - Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout"); - return null; + return this.CreateActorReference(ptr); } - var actorStruct = Marshal.PtrToStructure(ActorMem); - - return actorStruct.ObjectKind switch - { - ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud), - ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud), - ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud), - ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud), - _ => new Actor(offset, actorStruct, this.dalamud), - }; - } - catch (Exception e) - { - Log.Error(e, "Could not read actor from memory."); return null; } } - private void ResetCache() => this.actorsCache = null; - - private void Framework_OnUpdateEvent(Internal.Framework framework) + /// + /// Gets the address of the actor at the specified index of the actor table. + /// + /// The index of the actor. + /// The memory address of the actor. + public unsafe IntPtr GetActorAddress(int index) { - this.ResetCache(); + if (index >= ActorTableLength) + { + return IntPtr.Zero; + } + + return *(IntPtr*)(this.Address.ActorTable + (8 * index)); } - private IntPtr[] GetPointerTable() + /// + public IEnumerator GetEnumerator() { - var ret = new IntPtr[ActorTableLength]; - Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength); - return ret; - } - - private List GetActorTable() - { - var actors = new List(); - var ptrTable = this.GetPointerTable(); for (var i = 0; i < ActorTableLength; i++) { - actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null); + yield return this[i]; } - - return actors; } - } - /// - /// Implementing IDisposable. - /// - public sealed partial class ActorTable : IDisposable - { - private bool disposed = false; - - /// - /// Finalizes an instance of the class. - /// - ~ActorTable() => this.Dispose(false); - - /// - /// Disposes of managed and unmanaged resources. - /// - public void Dispose() + /// + IEnumerator IEnumerable.GetEnumerator() { - this.Dispose(true); - GC.SuppressFinalize(this); + return this.GetEnumerator(); } - private void Dispose(bool disposing) - { - if (this.disposed) - return; - - if (disposing) - { - this.dalamud.Framework.OnUpdateEvent -= this.Framework_OnUpdateEvent; - Marshal.FreeHGlobal(ActorMem); - } - - this.disposed = true; - } - } - - /// - /// Implementing IReadOnlyCollection, IEnumerable, and Enumerable. - /// - public sealed partial class ActorTable : IReadOnlyCollection - { - /// - /// Gets the number of elements in the collection. - /// - /// The number of elements in the collection. - int IReadOnlyCollection.Count => this.Length; - - /// - /// Gets an enumerator capable of iterating through the actor table. - /// - /// An actor enumerable. - public IEnumerator GetEnumerator() => this.ActorsCache.Where(a => a != null).GetEnumerator(); - - /// - /// Gets an enumerator capable of iterating through the actor table. - /// - /// An actor enumerable. - IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); - } - - /// - /// Implementing ICollection. - /// - public sealed partial class ActorTable : ICollection - { - /// - /// Gets the number of elements in the collection. - /// - /// The number of elements in the collection. - int ICollection.Count => this.Length; - - /// - /// Gets a value indicating whether access to the collection is synchronized (thread safe). - /// - /// Whether access is synchronized (thread safe) or not. - bool ICollection.IsSynchronized => false; - - /// - /// Gets an object that can be used to synchronize access to the collection. - /// - /// An object that can be used to synchronize access to the collection. - object ICollection.SyncRoot => this; - - /// - /// Copies the elements of the collection to an array, starting at a particular index. - /// - /// - /// The one-dimensional array that is the destination of the elements copied from the collection. The array must have zero-based indexing. - /// - /// - /// The zero-based index in array at which copying begins. - /// + /// void ICollection.CopyTo(Array array, int index) { for (var i = 0; i < this.Length; i++) @@ -220,5 +129,33 @@ namespace Dalamud.Game.ClientState.Actors index++; } } + + /// + /// Create a reference to a FFXIV actor. + /// + /// The offset of the actor in memory. + /// object or inheritor containing requested data. + [CanBeNull] + internal unsafe Actor CreateActorReference(IntPtr offset) + { + if (this.dalamud.ClientState.LocalContentId == 0) + { + return null; + } + + var objKind = *(ObjectKind*)(offset + ActorOffsets.ObjectKind); + + // TODO: This is for compatibility with legacy actor classes - superseded once ready + var actorStruct = Marshal.PtrToStructure(offset); + + return objKind switch + { + ObjectKind.Player => new PlayerCharacter(offset, actorStruct, this.dalamud), + ObjectKind.BattleNpc => new BattleNpc(offset, actorStruct, this.dalamud), + ObjectKind.EventObj => new EventObj(offset, actorStruct, this.dalamud), + ObjectKind.Companion => new Npc(offset, actorStruct, this.dalamud), + _ => new Actor(offset, actorStruct, this.dalamud), + }; + } } } diff --git a/Dalamud/Game/ClientState/Actors/Targets.cs b/Dalamud/Game/ClientState/Actors/Targets.cs index c9e8a5129..49ec02095 100644 --- a/Dalamud/Game/ClientState/Actors/Targets.cs +++ b/Dalamud/Game/ClientState/Actors/Targets.cs @@ -2,6 +2,7 @@ using System; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Actors.Types; +using JetBrains.Annotations; namespace Dalamud.Game.ClientState.Actors { @@ -91,6 +92,7 @@ namespace Dalamud.Game.ClientState.Actors Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress); } + [CanBeNull] private Actor GetActorByOffset(int offset) { if (this.address.TargetManager == IntPtr.Zero) @@ -100,7 +102,7 @@ namespace Dalamud.Game.ClientState.Actors if (actorAddress == IntPtr.Zero) return null; - return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress); + return this.dalamud.ClientState.Actors.CreateActorReference(actorAddress); } } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index bf5ed523f..d593786d3 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -181,7 +181,6 @@ namespace Dalamud.Game.ClientState { this.PartyList.Dispose(); this.setupTerritoryTypeHook.Dispose(); - this.Actors.Dispose(); this.GamepadState.Dispose(); this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;