MEOW MEOW MEOW

This commit is contained in:
2026-01-06 11:57:01 +09:00
parent 9b9010ab8e
commit 0e24da75d5
3 changed files with 176 additions and 194 deletions

View File

@@ -93,6 +93,7 @@ public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorS
}
RefreshTrackedActors(force: true);
});
_mediator.Subscribe<DalamudLogoutMessage>(this, _ => ClearTrackingState());
}
private bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
@@ -342,18 +343,8 @@ public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorS
public Task StopAsync(CancellationToken cancellationToken)
{
DisposeHooks();
_activePlayers.Clear();
_gposePlayers.Clear();
_actorsByHash.Clear();
_actorsByName.Clear();
_pendingHashResolutions.Clear();
ClearTrackingState();
_mediator.UnsubscribeAll(this);
lock (_playerRelatedHandlerLock)
{
_playerRelatedHandlers.Clear();
}
Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty);
return Task.CompletedTask;
}
@@ -1077,6 +1068,22 @@ public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorS
}
}
private void ClearTrackingState()
{
_activePlayers.Clear();
_gposePlayers.Clear();
_actorsByHash.Clear();
_actorsByName.Clear();
_pendingHashResolutions.Clear();
lock (_playerRelatedHandlerLock)
{
_playerRelatedHandlers.Clear();
}
Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty);
_nextRefreshAllowed = DateTime.MinValue;
}
public void Dispose()
{
DisposeHooks();

View File

@@ -22,10 +22,8 @@ using LightlessSync.Utils;
using Lumina.Excel.Sheets;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using BattleNpcSubKind = FFXIVClientStructs.FFXIV.Client.Game.Object.BattleNpcSubKind;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
@@ -229,6 +227,28 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
_ = RunOnFrameworkThread(ReleaseFocusUnsafe);
}
public void TargetPlayerByAddress(nint address)
{
if (address == nint.Zero) return;
if (_clientState.IsPvP) return;
_ = RunOnFrameworkThread(() =>
{
var gameObject = CreateGameObject(address);
if (gameObject is null) return;
var useFocusTarget = _configService.Current.UseFocusTarget;
if (useFocusTarget)
{
_targetManager.FocusTarget = gameObject;
}
else
{
_targetManager.Target = gameObject;
}
});
}
private void FocusPairUnsafe(nint address, PairUniqueIdentifier pairIdent)
{
var target = CreateGameObject(address);
@@ -634,6 +654,37 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
return true;
}
public bool TryGetHashedCIDFromAddress(nint address, out string hashedCid)
{
hashedCid = string.Empty;
if (address == nint.Zero)
return false;
if (_framework.IsInFrameworkUpdateThread)
{
return TryGetHashedCIDFromAddressInternal(address, out hashedCid);
}
var result = _framework.RunOnFrameworkThread(() =>
{
var success = TryGetHashedCIDFromAddressInternal(address, out var resolved);
return (success, resolved);
}).GetAwaiter().GetResult();
hashedCid = result.resolved;
return result.success;
}
private bool TryGetHashedCIDFromAddressInternal(nint address, out string hashedCid)
{
hashedCid = string.Empty;
var player = _objectTable.CreateObjectReference(address) as IPlayerCharacter;
if (player == null || player.Address != address)
return false;
return TryGetHashedCID(player, out hashedCid);
}
public unsafe static string GetHashedCIDFromPlayerPointer(nint ptr)
{
return ((BattleChara*)ptr)->Character.ContentId.ToString().GetHash256();
@@ -845,43 +896,33 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
return Task.CompletedTask;
}
public async Task WaitWhileCharacterIsDrawing(
ILogger logger,
GameObjectHandler handler,
Guid redrawId,
int timeOut = 5000,
CancellationToken? ct = null)
public async Task WaitWhileCharacterIsDrawing(ILogger logger, GameObjectHandler handler, Guid redrawId, int timeOut = 5000, CancellationToken? ct = null)
{
if (!_clientState.IsLoggedIn) return;
var token = ct ?? CancellationToken.None;
if (ct == null)
ct = CancellationToken.None;
const int tick = 250;
const int initialSettle = 50;
var sw = Stopwatch.StartNew();
int curWaitTime = 0;
try
{
logger.LogTrace("[{redrawId}] Starting wait for {handler} to draw", redrawId, handler);
await Task.Delay(tick, ct.Value).ConfigureAwait(true);
curWaitTime += tick;
await Task.Delay(initialSettle, token).ConfigureAwait(false);
while (!token.IsCancellationRequested
&& sw.ElapsedMilliseconds < timeOut
&& await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false))
while ((!ct.Value.IsCancellationRequested)
&& curWaitTime < timeOut
&& await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something
{
logger.LogTrace("[{redrawId}] Waiting for {handler} to finish drawing", redrawId, handler);
await Task.Delay(tick, token).ConfigureAwait(false);
curWaitTime += tick;
await Task.Delay(tick, ct.Value).ConfigureAwait(true);
}
logger.LogTrace("[{redrawId}] Finished drawing after {ms}ms", redrawId, sw.ElapsedMilliseconds);
logger.LogTrace("[{redrawId}] Finished drawing after {curWaitTime}ms", redrawId, curWaitTime);
}
catch (OperationCanceledException)
{
// ignore
}
catch (Exception ex)
catch (AccessViolationException ex)
{
logger.LogWarning(ex, "Error accessing {handler}, object does not exist anymore?", handler);
}
@@ -931,109 +972,37 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null;
}
public void TargetPlayerByAddress(nint address)
{
if (address == nint.Zero) return;
if (_clientState.IsPvP) return;
_ = RunOnFrameworkThread(() =>
{
var gameObject = CreateGameObject(address);
if (gameObject is null) return;
var useFocusTarget = _configService.Current.UseFocusTarget;
if (useFocusTarget)
{
_targetManager.FocusTarget = gameObject;
}
else
{
_targetManager.Target = gameObject;
}
});
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool IsBadReadPtr(IntPtr ptr, UIntPtr size);
private static bool IsValidPointer(nint ptr, int size = 8)
{
if (ptr == nint.Zero)
return false;
try
{
if (!Util.IsWine())
{
return !IsBadReadPtr(ptr, (UIntPtr)size);
}
return ptr != nint.Zero && (ptr % IntPtr.Size) == 0;
}
catch
{
return false;
}
}
private unsafe void CheckCharacterForDrawing(nint address, string characterName)
{
if (address == nint.Zero)
return;
if (!IsValidPointer(address))
{
_logger.LogDebug("Invalid pointer for character {name} at {addr}", characterName, address.ToString("X"));
return;
}
var gameObj = (GameObject*)address;
if (gameObj == null)
return;
if (!_objectTable.Any(o => o?.Address == address))
{
_logger.LogDebug("Character {name} at {addr} no longer in object table", characterName, address.ToString("X"));
return;
}
if (gameObj->ObjectKind == 0)
return;
var drawObj = gameObj->DrawObject;
bool isDrawing = false;
bool isDrawingChanged = false;
if ((nint)drawObj != IntPtr.Zero && IsValidPointer((nint)drawObj))
if ((nint)drawObj != IntPtr.Zero)
{
isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000;
if (!isDrawing)
{
var charBase = (CharacterBase*)drawObj;
if (charBase != null && IsValidPointer((nint)charBase))
isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0;
if (!isDrawing)
{
isDrawing = charBase->HasModelInSlotLoaded != 0;
if (!isDrawing)
isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0;
if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
&& !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal))
{
isDrawing = charBase->HasModelFilesInSlotLoaded != 0;
if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
&& !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal))
{
_lastGlobalBlockPlayer = characterName;
_lastGlobalBlockReason = "HasModelFilesInSlotLoaded";
isDrawingChanged = true;
}
_lastGlobalBlockPlayer = characterName;
_lastGlobalBlockReason = "HasModelFilesInSlotLoaded";
isDrawingChanged = true;
}
else
}
else
{
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
&& !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal))
{
if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal)
&& !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal))
{
_lastGlobalBlockPlayer = characterName;
_lastGlobalBlockReason = "HasModelInSlotLoaded";
isDrawingChanged = true;
}
_lastGlobalBlockPlayer = characterName;
_lastGlobalBlockReason = "HasModelInSlotLoaded";
isDrawingChanged = true;
}
}
}
@@ -1064,12 +1033,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
private unsafe void FrameworkOnUpdateInternal()
{
if (!_clientState.IsLoggedIn || _objectTable.LocalPlayer == null)
{
return;
}
if ((_objectTable.LocalPlayer?.IsDead ?? false) && _condition[ConditionFlag.BoundByDuty])
var localPlayer = _objectTable.LocalPlayer;
if ((localPlayer?.IsDead ?? false) && _condition[ConditionFlag.BoundByDuty])
{
return;
}
@@ -1079,6 +1044,44 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
_performanceCollector.LogPerformance(this, $"FrameworkOnUpdateInternal+{(isNormalFrameworkUpdate ? "Regular" : "Delayed")}", () =>
{
IsAnythingDrawing = false;
if (!isNormalFrameworkUpdate)
{
if (localPlayer != null && !IsLoggedIn)
{
_logger.LogDebug("Logged in");
IsLoggedIn = true;
_lastZone = _clientState.TerritoryType;
_lastWorldId = (ushort)localPlayer.CurrentWorld.RowId;
_cid = RebuildCID();
Mediator.Publish(new DalamudLoginMessage());
}
else if (localPlayer == null && IsLoggedIn)
{
_logger.LogDebug("Logged out");
IsLoggedIn = false;
_lastWorldId = 0;
Mediator.Publish(new DalamudLogoutMessage());
}
if (_gameConfig != null
&& _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled))
{
IsLodEnabled = lodEnabled;
}
if (IsInCombat || IsPerforming || IsInInstance)
Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new DelayedFrameworkUpdateMessage());
_delayedFrameworkUpdateCheck = DateTime.UtcNow;
}
if (!_clientState.IsLoggedIn || localPlayer == null)
{
return;
}
_performanceCollector.LogPerformance(this, $"TrackedActorsToState",
() =>
{
@@ -1087,40 +1090,46 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
_actorObjectService.RefreshTrackedActors();
}
var playerDescriptors = _actorObjectService.PlayerDescriptors;
var descriptorCount = playerDescriptors.Count;
for (var i = 0; i < descriptorCount; i++)
if (_clientState.IsLoggedIn && localPlayer != null)
{
if (i >= playerDescriptors.Count)
break;
var actor = playerDescriptors[i];
var playerAddress = actor.Address;
if (playerAddress == nint.Zero || !IsValidPointer(playerAddress))
continue;
if (actor.ObjectIndex >= 200)
continue;
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime)
var playerDescriptors = _actorObjectService.PlayerDescriptors;
for (var i = 0; i < playerDescriptors.Count; i++)
{
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
continue;
}
var actor = playerDescriptors[i];
if (!IsAnythingDrawing)
{
if (!_objectTable.Any(o => o?.Address == playerAddress))
var playerAddress = actor.Address;
if (playerAddress == nint.Zero)
continue;
if (actor.ObjectIndex >= 200)
continue;
var obj = _objectTable[actor.ObjectIndex];
if (obj is not IPlayerCharacter player || player.Address != playerAddress)
continue;
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime)
{
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
continue;
}
CheckCharacterForDrawing(playerAddress, actor.Name);
if (!IsAnythingDrawing)
{
var charaName = player.Name.TextValue;
if (string.IsNullOrEmpty(charaName))
{
charaName = actor.Name;
}
if (IsAnythingDrawing)
CheckCharacterForDrawing(playerAddress, charaName);
if (IsAnythingDrawing)
break;
}
else
{
break;
}
}
}
});
@@ -1246,7 +1255,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
}
var localPlayer = _objectTable.LocalPlayer;
if (localPlayer != null)
{
_classJobId = localPlayer.ClassJob.RowId;
@@ -1268,39 +1276,6 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new PriorityFrameworkUpdateMessage());
if (isNormalFrameworkUpdate)
return;
if (localPlayer != null && !IsLoggedIn)
{
_logger.LogDebug("Logged in");
IsLoggedIn = true;
_lastZone = _clientState.TerritoryType;
_lastWorldId = (ushort)localPlayer.CurrentWorld.RowId;
_cid = RebuildCID();
Mediator.Publish(new DalamudLoginMessage());
}
else if (localPlayer == null && IsLoggedIn)
{
_logger.LogDebug("Logged out");
IsLoggedIn = false;
_lastWorldId = 0;
Mediator.Publish(new DalamudLogoutMessage());
}
if (_gameConfig != null
&& _gameConfig.TryGet(Dalamud.Game.Config.SystemConfigOption.LodType_DX11, out bool lodEnabled))
{
IsLodEnabled = lodEnabled;
}
if (IsInCombat || IsPerforming || IsInInstance)
Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new DelayedFrameworkUpdateMessage());
_delayedFrameworkUpdateCheck = DateTime.UtcNow;
});
}
@@ -1330,4 +1305,4 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
onExit();
}
}
}
}