StyleCop: everything else

This commit is contained in:
Raymond Lynch 2021-05-30 07:10:00 -04:00
parent f64c9b8321
commit 595fd3f1e4
134 changed files with 16346 additions and 6202 deletions

View file

@ -1,170 +1,224 @@
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;
using JetBrains.Annotations;
using Serilog;
namespace Dalamud.Game.ClientState.Actors {
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// This collection represents the currently spawned FFXIV actors.
/// This collection represents the currently spawned FFXIV actors.
/// </summary>
public class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable {
public sealed partial class ActorTable : IReadOnlyCollection<Actor>, ICollection, IDisposable
{
private const int ActorTableLength = 424;
#region Actor Table Cache
#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 List<Actor> ActorsCache {
get {
if (this.actorsCache != null) return this.actorsCache;
this.actorsCache = GetActorTable();
return this.actorsCache;
}
}
private void ResetCache() => actorsCache = null;
#endregion
#region ReadProcessMemory Hack
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
IntPtr lpBuffer,
int dwSize,
out IntPtr lpNumberOfBytesRead);
private static readonly int ActorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
private IntPtr actorMem = Marshal.AllocHGlobal(ActorMemSize);
private IntPtr currentProcessHandle = new IntPtr(-1);
#endregion
private ClientStateAddressResolver Address { get; }
private Dalamud dalamud;
/// <summary>
/// Set up the actor table collection.
/// Initializes a new instance of the <see cref="ActorTable"/> class.
/// Set up the actor table collection.
/// </summary>
/// <param name="addressResolver">Client state address resolver.</param>
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
Address = addressResolver;
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
public ActorTable(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;
dalamud.Framework.OnUpdateEvent += Framework_OnUpdateEvent;
dalamud.Framework.OnUpdateEvent += this.Framework_OnUpdateEvent;
Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
}
private void Framework_OnUpdateEvent(Internal.Framework framework) {
this.ResetCache();
Log.Verbose("Actor table address {ActorTable}", this.address.ActorTable);
}
/// <summary>
/// Get an actor at the specified spawn index.
/// Gets the amount of currently spawned actors.
/// </summary>
public int Length => this.ActorsCache.Count;
private List<Actor> ActorsCache => this.actorsCache ??= this.GetActorTable();
/// <summary>
/// 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] {
get => ActorsCache[index];
}
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)
{
try {
try
{
// FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
if (!ReadProcessMemory(this.currentProcessHandle, offset, this.actorMem, ActorMemSize, out _))
if (!NativeFunctions.ReadProcessMemory(CurrentProcessHandle, offset, ActorMem, ActorMemSize, out _))
{
Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
return null;
}
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(this.actorMem);
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(ActorMem);
return actorStruct.ObjectKind switch {
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)
_ => new Actor(offset, actorStruct, this.dalamud),
};
}
catch (Exception e) {
catch (Exception e)
{
Log.Error(e, "Could not read actor from memory.");
return null;
}
}
private IntPtr[] GetPointerTable() {
private void ResetCache() => this.actorsCache = null;
private void Framework_OnUpdateEvent(Internal.Framework framework)
{
this.ResetCache();
}
private IntPtr[] GetPointerTable()
{
var ret = new IntPtr[ActorTableLength];
Marshal.Copy(Address.ActorTable, ret, 0, ActorTableLength);
Marshal.Copy(this.address.ActorTable, ret, 0, ActorTableLength);
return ret;
}
private List<Actor> GetActorTable() {
private List<Actor> GetActorTable()
{
var actors = new List<Actor>();
var ptrTable = GetPointerTable();
for (var i = 0; i < ActorTableLength; i++) {
actors.Add(ptrTable[i] != IntPtr.Zero ? ReadActorFromMemory(ptrTable[i]) : null);
var ptrTable = this.GetPointerTable();
for (var i = 0; i < ActorTableLength; i++)
{
actors.Add(ptrTable[i] != IntPtr.Zero ? this.ReadActorFromMemory(ptrTable[i]) : null);
}
return actors;
}
}
public IEnumerator<Actor> GetEnumerator() {
return ActorsCache.Where(a => a != null).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
/// <summary>
/// Implementing IDisposable.
/// </summary>
public sealed partial class ActorTable : IDisposable
{
private bool disposed = false;
/// <summary>
/// The amount of currently spawned actors.
/// Finalizes an instance of the <see cref="ActorTable"/> class.
/// </summary>
public int Length => ActorsCache.Count;
~ActorTable() => this.Dispose(false);
int IReadOnlyCollection<Actor>.Count => Length;
/// <summary>
/// Disposes of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
int ICollection.Count => Length;
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;
void ICollection.CopyTo(Array array, int index) {
for (var i = 0; i < Length; i++) {
/// <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)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
#region IDisposable Pattern
private bool disposed = false;
private void Dispose(bool disposing)
{
if (this.disposed) return;
this.dalamud.Framework.OnUpdateEvent -= Framework_OnUpdateEvent;
Marshal.FreeHGlobal(this.actorMem);
this.disposed = true;
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
~ActorTable() {
Dispose(false);
}
#endregion
}
}

View file

@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// This enum describes the indices of the Customize array.
/// </summary>
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
public enum CustomizeIndex {
public enum CustomizeIndex
{
/// <summary>
/// The race of the character.
/// </summary>
@ -35,12 +30,12 @@ namespace Dalamud.Game.ClientState.Actors
/// The model type of the character.
/// </summary>
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
/// <summary>
/// The face type of the character.
/// </summary>
FaceType = 0x05,
/// <summary>
/// The hair of the character.
/// </summary>
@ -50,12 +45,12 @@ namespace Dalamud.Game.ClientState.Actors
/// Whether or not the character has hair highlights.
/// </summary>
HasHighlights = 0x07, // negative to enable, positive to disable
/// <summary>
/// The skin color of the character.
/// </summary>
SkinColor = 0x08,
/// <summary>
/// The eye color of the character.
/// </summary>
@ -125,17 +120,17 @@ namespace Dalamud.Game.ClientState.Actors
/// The race feature type of the character.
/// </summary>
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
/// <summary>
/// The bust size of the character.
/// </summary>
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
/// <summary>
/// The face paint of the character.
/// </summary>
Facepaint = 0x18,
/// <summary>
/// The face paint color of the character.
/// </summary>

View file

@ -1,69 +1,83 @@
namespace Dalamud.Game.ClientState.Actors {
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// Enum describing possible entity kinds.
/// Enum describing possible entity kinds.
/// </summary>
public enum ObjectKind : byte {
public enum ObjectKind : byte
{
/// <summary>
/// Invalid actor.
/// Invalid actor.
/// </summary>
None = 0x00,
/// <summary>
/// Objects representing player characters.
/// Objects representing player characters.
/// </summary>
Player = 0x01,
/// <summary>
/// Objects representing battle NPCs.
/// Objects representing battle NPCs.
/// </summary>
BattleNpc = 0x02,
/// <summary>
/// Objects representing event NPCs.
/// Objects representing event NPCs.
/// </summary>
EventNpc = 0x03,
/// <summary>
/// Objects representing treasures.
/// Objects representing treasures.
/// </summary>
Treasure = 0x04,
/// <summary>
/// Objects representing aetherytes.
/// Objects representing aetherytes.
/// </summary>
Aetheryte = 0x05,
/// <summary>
/// Objects representing gathering points.
/// Objects representing gathering points.
/// </summary>
GatheringPoint = 0x06,
/// <summary>
/// Objects representing event objects.
/// Objects representing event objects.
/// </summary>
EventObj = 0x07,
/// <summary>
/// Objects representing mounts.
/// Objects representing mounts.
/// </summary>
MountType = 0x08,
/// <summary>
/// Objects representing minions.
/// Objects representing minions.
/// </summary>
Companion = 0x09, // Minion
/// <summary>
/// Objects representing retainers.
/// Objects representing retainers.
/// </summary>
Retainer = 0x0A,
/// <summary>
/// Objects representing area objects.
/// </summary>
Area = 0x0B,
/// <summary>
/// Objects representing housing objects.
/// Objects representing housing objects.
/// </summary>
Housing = 0x0C,
/// <summary>
/// Objects representing cutscene objects.
/// </summary>
Cutscene = 0x0D,
CardStand = 0x0E
/// <summary>
/// Objects representing card stand objects.
/// </summary>
CardStand = 0x0E,
}
}

View file

@ -1,22 +1,38 @@
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Actors {
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// A game native equivalent of a Vector3.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct Position3 {
public struct Position3
{
/// <summary>
/// The X of (X,Z,Y).
/// </summary>
public float X;
/// <summary>
/// The Z of (X,Z,Y).
/// </summary>
public float Z;
/// <summary>
/// The Y of (X,Z,Y).
/// </summary>
public float Y;
/// <summary>
/// Convert this Position3 to a System.Numerics.Vector3
/// Convert this Position3 to a System.Numerics.Vector3.
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator System.Numerics.Vector3(Position3 pos) => new System.Numerics.Vector3(pos.X, pos.Y, pos.Z);
public static implicit operator System.Numerics.Vector3(Position3 pos) => new(pos.X, pos.Y, pos.Z);
/// <summary>
/// Convert this Position3 to a SharpDX.Vector3
/// Convert this Position3 to a SharpDX.Vector3.
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator SharpDX.Vector3(Position3 pos) => new SharpDX.Vector3(pos.X, pos.Z, pos.Y);
public static implicit operator SharpDX.Vector3(Position3 pos) => new(pos.X, pos.Z, pos.Y);
}
}

View file

@ -1,17 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
public abstract class BaseResolver {
protected Dalamud dalamud;
/// <summary>
/// Base object resolver.
/// </summary>
public abstract class BaseResolver
{
private Dalamud dalamud;
public BaseResolver(Dalamud dalamud) {
/// <summary>
/// Initializes a new instance of the <see cref="BaseResolver"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
public BaseResolver(Dalamud dalamud)
{
this.dalamud = dalamud;
}
/// <summary>
/// Gets the Dalamud instance.
/// </summary>
protected Dalamud Dalamud => this.dalamud;
}
}

View file

@ -1,32 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
/// <summary>
/// This object represents a class or job.
/// </summary>
public class ClassJob : BaseResolver {
public class ClassJob : BaseResolver
{
/// <summary>
/// ID of the ClassJob.
/// </summary>
public readonly uint Id;
/// <summary>
/// GameData linked to this ClassJob.
/// </summary>
public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
this.dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>().GetRow(this.Id);
/// <summary>
/// Initializes a new instance of the <see cref="ClassJob"/> class.
/// Set up the ClassJob resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the world.</param>
public ClassJob(byte id, Dalamud dalamud) : base(dalamud) {
/// <param name="id">The ID of the classJob.</param>
/// <param name="dalamud">The Dalamud instance.</param>
public ClassJob(byte id, Dalamud dalamud)
: base(dalamud)
{
this.Id = id;
}
/// <summary>
/// Gets GameData linked to this ClassJob.
/// </summary>
public Lumina.Excel.GeneratedSheets.ClassJob GameData =>
this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.ClassJob>().GetRow(this.Id);
}
}

View file

@ -1,32 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.ClientState.Actors.Resolvers
{
/// <summary>
/// This object represents a world a character can reside on.
/// </summary>
public class World : BaseResolver {
public class World : BaseResolver
{
/// <summary>
/// ID of the world.
/// </summary>
public readonly uint Id;
/// <summary>
/// GameData linked to this world.
/// </summary>
public Lumina.Excel.GeneratedSheets.World GameData =>
this.dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>().GetRow(this.Id);
/// <summary>
/// Initializes a new instance of the <see cref="World"/> class.
/// Set up the world resolver with the provided ID.
/// </summary>
/// <param name="id">The ID of the world.</param>
public World(ushort id, Dalamud dalamud) : base(dalamud) {
/// <param name="dalamud">The Dalamud instance.</param>
public World(ushort id, Dalamud dalamud)
: base(dalamud)
{
this.Id = id;
}
/// <summary>
/// Gets GameData linked to this world.
/// </summary>
public Lumina.Excel.GeneratedSheets.World GameData =>
this.Dalamud.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.World>().GetRow(this.Id);
}
}

View file

@ -1,50 +1,118 @@
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
namespace Dalamud.Game.ClientState.Actors {
public static class TargetOffsets {
public const int CurrentTarget = 0x80;
public const int MouseOverTarget = 0xD0;
public const int FocusTarget = 0xF8;
public const int PreviousTarget = 0x110;
public const int SoftTarget = 0x88;
}
public sealed class Targets {
private ClientStateAddressResolver Address { get; }
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// Get and set various kinds of targets for the player.
/// </summary>
public sealed class Targets
{
private Dalamud dalamud;
private ClientStateAddressResolver address;
public Actor CurrentTarget => GetActorByOffset(TargetOffsets.CurrentTarget);
public Actor MouseOverTarget => GetActorByOffset(TargetOffsets.MouseOverTarget);
public Actor FocusTarget => GetActorByOffset(TargetOffsets.FocusTarget);
public Actor PreviousTarget => GetActorByOffset(TargetOffsets.PreviousTarget);
public Actor SoftTarget => GetActorByOffset(TargetOffsets.SoftTarget);
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver) {
/// <summary>
/// Initializes a new instance of the <see cref="Targets"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
internal Targets(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.dalamud = dalamud;
Address = addressResolver;
this.address = addressResolver;
}
public void SetCurrentTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
public void SetCurrentTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.CurrentTarget);
/// <summary>
/// Gets the current target.
/// </summary>
public Actor CurrentTarget => this.GetActorByOffset(TargetOffsets.CurrentTarget);
public void SetFocusTarget(Actor actor) => SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
public void SetFocusTarget(IntPtr actorAddress) => SetTarget(actorAddress, TargetOffsets.FocusTarget);
/// <summary>
/// Gets the mouseover target.
/// </summary>
public Actor MouseOverTarget => this.GetActorByOffset(TargetOffsets.MouseOverTarget);
public void ClearCurrentTarget() => SetCurrentTarget(IntPtr.Zero);
public void ClearFocusTarget() => SetFocusTarget(IntPtr.Zero);
/// <summary>
/// Gets the focus target.
/// </summary>
public Actor FocusTarget => this.GetActorByOffset(TargetOffsets.FocusTarget);
private void SetTarget(IntPtr actorAddress, int offset) {
if (Address.TargetManager == IntPtr.Zero) return;
Marshal.WriteIntPtr(Address.TargetManager, offset, actorAddress);
/// <summary>
/// Gets the previous target.
/// </summary>
public Actor PreviousTarget => this.GetActorByOffset(TargetOffsets.PreviousTarget);
/// <summary>
/// Gets the soft target.
/// </summary>
public Actor SoftTarget => this.GetActorByOffset(TargetOffsets.SoftTarget);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actor">Actor to target.</param>
public void SetCurrentTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.CurrentTarget);
/// <summary>
/// Sets the current target.
/// </summary>
/// <param name="actorAddress">Actor (address) to target.</param>
public void SetCurrentTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.CurrentTarget);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actor">Actor to focus.</param>
public void SetFocusTarget(Actor actor) => this.SetTarget(actor?.Address ?? IntPtr.Zero, TargetOffsets.FocusTarget);
/// <summary>
/// Sets the focus target.
/// </summary>
/// <param name="actorAddress">Actor (address) to focus.</param>
public void SetFocusTarget(IntPtr actorAddress) => this.SetTarget(actorAddress, TargetOffsets.FocusTarget);
/// <summary>
/// Clears the current target.
/// </summary>
public void ClearCurrentTarget() => this.SetCurrentTarget(IntPtr.Zero);
/// <summary>
/// Clears the focus target.
/// </summary>
public void ClearFocusTarget() => this.SetFocusTarget(IntPtr.Zero);
private void SetTarget(IntPtr actorAddress, int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
return;
Marshal.WriteIntPtr(this.address.TargetManager, offset, actorAddress);
}
private Actor GetActorByOffset(int offset) {
if (Address.TargetManager == IntPtr.Zero) return null;
var actorAddress = Marshal.ReadIntPtr(Address.TargetManager + offset);
if (actorAddress == IntPtr.Zero) return null;
private Actor GetActorByOffset(int offset)
{
if (this.address.TargetManager == IntPtr.Zero)
return null;
var actorAddress = Marshal.ReadIntPtr(this.address.TargetManager + offset);
if (actorAddress == IntPtr.Zero)
return null;
return this.dalamud.ClientState.Actors.ReadActorFromMemory(actorAddress);
}
}
/// <summary>
/// Memory offsets for the <see cref="Targets"/> type.
/// </summary>
public static class TargetOffsets
{
public const int CurrentTarget = 0x80;
public const int SoftTarget = 0x88;
public const int MouseOverTarget = 0xD0;
public const int FocusTarget = 0xF8;
public const int PreviousTarget = 0x110;
}
}

View file

@ -5,13 +5,11 @@ using Dalamud.Game.ClientState.Structs;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a basic FFXIV actor.
/// This class represents a basic FFXIV actor.
/// </summary>
public class Actor : IEquatable<Actor>
{
private readonly Structs.Actor actorStruct;
// This is a breaking change. StyleCop demands it.
// private readonly IntPtr address;
private readonly Dalamud dalamud;
/// <summary>
@ -83,8 +81,6 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <summary>
/// Gets the address of this actor in memory.
/// </summary>
// TODO: This is a breaking change, StyleCop demands it.
// public IntPtr Address => this.address;
public readonly IntPtr Address;
/// <summary>

View file

@ -29,7 +29,7 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <summary>
/// Gets the ClassJob of this Chara.
/// </summary>
public ClassJob ClassJob => new ClassJob(this.ActorStruct.ClassJob, this.Dalamud);
public ClassJob ClassJob => new(this.ActorStruct.ClassJob, this.Dalamud);
/// <summary>
/// Gets the current HP of this Chara.

View file

@ -2,27 +2,51 @@ using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Actors.Types
{
/// <summary>
/// This class represents a party member.
/// </summary>
public class PartyMember
{
/// <summary>
/// The name of the character.
/// </summary>
public string CharacterName;
/// <summary>
/// Unknown.
/// </summary>
public long Unknown;
/// <summary>
/// The actor object that corresponds to this party member.
/// </summary>
public Actor Actor;
/// <summary>
/// The kind or type of actor.
/// </summary>
public ObjectKind ObjectKind;
/// <summary>
/// Initializes a new instance of the <see cref="PartyMember"/> class.
/// </summary>
/// <param name="table">The ActorTable instance.</param>
/// <param name="rawData">The interop data struct.</param>
public PartyMember(ActorTable table, Structs.PartyMember rawData)
{
CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
Unknown = rawData.unknown;
Actor = null;
this.CharacterName = Marshal.PtrToStringAnsi(rawData.namePtr);
this.Unknown = rawData.unknown;
this.Actor = null;
for (var i = 0; i < table.Length; i++)
{
if (table[i] != null && table[i].ActorId == rawData.actorId)
{
Actor = table[i];
this.Actor = table[i];
break;
}
}
ObjectKind = rawData.objectKind;
this.ObjectKind = rawData.objectKind;
}
}
}

View file

@ -31,12 +31,12 @@ namespace Dalamud.Game.ClientState.Actors.Types
/// <summary>
/// Gets the current <see cref="World">world</see> of the character.
/// </summary>
public World CurrentWorld => new World(this.ActorStruct.CurrentWorld, this.Dalamud);
public World CurrentWorld => new(this.ActorStruct.CurrentWorld, this.Dalamud);
/// <summary>
/// Gets the home <see cref="World">world</see> of the character.
/// </summary>
public World HomeWorld => new World(this.ActorStruct.HomeWorld, this.Dalamud);
public World HomeWorld => new(this.ActorStruct.HomeWorld, this.Dalamud);
/// <summary>
/// Gets the Free Company tag of this player.

View file

@ -1,10 +1,10 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.Internal;
using Dalamud.Game.Internal.Network;
using Dalamud.Hooking;
using JetBrains.Annotations;
using Lumina.Excel.GeneratedSheets;
@ -15,41 +15,17 @@ namespace Dalamud.Game.ClientState
/// <summary>
/// This class represents the state of the game client at the time of access.
/// </summary>
public class ClientState : INotifyPropertyChanged, IDisposable {
private readonly Dalamud dalamud;
public event PropertyChangedEventHandler PropertyChanged;
private ClientStateAddressResolver Address { get; }
public readonly ClientLanguage ClientLanguage;
public class ClientState : INotifyPropertyChanged, IDisposable
{
/// <summary>
/// The table of all present actors.
/// </summary>
public readonly ActorTable Actors;
/// <summary>
/// The local player character, if one is present.
/// Gets the language of the client.
/// </summary>
[CanBeNull]
public PlayerCharacter LocalPlayer {
get {
var actor = this.Actors[0];
if (actor is PlayerCharacter pc)
return pc;
return null;
}
}
#region TerritoryType
// TODO: The hooking logic for this should go into a separate class.
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
public readonly ClientLanguage ClientLanguage;
/// <summary>
/// The current Territory the player resides in.
@ -57,39 +33,12 @@ namespace Dalamud.Game.ClientState
public ushort TerritoryType;
/// <summary>
/// Event that gets fired when the current Territory changes.
/// </summary>
public EventHandler<ushort> TerritoryChanged;
/// <summary>
/// Event that gets fired when a duty is ready.
/// </summary>
public event EventHandler<ContentFinderCondition> CfPop;
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
{
this.TerritoryType = terriType;
this.TerritoryChanged?.Invoke(this, terriType);
Log.Debug("TerritoryType changed: {0}", terriType);
return this.setupTerritoryTypeHook.Original(manager, terriType);
}
#endregion
/// <summary>
/// The content ID of the local character.
/// </summary>
public ulong LocalContentId => (ulong) Marshal.ReadInt64(Address.LocalContentId);
/// <summary>
/// The class facilitating Job Gauge data access
/// The class facilitating Job Gauge data access.
/// </summary>
public JobGauges JobGauges;
/// <summary>
/// The class facilitating party list data access
/// The class facilitating party list data access.
/// </summary>
public PartyList PartyList;
@ -102,77 +51,76 @@ namespace Dalamud.Game.ClientState
/// Provides access to the button state of gamepad buttons in game.
/// </summary>
public GamepadState GamepadState;
/// <summary>
/// Provides access to client conditions/player state. Allows you to check if a player is in a duty, mounted, etc.
/// </summary>
public Condition Condition;
/// <summary>
/// The class facilitating target data access
/// The class facilitating target data access.
/// </summary>
public Targets Targets;
/// <summary>
/// Event that gets fired when the current Territory changes.
/// </summary>
public EventHandler<ushort> TerritoryChanged;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
private readonly Hook<SetupTerritoryTypeDelegate> setupTerritoryTypeHook;
private bool lastConditionNone = true;
/// <summary>
/// Initializes a new instance of the <see cref="ClientState"/> class.
/// Set up client state access.
/// </summary>
/// <param name="dalamud">Dalamud instance</param>
/// /// <param name="startInfo">StartInfo of the current Dalamud launch</param>
/// <param name="scanner">Sig scanner</param>
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner) {
/// <param name="dalamud">Dalamud instance.</param>
/// <param name="startInfo">StartInfo of the current Dalamud launch.</param>
/// <param name="scanner">Sig scanner.</param>
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner)
{
this.dalamud = dalamud;
Address = new ClientStateAddressResolver();
Address.Setup(scanner);
this.address = new ClientStateAddressResolver();
this.address.Setup(scanner);
Log.Verbose("===== C L I E N T S T A T E =====");
this.ClientLanguage = startInfo.Language;
this.Actors = new ActorTable(dalamud, Address);
this.Actors = new ActorTable(dalamud, this.address);
this.PartyList = new PartyList(dalamud, Address);
this.PartyList = new PartyList(dalamud, this.address);
this.JobGauges = new JobGauges(Address);
this.JobGauges = new JobGauges(this.address);
this.KeyState = new KeyState(Address, scanner.Module.BaseAddress);
this.KeyState = new KeyState(this.address, scanner.Module.BaseAddress);
this.GamepadState = new GamepadState(this.Address);
this.GamepadState = new GamepadState(this.address);
this.Condition = new Condition( Address );
this.Condition = new Condition(this.address);
this.Targets = new Targets(dalamud, Address);
this.Targets = new Targets(dalamud, this.address);
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", Address.SetupTerritoryType);
Log.Verbose("SetupTerritoryType address {SetupTerritoryType}", this.address.SetupTerritoryType);
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(Address.SetupTerritoryType,
new SetupTerritoryTypeDelegate(SetupTerritoryTypeDetour),
this);
this.setupTerritoryTypeHook = new Hook<SetupTerritoryTypeDelegate>(this.address.SetupTerritoryType, new SetupTerritoryTypeDelegate(this.SetupTerritoryTypeDetour), this);
dalamud.Framework.OnUpdateEvent += FrameworkOnOnUpdateEvent;
dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
dalamud.Framework.OnUpdateEvent += this.FrameworkOnOnUpdateEvent;
dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
}
private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e) {
CfPop?.Invoke(this, e);
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate IntPtr SetupTerritoryTypeDelegate(IntPtr manager, ushort terriType);
public void Enable() {
this.GamepadState.Enable();
this.PartyList.Enable();
this.setupTerritoryTypeHook.Enable();
}
public void Dispose() {
this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose();
this.Actors.Dispose();
this.GamepadState.Dispose();
this.dalamud.Framework.OnUpdateEvent -= FrameworkOnOnUpdateEvent;
this.dalamud.NetworkHandlers.CfPop += NetworkHandlersOnCfPop;
}
private bool lastConditionNone = true;
/// <summary>
/// Event that fires when a property changes.
/// </summary>
#pragma warning disable CS0067
public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore
/// <summary>
/// Event that fires when a character is logging in.
@ -184,24 +132,93 @@ namespace Dalamud.Game.ClientState
/// </summary>
public event EventHandler OnLogout;
/// <summary>
/// Event that gets fired when a duty is ready.
/// </summary>
public event EventHandler<ContentFinderCondition> CfPop;
/// <summary>
/// Gets the local player character, if one is present.
/// </summary>
[CanBeNull]
public PlayerCharacter LocalPlayer
{
get
{
var actor = this.Actors[0];
if (actor is PlayerCharacter pc)
return pc;
return null;
}
}
/// <summary>
/// Gets the content ID of the local character.
/// </summary>
public ulong LocalContentId => (ulong)Marshal.ReadInt64(this.address.LocalContentId);
/// <summary>
/// Gets a value indicating whether a character is logged in.
/// </summary>
public bool IsLoggedIn { get; private set; }
private void FrameworkOnOnUpdateEvent(Framework framework) {
if (this.Condition.Any() && this.lastConditionNone == true) {
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
this.GamepadState.Enable();
this.PartyList.Enable();
this.setupTerritoryTypeHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose();
this.Actors.Dispose();
this.GamepadState.Dispose();
this.dalamud.Framework.OnUpdateEvent -= this.FrameworkOnOnUpdateEvent;
this.dalamud.NetworkHandlers.CfPop += this.NetworkHandlersOnCfPop;
}
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
{
this.TerritoryType = terriType;
this.TerritoryChanged?.Invoke(this, terriType);
Log.Debug("TerritoryType changed: {0}", terriType);
return this.setupTerritoryTypeHook.Original(manager, terriType);
}
private void NetworkHandlersOnCfPop(object sender, ContentFinderCondition e)
{
this.CfPop?.Invoke(this, e);
}
private void FrameworkOnOnUpdateEvent(Framework framework)
{
if (this.Condition.Any() && this.lastConditionNone == true)
{
Log.Debug("Is login");
this.lastConditionNone = false;
this.IsLoggedIn = true;
OnLogin?.Invoke(this, null);
this.OnLogin?.Invoke(this, null);
}
if (!this.Condition.Any() && this.lastConditionNone == false) {
if (!this.Condition.Any() && this.lastConditionNone == false)
{
Log.Debug("Is logout");
this.lastConditionNone = true;
this.IsLoggedIn = false;
OnLogout?.Invoke(this, null);
this.OnLogout?.Invoke(this, null);
}
}
}

View file

@ -1,50 +1,88 @@
using System;
using Dalamud.Game.Internal;
namespace Dalamud.Game.ClientState
{
public sealed class ClientStateAddressResolver : BaseAddressResolver {
/// <summary>
/// Client state memory address resolver.
/// </summary>
public sealed class ClientStateAddressResolver : BaseAddressResolver
{
// Static offsets
public IntPtr ActorTable { get; private set; }
//public IntPtr ViewportActorTable { get; private set; }
public IntPtr LocalContentId { get; private set; }
public IntPtr JobGaugeData { get; private set; }
public IntPtr KeyboardState { get; private set; }
public IntPtr TargetManager { get; private set; }
// Functions
public IntPtr SetupTerritoryType { get; private set; }
//public IntPtr SomeActorTableAccess { get; private set; }
//public IntPtr PartyListUpdate { get; private set; }
/// <summary>
/// Game function which polls the gamepads for data.
///
/// Gets the address of the actor table.
/// </summary>
public IntPtr ActorTable { get; private set; }
// public IntPtr ViewportActorTable { get; private set; }
/// <summary>
/// Gets the address of the local content id.
/// </summary>
public IntPtr LocalContentId { get; private set; }
/// <summary>
/// Gets the address of job gauge data.
/// </summary>
public IntPtr JobGaugeData { get; private set; }
/// <summary>
/// Gets the address of the keyboard state.
/// </summary>
public IntPtr KeyboardState { get; private set; }
/// <summary>
/// Gets the address of the target manager.
/// </summary>
public IntPtr TargetManager { get; private set; }
/// <summary>
/// Gets the address of the condition flag array.
/// </summary>
public IntPtr ConditionFlags { get; private set; }
// Functions
/// <summary>
/// Gets the address of the method which sets the territory type.
/// </summary>
public IntPtr SetupTerritoryType { get; private set; }
// public IntPtr SomeActorTableAccess { get; private set; }
// public IntPtr PartyListUpdate { get; private set; }
/// <summary>
/// Gets the address of the method which polls the gamepads for data.
/// Called every frame, even when `Enable Gamepad` is off in the settings.
/// </summary>
public IntPtr GamepadPoll { get; private set; }
public IntPtr ConditionFlags { get; private set; }
protected override void Setup64Bit(SigScanner sig) {
/// <summary>
/// Scan for and setup any configured address pointers.
/// </summary>
/// <param name="sig">The signature scanner to facilitate setup.</param>
protected override void Setup64Bit(SigScanner sig)
{
// We don't need those anymore, but maybe someone else will - let's leave them here for good measure
//ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
//SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
// ViewportActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 85 ED", 0) + 0x148;
// SomeActorTableAccess = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 55 A0 48 8D 8E ?? ?? ?? ??");
this.ActorTable = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B6 83");
LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
this.LocalContentId = sig.GetStaticAddressFromSig("48 0F 44 05 ?? ?? ?? ?? 48 39 07");
this.JobGaugeData = sig.GetStaticAddressFromSig("E8 ?? ?? ?? ?? FF C6 48 8D 5B 0C", 0xB9) + 0x10;
SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
this.SetupTerritoryType = sig.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F9 66 89 91 ?? ?? ?? ??");
// This resolves to a fixed offset only, without the base address added in, so GetStaticAddressFromSig() can't be used
KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
this.KeyboardState = sig.ScanText("48 8D 0C 85 ?? ?? ?? ?? 8B 04 31 85 C2 0F 85") + 0x4;
//PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
// PartyListUpdate = sig.ScanText("E8 ?? ?? ?? ?? 49 8B D7 4C 8D 86 ?? ?? ?? ??");
ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
this.ConditionFlags = sig.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? BA ?? ?? ?? ?? E8 ?? ?? ?? ?? B0 01 48 83 C4 30");
TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
this.TargetManager = sig.GetStaticAddressFromSig("48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 3);
this.GamepadPoll = sig.ScanText("40 ?? 57 41 ?? 48 81 EC ?? ?? ?? ?? 44 0F ?? ?? ?? ?? ?? ?? ?? 48 8B");
}

View file

@ -1,7 +1,4 @@
using System;
using System.Runtime.CompilerServices;
using Dalamud.Hooking;
using Serilog;
namespace Dalamud.Game.ClientState
{
@ -10,36 +7,49 @@ namespace Dalamud.Game.ClientState
/// </summary>
public class Condition
{
internal readonly IntPtr conditionArrayBase;
/// <summary>
/// The current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
/// </summary>
public const int MaxConditionEntries = 100;
internal Condition( ClientStateAddressResolver resolver )
/// <summary>
/// Initializes a new instance of the <see cref="Condition"/> class.
/// </summary>
/// <param name="resolver">The ClientStateAddressResolver instance.</param>
internal Condition(ClientStateAddressResolver resolver)
{
this.conditionArrayBase = resolver.ConditionFlags;
this.ConditionArrayBase = resolver.ConditionFlags;
}
/// <summary>
/// Gets the condition array base pointer.
/// Would typically be private but is used in /xldata windows.
/// </summary>
internal IntPtr ConditionArrayBase { get; private set; }
/// <summary>
/// Check the value of a specific condition/state flag.
/// </summary>
/// <param name="flag">The condition flag to check</param>
public unsafe bool this[ ConditionFlag flag ]
/// <param name="flag">The condition flag to check.</param>
public unsafe bool this[ConditionFlag flag]
{
get
{
var idx = ( int )flag;
if( idx > MaxConditionEntries || idx < 0 )
var idx = (int)flag;
if (idx > MaxConditionEntries || idx < 0)
return false;
return *( bool* )( this.conditionArrayBase + idx );
return *(bool*)(this.ConditionArrayBase + idx);
}
}
public bool Any() {
/// <summary>
/// Check if any condition flags are set.
/// </summary>
/// <returns>Whether any single flag is set.</returns>
public bool Any()
{
for (var i = 0; i < MaxConditionEntries; i++)
{
var typedCondition = (ConditionFlag)i;

View file

@ -6,7 +6,8 @@ namespace Dalamud.Game.ClientState
/// These come from LogMessage (somewhere) and directly map to each state field managed by the client. As of 5.25, it maps to
/// LogMessage row 7700 and onwards, which can be checked by looking at the Condition sheet and looking at what column 2 maps to.
/// </summary>
public enum ConditionFlag {
public enum ConditionFlag
{
/// <summary>
/// Unused.
/// </summary>
@ -27,9 +28,6 @@ namespace Dalamud.Game.ClientState
/// </summary>
Emoting = 3,
/// <summary>
/// Unable to execute command while mounted.
/// </summary>
/// <summary>
/// Unable to execute command while mounted.
/// </summary>
@ -94,15 +92,15 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while performing.
/// </summary>
Performing = 16,
//Unknown17 = 17,
//Unknown18 = 18,
//Unknown19 = 19,
//Unknown20 = 20,
//Unknown21 = 21,
//Unknown22 = 22,
//Unknown23 = 23,
//Unknown24 = 24,
// Unknown17 = 17,
// Unknown18 = 18,
// Unknown19 = 19,
// Unknown20 = 20,
// Unknown21 = 21,
// Unknown22 = 22,
// Unknown23 = 23,
// Unknown24 = 24,
/// <summary>
/// Unable to execute command while occupied.
@ -199,8 +197,8 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while fishing.
/// </summary>
Fishing = 43,
//Unknown44 = 44,
// Unknown44 = 44,
/// <summary>
/// Unable to execute command while between areas.
@ -211,8 +209,8 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while stealthed.
/// </summary>
Stealthed = 46,
//Unknown47 = 47,
// Unknown47 = 47,
/// <summary>
/// Unable to execute command while jumping.
@ -399,8 +397,8 @@ namespace Dalamud.Game.ClientState
/// Unable to execute command while participating in a cross-world party or alliance.
/// </summary>
ParticipatingInCrossWorldPartyOrAlliance = 84,
//Unknown85 = 85,
// Unknown85 = 85,
/// <summary>
/// Unable to execute command while playing duty record.

View file

@ -126,7 +126,7 @@ namespace Dalamud.Game.ClientState
/// Gets or sets a value indicating whether detour should block gamepad input for game.
///
/// Ideally, we would use
/// (ImGui.GetIO().ConfigFlags & ImGuiConfigFlags.NavEnableGamepad) > 0
/// (ImGui.GetIO().ConfigFlags &amp; ImGuiConfigFlags.NavEnableGamepad) > 0
/// but this has a race condition during load with the detour which sets up ImGui
/// and throws if our detour gets called before the other.
/// </summary>

View file

@ -1,22 +1,26 @@
using Serilog;
using System;
using System.Runtime.InteropServices;
using Serilog;
namespace Dalamud.Game.ClientState
{
/// <summary>
/// Wrapper around the game keystate buffer, which contains the pressed state for
/// all keyboard keys, indexed by virtual vkCode
/// Wrapper around the game keystate buffer, which contains the pressed state for all keyboard keys, indexed by virtual vkCode.
/// </summary>
public class KeyState
{
private IntPtr bufferBase;
// The array is accessed in a way that this limit doesn't appear to exist
// but there is other state data past this point, and keys beyond here aren't
// generally valid for most things anyway
private const int MaxKeyCodeIndex = 0xA0;
private IntPtr bufferBase;
/// <summary>
/// Initializes a new instance of the <see cref="KeyState"/> class.
/// </summary>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
/// <param name="moduleBaseAddress">The base address of the main process module.</param>
public KeyState(ClientStateAddressResolver addressResolver, IntPtr moduleBaseAddress)
{
this.bufferBase = moduleBaseAddress + Marshal.ReadInt32(addressResolver.KeyboardState);
@ -33,10 +37,10 @@ namespace Dalamud.Game.ClientState
{
get
{
if (vkCode< 0 || vkCode > MaxKeyCodeIndex)
if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
throw new ArgumentException($"Keycode state only appears to be valid up to {MaxKeyCodeIndex}");
return (Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0);
return Marshal.ReadInt32(this.bufferBase + (4 * vkCode)) != 0;
}
set

View file

@ -1,96 +1,139 @@
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Hooking;
using Dalamud.Plugin;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors.Types;
// using Dalamud.Hooking;
namespace Dalamud.Game.ClientState
{
public class PartyList : IReadOnlyCollection<PartyMember>, ICollection, IDisposable
/// <summary>
/// This class represents the members of your party.
/// </summary>
public sealed partial class PartyList
{
private ClientStateAddressResolver Address { get; }
private Dalamud dalamud;
private readonly Dalamud dalamud;
private readonly ClientStateAddressResolver address;
// private bool isReady = false;
// private IntPtr partyListBegin;
// private Hook<PartyListUpdateDelegate> partyListUpdateHook;
/// <summary>
/// Initializes a new instance of the <see cref="PartyList"/> class.
/// </summary>
/// <param name="dalamud">The Dalamud instance.</param>
/// <param name="addressResolver">The ClientStateAddressResolver instance.</param>
public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
{
this.address = addressResolver;
this.dalamud = dalamud;
// this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
}
private delegate long PartyListUpdateDelegate(IntPtr structBegin, long param2, char param3);
private Hook<PartyListUpdateDelegate> partyListUpdateHook;
private IntPtr partyListBegin;
private bool isReady = false;
/// <summary>
/// Gets the length of the PartyList.
/// </summary>
public int Length => 0; // !this.isReady ? 0 : Marshal.ReadByte(this.partyListBegin + 0xF0);
public PartyList(Dalamud dalamud, ClientStateAddressResolver addressResolver)
/// <summary>
/// Get the nth party member.
/// </summary>
/// <param name="index">Index of the party member.</param>
/// <returns>The party member.</returns>
public PartyMember this[int index]
{
Address = addressResolver;
this.dalamud = dalamud;
//this.partyListUpdateHook = new Hook<PartyListUpdateDelegate>(Address.PartyListUpdate, new PartyListUpdateDelegate(PartyListUpdateDetour), this);
get
{
return null;
// if (!this.isReady)
// return null;
// if (index >= this.Length)
// return null;
// var tblIndex = this.partyListBegin + (index * 24);
// var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
// return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
}
}
/// <summary>
/// Enable this module.
/// </summary>
public void Enable()
{
// TODO Fix for 5.3
//this.partyListUpdateHook.Enable();
// this.partyListUpdateHook.Enable();
}
/// <summary>
/// Dispose of managed and unmanaged resources.
/// </summary>
public void Dispose()
{
//if (!this.isReady)
// this.partyListUpdateHook.Dispose();
this.isReady = false;
// if (!this.isReady)
// this.partyListUpdateHook.Dispose();
// this.isReady = false;
}
private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
{
var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
this.partyListBegin = structBegin + 0xB48;
this.partyListUpdateHook.Dispose();
this.isReady = true;
return result;
}
// private long PartyListUpdateDetour(IntPtr structBegin, long param2, char param3)
// {
// var result = this.partyListUpdateHook.Original(structBegin, param2, param3);
// this.partyListBegin = structBegin + 0xB48;
// this.partyListUpdateHook.Dispose();
// this.isReady = true;
// return result;
// }
}
public PartyMember this[int index]
{
get {
if (!this.isReady)
return null;
if (index >= Length)
return null;
var tblIndex = partyListBegin + index * 24;
var memberStruct = Marshal.PtrToStructure<Structs.PartyMember>(tblIndex);
return new PartyMember(this.dalamud.ClientState.Actors, memberStruct);
}
}
/// <summary>
/// Implements IReadOnlyCollection, IEnumerable.
/// </summary>
public sealed partial class PartyList : IReadOnlyCollection<PartyMember>
{
/// <inheritdoc/>
int IReadOnlyCollection<PartyMember>.Count => this.Length;
public void CopyTo(Array array, int index)
/// <inheritdoc/>
public IEnumerator<PartyMember> GetEnumerator()
{
for (var i = 0; i < Length; i++)
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
public IEnumerator<PartyMember> GetEnumerator() {
for (var i = 0; i < Length; i++) {
if (this[i] != null) {
if (this[i] != null)
{
yield return this[i];
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
}
public int Length => !this.isReady ? 0 : Marshal.ReadByte(partyListBegin + 0xF0);
int IReadOnlyCollection<PartyMember>.Count => Length;
public int Count => Length;
/// <summary>
/// Implements ICollection.
/// </summary>
public sealed partial class PartyList : ICollection
{
/// <inheritdoc/>
public int Count => this.Length;
/// <inheritdoc/>
public object SyncRoot => this;
/// <inheritdoc/>
public bool IsSynchronized => false;
/// <inheritdoc/>
public void CopyTo(Array array, int index)
{
for (var i = 0; i < this.Length; i++)
{
array.SetValue(this[i], index);
index++;
}
}
}
}

View file

@ -4,7 +4,245 @@ using Dalamud.Game.ClientState.Actors;
namespace Dalamud.Game.ClientState.Structs
{
public class ActorOffsets
/// <summary>
/// Native memory representation of an FFXIV actor.
/// </summary>
[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct Actor
{
/// <summary>
/// The actor name.
/// </summary>
[FieldOffset(ActorOffsets.Name)]
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
public string Name;
/// <summary>
/// The actor's internal id.
/// </summary>
[FieldOffset(ActorOffsets.ActorId)]
public int ActorId;
/// <summary>
/// The actor's data id.
/// </summary>
[FieldOffset(ActorOffsets.DataId)]
public int DataId;
/// <summary>
/// The actor's owner id. This is useful for pets, summons, and the like.
/// </summary>
[FieldOffset(ActorOffsets.OwnerId)]
public int OwnerId;
/// <summary>
/// The type or kind of actor.
/// </summary>
[FieldOffset(ActorOffsets.ObjectKind)]
public ObjectKind ObjectKind;
/// <summary>
/// The sub-type or sub-kind of actor.
/// </summary>
[FieldOffset(ActorOffsets.SubKind)]
public byte SubKind;
/// <summary>
/// Whether the actor is friendly.
/// </summary>
[FieldOffset(ActorOffsets.IsFriendly)]
public bool IsFriendly;
/// <summary>
/// The horizontal distance in game units from the player.
/// </summary>
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)]
public byte YalmDistanceFromPlayerX;
/// <summary>
/// The player target status.
/// </summary>
/// <remarks>
/// This is some kind of enum.
/// </remarks>
[FieldOffset(ActorOffsets.PlayerTargetStatus)]
public byte PlayerTargetStatus;
/// <summary>
/// The vertical distance in game units from the player.
/// </summary>
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)]
public byte YalmDistanceFromPlayerY;
/// <summary>
/// The (X,Z,Y) position of the actor.
/// </summary>
[FieldOffset(ActorOffsets.Position)]
public Position3 Position;
/// <summary>
/// The rotation of the actor.
/// </summary>
/// <remarks>
/// The rotation is around the vertical axis (yaw), from -pi to pi radians.
/// </remarks>
[FieldOffset(ActorOffsets.Rotation)]
public float Rotation;
/// <summary>
/// The hitbox radius of the actor.
/// </summary>
[FieldOffset(ActorOffsets.HitboxRadius)]
public float HitboxRadius;
/// <summary>
/// The current HP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentHp)]
public int CurrentHp;
/// <summary>
/// The max HP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxHp)]
public int MaxHp;
/// <summary>
/// The current MP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentMp)]
public int CurrentMp;
/// <summary>
/// The max MP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxMp)]
public short MaxMp;
/// <summary>
/// The current GP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentGp)]
public short CurrentGp;
/// <summary>
/// The max GP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxGp)]
public short MaxGp;
/// <summary>
/// The current CP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCp)]
public short CurrentCp;
/// <summary>
/// The max CP of the actor.
/// </summary>
[FieldOffset(ActorOffsets.MaxCp)]
public short MaxCp;
/// <summary>
/// The class-job of the actor.
/// </summary>
[FieldOffset(ActorOffsets.ClassJob)]
public byte ClassJob;
/// <summary>
/// The level of the actor.
/// </summary>
[FieldOffset(ActorOffsets.Level)]
public byte Level;
/// <summary>
/// The (player character) actor ID being targeted by the actor.
/// </summary>
[FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)]
public int PlayerCharacterTargetActorId;
/// <summary>
/// The customization byte/bitfield of the actor.
/// </summary>
[FieldOffset(ActorOffsets.Customize)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
public byte[] Customize;
// Normally pack=2 should work, but ByTVal or Injection breaks this.
// [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
/// <summary>
/// The (battle npc) actor ID being targeted by the actor.
/// </summary>
[FieldOffset(ActorOffsets.BattleNpcTargetActorId)]
public int BattleNpcTargetActorId;
/// <summary>
/// The name ID of the actor.
/// </summary>
[FieldOffset(ActorOffsets.NameId)]
public int NameId;
/// <summary>
/// The current world ID of the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentWorld)]
public ushort CurrentWorld;
/// <summary>
/// The home world ID of the actor.
/// </summary>
[FieldOffset(ActorOffsets.HomeWorld)]
public ushort HomeWorld;
/// <summary>
/// Whether the actor is currently casting.
/// </summary>
[FieldOffset(ActorOffsets.IsCasting)]
public bool IsCasting;
/// <summary>
/// Whether the actor is currently casting (dup?).
/// </summary>
[FieldOffset(ActorOffsets.IsCasting2)]
public bool IsCasting2;
/// <summary>
/// The spell action ID currently being cast by the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCastSpellActionId)]
public uint CurrentCastSpellActionId;
/// <summary>
/// The actor ID of the target currently being cast at by the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCastTargetActorId)]
public uint CurrentCastTargetActorId;
/// <summary>
/// The current casting time of the spell being cast by the actor.
/// </summary>
[FieldOffset(ActorOffsets.CurrentCastTime)]
public float CurrentCastTime;
/// <summary>
/// The total casting time of the spell being cast by the actor.
/// </summary>
[FieldOffset(ActorOffsets.TotalCastTime)]
public float TotalCastTime;
/// <summary>
/// The array of status effects that the actor is currently affected by.
/// </summary>
[FieldOffset(ActorOffsets.UIStatusEffects)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
public StatusEffect[] UIStatusEffects;
}
/// <summary>
/// Memory offsets for the <see cref="Actor"/> type.
/// </summary>
public static class ActorOffsets
{
// Reference https://github.com/FFXIVAPP/sharlayan-resources/blob/master/structures/5.4/x64.json for more
public const int Name = 48; // 0x0030
@ -48,51 +286,4 @@ namespace Dalamud.Game.ClientState.Structs
public const int TotalCastTime = 0x1BB8;
public const int UIStatusEffects = 0x19F8;
}
/// <summary>
/// Native memory representation of a FFXIV actor.
/// </summary>
[StructLayout(LayoutKind.Explicit, Pack = 2)]
public struct Actor
{
[FieldOffset(ActorOffsets.Name)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)] public string Name;
[FieldOffset(ActorOffsets.ActorId)] public int ActorId;
[FieldOffset(ActorOffsets.DataId)] public int DataId;
[FieldOffset(ActorOffsets.OwnerId)] public int OwnerId;
[FieldOffset(ActorOffsets.ObjectKind)] public ObjectKind ObjectKind;
[FieldOffset(ActorOffsets.SubKind)] public byte SubKind;
[FieldOffset(ActorOffsets.IsFriendly)] public bool IsFriendly;
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerX)] public byte YalmDistanceFromPlayerX; // Demo says one of these is x distance
[FieldOffset(ActorOffsets.PlayerTargetStatus)] public byte PlayerTargetStatus; // This is some kind of enum
[FieldOffset(ActorOffsets.YalmDistanceFromPlayerY)] public byte YalmDistanceFromPlayerY; // and the other is z distance
[FieldOffset(ActorOffsets.Position)] public Position3 Position;
[FieldOffset(ActorOffsets.Rotation)] public float Rotation; // Rotation around the vertical axis (yaw), from -pi to pi radians
[FieldOffset(ActorOffsets.HitboxRadius)] public float HitboxRadius;
[FieldOffset(ActorOffsets.CurrentHp)] public int CurrentHp;
[FieldOffset(ActorOffsets.MaxHp)] public int MaxHp;
[FieldOffset(ActorOffsets.CurrentMp)] public int CurrentMp;
[FieldOffset(ActorOffsets.MaxMp)] public short MaxMp;
[FieldOffset(ActorOffsets.CurrentGp)] public short CurrentGp;
[FieldOffset(ActorOffsets.MaxGp)] public short MaxGp;
[FieldOffset(ActorOffsets.CurrentCp)] public short CurrentCp;
[FieldOffset(ActorOffsets.MaxCp)] public short MaxCp;
[FieldOffset(ActorOffsets.ClassJob)] public byte ClassJob;
[FieldOffset(ActorOffsets.Level)] public byte Level;
[FieldOffset(ActorOffsets.PlayerCharacterTargetActorId)] public int PlayerCharacterTargetActorId;
[FieldOffset(ActorOffsets.Customize)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)] public byte[] Customize;
// Normally pack=2 should work, but ByTVal or Injection breaks this.
// [FieldOffset(ActorOffsets.CompanyTag)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public string CompanyTag;
[FieldOffset(ActorOffsets.BattleNpcTargetActorId)] public int BattleNpcTargetActorId;
[FieldOffset(ActorOffsets.NameId)] public int NameId;
[FieldOffset(ActorOffsets.CurrentWorld)] public ushort CurrentWorld;
[FieldOffset(ActorOffsets.HomeWorld)] public ushort HomeWorld;
[FieldOffset(ActorOffsets.IsCasting)] public bool IsCasting;
[FieldOffset(ActorOffsets.IsCasting2)] public bool IsCasting2;
[FieldOffset(ActorOffsets.CurrentCastSpellActionId)] public uint CurrentCastSpellActionId;
[FieldOffset(ActorOffsets.CurrentCastTargetActorId)] public uint CurrentCastTargetActorId;
[FieldOffset(ActorOffsets.CurrentCastTime)] public float CurrentCastTime;
[FieldOffset(ActorOffsets.TotalCastTime)] public float TotalCastTime;
[FieldOffset(ActorOffsets.UIStatusEffects)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public StatusEffect[] UIStatusEffects;
}
}

View file

@ -1,5 +1,7 @@
using System;
#pragma warning disable SA1402 // File may only contain a single type
namespace Dalamud.Game.ClientState.Structs.JobGauge
{
#region AST
@ -270,3 +272,5 @@ namespace Dalamud.Game.ClientState.Structs.JobGauge
#endregion
}
#pragma warning restore SA1402 // File may only contain a single type

View file

@ -1,19 +1,26 @@
using Dalamud.Game.ClientState.Actors;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Game.ClientState.Actors;
namespace Dalamud.Game.ClientState.Structs
{
/// <summary>
/// This represents a native PartyMember class in memory.
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct PartyMember
{
[FieldOffset(0x0)] public IntPtr namePtr;
[FieldOffset(0x8)] public long unknown;
[FieldOffset(0x10)] public int actorId;
[FieldOffset(0x14)] public ObjectKind objectKind;
[FieldOffset(0x0)]
public IntPtr namePtr;
[FieldOffset(0x8)]
public long unknown;
[FieldOffset(0x10)]
public int actorId;
[FieldOffset(0x14)]
public ObjectKind objectKind;
}
}

View file

@ -1,4 +1,3 @@
using System;
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs
@ -9,10 +8,29 @@ namespace Dalamud.Game.ClientState.Structs
[StructLayout(LayoutKind.Sequential)]
public struct StatusEffect
{
/// <summary>
/// The effect ID.
/// </summary>
public short EffectId;
/// <summary>
/// How many stacks are present.
/// </summary>
public byte StackCount;
/// <summary>
/// Additional parameters.
/// </summary>
public byte Param;
/// <summary>
/// The duration remaining.
/// </summary>
public float Duration;
/// <summary>
/// The ID of the actor that caused this effect.
/// </summary>
public int OwnerId;
}
}