mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-13 12:14:16 +01:00
feat: add new-style actor table
This commit is contained in:
parent
da579f7cd6
commit
5ae1d9c178
3 changed files with 96 additions and 158 deletions
|
|
@ -1,217 +1,126 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
||||||
|
using Dalamud.Game.ClientState.Structs;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
using Actor = Dalamud.Game.ClientState.Actors.Types.Actor;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the currently spawned FFXIV actors.
|
/// This collection represents the currently spawned FFXIV actors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable
|
public class ActorTable : IReadOnlyCollection<Actor>, ICollection
|
||||||
{
|
{
|
||||||
private const int ActorTableLength = 424;
|
private const int ActorTableLength = 424;
|
||||||
|
|
||||||
#region ReadProcessMemory Hack
|
private readonly Dalamud dalamud;
|
||||||
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<Actor> actorsCache;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ActorTable"/> class.
|
/// Initializes a new instance of the <see cref="ActorTable"/> class.
|
||||||
/// Set up the actor table collection.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dalamud">The Dalamud instance.</param>
|
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
|
||||||
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
|
/// <param name="addressResolver">Client state address resolver.</param>
|
||||||
internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
internal ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
|
||||||
{
|
{
|
||||||
this.address = addressResolver;
|
this.Address = addressResolver;
|
||||||
this.dalamud = dalamud;
|
this.dalamud = dalamud;
|
||||||
|
|
||||||
dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent;
|
Log.Verbose("Actor table address {ActorTable}", this.Address.ActorTable);
|
||||||
|
|
||||||
Log.Verbose($"Actor table address 0x{this.address.ActorTable.ToInt64():X}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the amount of currently spawned actors.
|
/// Gets the amount of currently spawned actors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
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<Actor> ActorsCache => this.actorsCache ??= this.GetActorTable();
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int IReadOnlyCollection<Actor>.Count => this.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
int ICollection.Count => this.Length;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
bool ICollection.IsSynchronized => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
object ICollection.SyncRoot => this;
|
||||||
|
|
||||||
|
private ClientStateAddressResolver Address { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get an actor at the specified spawn index.
|
/// Get an actor at the specified spawn index.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="index">Spawn index.</param>
|
/// <param name="index">Spawn index.</param>
|
||||||
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public Actor this[int index] => this.ActorsCache[index];
|
public Actor this[int index]
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Read an actor struct from memory and create the appropriate <see cref="ObjectKind"/> type of actor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="offset">Offset of the actor in the actor table.</param>
|
|
||||||
/// <returns>An instantiated actor.</returns>
|
|
||||||
internal Actor ReadActorFromMemory(IntPtr offset)
|
|
||||||
{
|
{
|
||||||
try
|
get
|
||||||
{
|
{
|
||||||
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
|
var ptr = this.GetActorAddress(index);
|
||||||
if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _))
|
if (ptr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
|
return this.CreateActorReference(ptr);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(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;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ResetCache() => this.actorsCache = null;
|
/// <summary>
|
||||||
|
/// Gets the address of the actor at the specified index of the actor table.
|
||||||
private void Framework_OnUpdateEvent(Internal.Framework framework)
|
/// </summary>
|
||||||
|
/// <param name="index">The index of the actor.</param>
|
||||||
|
/// <returns>The memory address of the actor.</returns>
|
||||||
|
public unsafe IntPtr GetActorAddress(int index)
|
||||||
{
|
{
|
||||||
this.ResetCache();
|
if (index >= ActorTableLength)
|
||||||
|
{
|
||||||
|
return IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *(IntPtr*)(this.Address.ActorTable + (8 * index));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr[] GetPointerTable()
|
/// <inheritdoc/>
|
||||||
|
public IEnumerator<Actor> GetEnumerator()
|
||||||
{
|
{
|
||||||
var ret = new IntPtr[ActorTableLength];
|
|
||||||
Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Actor> GetActorTable()
|
|
||||||
{
|
|
||||||
var actors = new List<Actor>();
|
|
||||||
var ptrTable = this.GetPointerTable();
|
|
||||||
for (var i = 0; i < ActorTableLength; i++)
|
for (var i = 0; i < ActorTableLength; i++)
|
||||||
{
|
{
|
||||||
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
|
yield return this[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return actors;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Implementing IDisposable.
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
/// </summary>
|
|
||||||
public sealed partial class ActorTable : IDisposable
|
|
||||||
{
|
|
||||||
private bool disposed = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Finalizes an instance of the <see cref="ActorTable"/> class.
|
|
||||||
/// </summary>
|
|
||||||
~ActorTable() => this.Dispose(false);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Disposes of managed and unmanaged resources.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
{
|
||||||
this.Dispose(true);
|
return this.GetEnumerator();
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Dispose(bool disposing)
|
/// <inheritdoc/>
|
||||||
{
|
|
||||||
if (this.disposed)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
this.dalamud.Framework.OnUpdateEvent -= this.Framework_OnUpdateEvent;
|
|
||||||
Marshal.FreeHGlobal(ActorMem);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implementing IReadOnlyCollection, IEnumerable, and Enumerable.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class ActorTable : IReadOnlyCollection<Actor>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the number of elements in the collection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The number of elements in the collection.</returns>
|
|
||||||
int IReadOnlyCollection<Actor>.Count => this.Length;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an enumerator capable of iterating through the actor table.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An actor enumerable.</returns>
|
|
||||||
public IEnumerator<Actor> GetEnumerator() => this.ActorsCache.Where(a => a != null).GetEnumerator();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an enumerator capable of iterating through the actor table.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An actor enumerable.</returns>
|
|
||||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Implementing ICollection.
|
|
||||||
/// </summary>
|
|
||||||
public sealed partial class ActorTable : ICollection
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the number of elements in the collection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The number of elements in the collection.</returns>
|
|
||||||
int ICollection.Count => this.Length;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether access to the collection is synchronized (thread safe).
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Whether access is synchronized (thread safe) or not.</returns>
|
|
||||||
bool ICollection.IsSynchronized => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets an object that can be used to synchronize access to the collection.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>An object that can be used to synchronize access to the collection.</returns>
|
|
||||||
object ICollection.SyncRoot => this;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Copies the elements of the collection to an array, starting at a particular index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="array">
|
|
||||||
/// The one-dimensional array that is the destination of the elements copied from the collection. The array must have zero-based indexing.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="index">
|
|
||||||
/// The zero-based index in array at which copying begins.
|
|
||||||
/// </param>
|
|
||||||
void ICollection.CopyTo(Array array, int index)
|
void ICollection.CopyTo(Array array, int index)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.Length; i++)
|
for (var i = 0; i < this.Length; i++)
|
||||||
|
|
@ -220,5 +129,33 @@ namespace Dalamud.Game.ClientState.Actors
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a reference to a FFXIV actor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset of the actor in memory.</param>
|
||||||
|
/// <returns><see cref="Actor"/> object or inheritor containing requested data.</returns>
|
||||||
|
[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<Structs.Actor>(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),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.Game.ClientState.Actors.Types;
|
using Dalamud.Game.ClientState.Actors.Types;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace Dalamud.Game.ClientState.Actors
|
namespace Dalamud.Game.ClientState.Actors
|
||||||
{
|
{
|
||||||
|
|
@ -91,6 +92,7 @@ namespace Dalamud.Game.ClientState.Actors
|
||||||
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
|
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[CanBeNull]
|
||||||
private Actor GetActorByOffset(int offset)
|
private Actor GetActorByOffset(int offset)
|
||||||
{
|
{
|
||||||
if (this.address.TargetManager == IntPtr.Zero)
|
if (this.address.TargetManager == IntPtr.Zero)
|
||||||
|
|
@ -100,7 +102,7 @@ namespace Dalamud.Game.ClientState.Actors
|
||||||
if (actorAddress == IntPtr.Zero)
|
if (actorAddress == IntPtr.Zero)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
|
return this.dalamud.ClientState.Actors.CreateActorReference(actorAddress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,6 @@ namespace Dalamud.Game.ClientState
|
||||||
{
|
{
|
||||||
this.PartyList.Dispose();
|
this.PartyList.Dispose();
|
||||||
this.setupTerritoryTypeHook.Dispose();
|
this.setupTerritoryTypeHook.Dispose();
|
||||||
this.Actors.Dispose();
|
|
||||||
this.GamepadState.Dispose();
|
this.GamepadState.Dispose();
|
||||||
|
|
||||||
this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;
|
this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue