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;