Removed unsafe handling of game object and owned object

This commit is contained in:
cake
2026-01-16 15:54:38 +01:00
parent f6a5c85c2d
commit d8b4122ec3
4 changed files with 291 additions and 202 deletions

View File

@@ -1,18 +1,17 @@
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Dalamud.Game.ClientState.Objects.Types;
using LightlessSync.Services;
using LightlessSync.Services.Mediator;
using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices;
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Plugin.Services;
namespace LightlessSync.PlayerData.Handlers;
public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighPriorityMediatorSubscriber
{
private readonly DalamudUtilService _dalamudUtil;
private readonly IObjectTable _objectTable;
private readonly Func<IntPtr> _getAddress;
private readonly bool _isOwnedObject;
private readonly PerformanceCollectorService _performanceCollector;
@@ -24,7 +23,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
private CancellationTokenSource _zoningCts = new();
public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector,
LightlessMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, bool ownedObject = true) : base(logger, mediator)
LightlessMediator mediator, DalamudUtilService dalamudUtil, ObjectKind objectKind, Func<IntPtr> getAddress, IObjectTable objectTable, bool ownedObject = true) : base(logger, mediator)
{
_performanceCollector = performanceCollector;
ObjectKind = objectKind;
@@ -82,6 +81,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
});
Mediator.Publish(new GameObjectHandlerCreatedMessage(this, _isOwnedObject));
_objectTable = objectTable;
_dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult();
}
@@ -110,14 +110,14 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
private ushort[] MainHandData { get; set; } = new ushort[3];
private ushort[] OffHandData { get; set; } = new ushort[3];
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<Dalamud.Game.ClientState.Objects.Types.ICharacter> act, CancellationToken token)
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<ICharacter> act, CancellationToken token)
{
while (await _dalamudUtil.RunOnFrameworkThread(() =>
{
EnsureLatestObjectState();
if (CurrentDrawCondition != DrawCondition.None) return true;
var gameObj = _dalamudUtil.CreateGameObject(Address);
if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara)
if (gameObj is ICharacter chara)
{
act.Invoke(chara);
}
@@ -140,7 +140,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
}
}
public Dalamud.Game.ClientState.Objects.Types.IGameObject? GetGameObject()
public IGameObject? GetGameObject()
{
return _dalamudUtil.CreateGameObject(Address);
}
@@ -169,9 +169,22 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
return $"{owned}/{ObjectKind}:{Name} ({Address:X},{DrawObjectAddress:X})";
}
private IGameObject? TryGetObjectByAddress(nint address)
{
if (address == nint.Zero) return null;
foreach (var obj in _objectTable)
{
if (obj is null) continue;
if (obj.Address == address)
return obj;
}
return null;
}
private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true);
private unsafe void CheckAndUpdateObject(bool allowPublish)
private void CheckAndUpdateObject(bool allowPublish)
{
var prevAddr = Address;
var prevDrawObj = DrawObjectAddress;
@@ -179,127 +192,118 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
Address = _getAddress();
if (Address != IntPtr.Zero)
{
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address;
DrawObjectAddress = (IntPtr)gameObject->DrawObject;
EntityId = gameObject->EntityId;
IGameObject? obj = null;
ICharacter? chara = null;
var chara = (Character*)Address;
nameString = chara->GameObject.NameString;
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
Name = nameString;
if (Address != nint.Zero)
{
obj = TryGetObjectByAddress(Address);
if (obj is not null)
{
EntityId = obj.EntityId;
DrawObjectAddress = Address;
nameString = obj.Name.TextValue ?? string.Empty;
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
Name = nameString;
chara = obj as ICharacter;
}
else
{
DrawObjectAddress = nint.Zero;
EntityId = uint.MaxValue;
}
}
else
{
DrawObjectAddress = IntPtr.Zero;
DrawObjectAddress = nint.Zero;
EntityId = uint.MaxValue;
}
CurrentDrawCondition = IsBeingDrawnUnsafe();
CurrentDrawCondition = IsBeingDrawnSafe(obj, chara);
if (_haltProcessing || !allowPublish) return;
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
bool addrDiff = Address != prevAddr;
if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
bool nameChange = false;
if (nameString is not null)
{
var chara = (Character*)Address;
var drawObj = (DrawObject*)DrawObjectAddress;
var objType = drawObj->Object.GetObjectType();
var isHuman = objType == ObjectType.CharacterBase
&& ((CharacterBase*)drawObj)->GetModelType() == CharacterBase.ModelType.Human;
nameString ??= ((Character*)Address)->GameObject.NameString;
var nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
if (nameChange) Name = nameString;
}
bool equipDiff = false;
bool customizeDiff = false;
if (isHuman)
if (chara is not null)
{
var classJob = chara.ClassJob.RowId;
if (classJob != _classJob)
{
var classJob = chara->CharacterData.ClassJob;
if (classJob != _classJob)
{
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
_classJob = classJob;
Mediator.Publish(new ClassJobChangedMessage(this));
}
equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)drawObj)->Head);
ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand);
ref var oh = ref chara->DrawData.Weapon(WeaponSlot.OffHand);
equipDiff |= CompareAndUpdateMainHand((Weapon*)mh.DrawObject);
equipDiff |= CompareAndUpdateOffHand((Weapon*)oh.DrawObject);
if (equipDiff)
Logger.LogTrace("Checking [{this}] equip data as human from draw obj, result: {diff}", this, equipDiff);
}
else
{
equipDiff = CompareAndUpdateEquipByteData((byte*)Unsafe.AsPointer(ref chara->DrawData.EquipmentModelIds[0]));
if (equipDiff)
Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff);
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
_classJob = (byte)classJob;
Mediator.Publish(new ClassJobChangedMessage(this));
}
if (equipDiff && !_isOwnedObject) // send the message out immediately and cancel out, no reason to continue if not self
customizeDiff = CompareAndUpdateCustomizeData(chara.Customize);
if (_isOwnedObject && ObjectKind == ObjectKind.Player && chara.Customize.Length > (int)CustomizeIndex.Tribe)
{
Logger.LogTrace("[{this}] Changed", this);
return;
}
var gender = chara.Customize[(int)CustomizeIndex.Gender];
var raceId = chara.Customize[(int)CustomizeIndex.Race];
var tribeId = chara.Customize[(int)CustomizeIndex.Tribe];
bool customizeDiff = false;
if (isHuman)
{
var gender = ((Human*)drawObj)->Customize.Sex;
var raceId = ((Human*)drawObj)->Customize.Race;
var tribeId = ((Human*)drawObj)->Customize.Tribe;
if (_isOwnedObject && ObjectKind == ObjectKind.Player
&& (gender != Gender || raceId != RaceId || tribeId != TribeId))
if (gender != Gender || raceId != RaceId || tribeId != TribeId)
{
Mediator.Publish(new CensusUpdateMessage(gender, raceId, tribeId));
Gender = gender;
RaceId = raceId;
TribeId = tribeId;
}
}
}
customizeDiff = CompareAndUpdateCustomizeData(((Human*)drawObj)->Customize.Data);
if (customizeDiff)
Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff);
}
else
{
customizeDiff = CompareAndUpdateCustomizeData(chara->DrawData.CustomizeData.Data);
if (customizeDiff)
Logger.LogTrace("Checking [{this}] customize data from game obj, result: {diff}", this, equipDiff);
}
if ((addrDiff || drawObjDiff || equipDiff || customizeDiff || nameChange) && _isOwnedObject)
{
Logger.LogDebug("[{this}] Changed, Sending CreateCacheObjectMessage", this);
Mediator.Publish(new CreateCacheForObjectMessage(this));
}
if ((addrDiff || drawObjDiff || customizeDiff || nameChange) && _isOwnedObject)
{
Logger.LogDebug("[{this}] Changed, Sending CreateCacheObjectMessage", this);
Mediator.Publish(new CreateCacheForObjectMessage(this));
}
else if (addrDiff || drawObjDiff)
{
CurrentDrawCondition = DrawCondition.DrawObjectZero;
if (Address == nint.Zero)
CurrentDrawCondition = DrawCondition.ObjectZero;
else if (DrawObjectAddress == nint.Zero)
CurrentDrawCondition = DrawCondition.DrawObjectZero;
Logger.LogTrace("[{this}] Changed", this);
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
{
Mediator.Publish(new ClearCacheForObjectMessage(this));
}
}
}
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
private DrawCondition IsBeingDrawnSafe(IGameObject? obj, ICharacter? chara)
{
if (Address == nint.Zero) return DrawCondition.ObjectZero;
if (obj is null) return DrawCondition.DrawObjectZero;
if (chara is not null && (chara.Customize is null || chara.Customize.Length == 0))
return DrawCondition.DrawObjectZero;
return DrawCondition.None;
}
private bool CompareAndUpdateCustomizeData(ReadOnlySpan<byte> customizeData)
{
bool hasChanges = false;
for (int i = 0; i < customizeData.Length; i++)
var len = Math.Min(customizeData.Length, CustomizeData.Length);
for (int i = 0; i < len; i++)
{
var data = customizeData[i];
if (CustomizeData[i] != data)
@@ -312,48 +316,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
return hasChanges;
}
private unsafe bool CompareAndUpdateEquipByteData(byte* equipSlotData)
{
bool hasChanges = false;
for (int i = 0; i < EquipSlotData.Length; i++)
{
var data = equipSlotData[i];
if (EquipSlotData[i] != data)
{
EquipSlotData[i] = data;
hasChanges = true;
}
}
return hasChanges;
}
private unsafe bool CompareAndUpdateMainHand(Weapon* weapon)
{
if ((nint)weapon == nint.Zero) return false;
bool hasChanges = false;
hasChanges |= weapon->ModelSetId != MainHandData[0];
MainHandData[0] = weapon->ModelSetId;
hasChanges |= weapon->Variant != MainHandData[1];
MainHandData[1] = weapon->Variant;
hasChanges |= weapon->SecondaryId != MainHandData[2];
MainHandData[2] = weapon->SecondaryId;
return hasChanges;
}
private unsafe bool CompareAndUpdateOffHand(Weapon* weapon)
{
if ((nint)weapon == nint.Zero) return false;
bool hasChanges = false;
hasChanges |= weapon->ModelSetId != OffHandData[0];
OffHandData[0] = weapon->ModelSetId;
hasChanges |= weapon->Variant != OffHandData[1];
OffHandData[1] = weapon->Variant;
hasChanges |= weapon->SecondaryId != OffHandData[2];
OffHandData[2] = weapon->SecondaryId;
return hasChanges;
}
private void FrameworkUpdate()
{
try
@@ -403,24 +365,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
}
}
private unsafe DrawCondition IsBeingDrawnUnsafe()
{
if (Address == IntPtr.Zero) return DrawCondition.ObjectZero;
if (DrawObjectAddress == IntPtr.Zero) return DrawCondition.DrawObjectZero;
var visibilityFlags = ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->RenderFlags;
if (visibilityFlags != VisibilityFlags.None) return DrawCondition.RenderFlags;
if (ObjectKind == ObjectKind.Player)
{
var modelInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelInSlotLoaded != 0);
if (modelInSlotLoaded) return DrawCondition.ModelInSlotLoaded;
var modelFilesInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelFilesInSlotLoaded != 0);
if (modelFilesInSlotLoaded) return DrawCondition.ModelFilesInSlotLoaded;
}
return DrawCondition.None;
}
private void ZoneSwitchEnd()
{
if (!_isOwnedObject) return;