diff --git a/Dalamud/Game/ClientState/Actors/ActorTable.cs b/Dalamud/Game/ClientState/Actors/ActorTable.cs index e6e02068d..2f5a2224e 100644 --- a/Dalamud/Game/ClientState/Actors/ActorTable.cs +++ b/Dalamud/Game/ClientState/Actors/ActorTable.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.InteropServices; using Dalamud.Game.ClientState.Actors.Types; using Dalamud.Game.ClientState.Actors.Types.NonPlayer; @@ -13,10 +14,23 @@ namespace Dalamud.Game.ClientState.Actors { /// /// This collection represents the currently spawned FFXIV actors. /// - public class ActorTable : IReadOnlyCollection, ICollection { + public class ActorTable : IReadOnlyCollection, ICollection, IDisposable { private const int ActorTableLength = 424; + #region Actor Table Cache + private List actorsCache; + private List ActorsCache { + get { + if (actorsCache != null) return actorsCache; + actorsCache = GetActorTable(); + return actorsCache; + } + } + internal void ResetCache() => actorsCache = null; + #endregion + + #region temporary imports for crash workaround [DllImport("kernel32.dll", SetLastError = true)] @@ -32,8 +46,8 @@ namespace Dalamud.Game.ClientState.Actors { private ClientStateAddressResolver Address { get; } private Dalamud dalamud; - private static int sz = Marshal.SizeOf(typeof(Structs.Actor)); - private IntPtr actorMem = Marshal.AllocHGlobal(sz); + private static int actorMemSize = Marshal.SizeOf(typeof(Structs.Actor)); + private IntPtr actorMem { get; set; } = Marshal.AllocHGlobal(actorMemSize); /// /// Set up the actor table collection. @@ -43,9 +57,15 @@ namespace Dalamud.Game.ClientState.Actors { Address = addressResolver; this.dalamud = dalamud; + dalamud.Framework.OnUpdateEvent += Framework_OnUpdateEvent; + Log.Verbose("Actor table address {ActorTable}", Address.ActorTable); } + private void Framework_OnUpdateEvent(Internal.Framework framework) { + this.ResetCache(); + } + /// /// Get an actor at the specified spawn index. /// @@ -53,47 +73,47 @@ namespace Dalamud.Game.ClientState.Actors { /// at the specified spawn index. [CanBeNull] public Actor this[int index] { - get { - if (index >= Length) - return null; + get => ActorsCache[index]; + } - var tblIndex = Address.ActorTable + index * 8; + private Actor ReadActorFromMemory(IntPtr offset) + { + try { + var actorStruct = Marshal.PtrToStructure(offset); - var offset = Marshal.ReadIntPtr(tblIndex); - - //Log.Debug($"Reading actor {index} at {tblIndex.ToInt64():X} pointing to {offset.ToInt64():X}"); - - if (offset == IntPtr.Zero) - return null; - - // FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted - //var sz = Marshal.SizeOf(typeof(Structs.Actor)); - //var actorMem = Marshal.AllocHGlobal(sz); // we arguably could just reuse this - if (!ReadProcessMemory(Process.GetCurrentProcess().Handle, offset, actorMem, sz, out _)) { - Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout"); - return null; - } - - var actorStruct = Marshal.PtrToStructure(actorMem); - //Marshal.FreeHGlobal(actorMem); - - //Log.Debug("ActorTable[{0}]: {1} - {2} - {3}", index, tblIndex.ToString("X"), offset.ToString("X"), - // actorStruct.ObjectKind.ToString()); - switch (actorStruct.ObjectKind) { case ObjectKind.Player: return new PlayerCharacter(offset, actorStruct, this.dalamud); case ObjectKind.BattleNpc: return new BattleNpc(offset, actorStruct, this.dalamud); default: return new Actor(offset, actorStruct, this.dalamud); } } + catch (Exception e) { + Log.Information($"{e}"); + return null; + } + } + + private IntPtr[] GetPointerTable() { + IntPtr[] ret = new IntPtr[ActorTableLength]; + Marshal.Copy(Address.ActorTable, ret, 0, ActorTableLength); + return ret; + } + + private List GetActorTable() { + var actors = new List(); + var ptrTable = GetPointerTable(); + for (int i = 0; i < ActorTableLength; i++) { + if (ptrTable[i] != IntPtr.Zero) { + actors.Add(ReadActorFromMemory(ptrTable[i])); + } else { + actors.Add(null); + } + } + return actors; } public IEnumerator GetEnumerator() { - for (int i=0;i a != null).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { @@ -103,7 +123,7 @@ namespace Dalamud.Game.ClientState.Actors { /// /// The amount of currently spawned actors. /// - public int Length => ActorTableLength; + public int Length => ActorsCache.Count; int IReadOnlyCollection.Count => Length; @@ -119,5 +139,26 @@ namespace Dalamud.Game.ClientState.Actors { index++; } } + + #region IDisposable Pattern + private bool disposed = false; + + private void Dispose(bool disposing) + { + if (disposed) return; + this.dalamud.Framework.OnUpdateEvent -= Framework_OnUpdateEvent; + Marshal.FreeHGlobal(actorMem); + disposed = true; + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + ~ActorTable() { + Dispose(false); + } + #endregion } } diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs index b37dc2651..2275345a5 100644 --- a/Dalamud/Game/ClientState/ClientState.cs +++ b/Dalamud/Game/ClientState/ClientState.cs @@ -138,6 +138,7 @@ namespace Dalamud.Game.ClientState public void Dispose() { this.PartyList.Dispose(); this.setupTerritoryTypeHook.Dispose(); + this.Actors.Dispose(); } private void FrameworkOnOnUpdateEvent(Framework framework) {