feat: add new-style actor table

This commit is contained in:
goat 2021-07-14 03:35:00 +02:00
parent da579f7cd6
commit 5ae1d9c178
No known key found for this signature in database
GPG key ID: F18F057873895461
3 changed files with 96 additions and 158 deletions

View file

@ -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
{
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable
public class ActorTable : IReadOnlyCollection<Actor>, 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<Actor> actorsCache;
private readonly Dalamud dalamud;
/// <summary>
/// Initializes a new instance of the <see cref="ActorTable"/> class.
/// Set up the actor table collection.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
/// <param name="dalamud">The <see cref="dalamud"/> instance.</param>
/// <param name="addressResolver">Client state address resolver.</param>
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);
}
/// <summary>
/// Gets the amount of currently spawned actors.
/// </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>
/// Get an actor at the specified spawn index.
/// Get an actor at the specified spawn index.
/// </summary>
/// <param name="index">Spawn index.</param>
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
[CanBeNull]
public Actor this[int index] => this.ActorsCache[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)
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<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;
}
}
private void ResetCache() => this.actorsCache = null;
private void Framework_OnUpdateEvent(Internal.Framework framework)
/// <summary>
/// Gets the address of the actor at the specified index of the actor table.
/// </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++)
{
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
yield return this[i];
}
return actors;
}
}
/// <summary>
/// Implementing IDisposable.
/// </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()
/// <inheritdoc/>
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;
}
}
/// <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>
/// <inheritdoc/>
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++;
}
}
/// <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),
};
}
}
}

View file

@ -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);
}
}

View file

@ -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;