mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +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.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),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue