diff --git a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs index 7b76953..5e1d99e 100644 --- a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs +++ b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs @@ -1,4 +1,5 @@ -using FFXIVClientStructs.FFXIV.Client.Game.Character; +using Dalamud.Utility; +using FFXIVClientStructs.FFXIV.Client.Game.Character; using LightlessSync.API.Data.Enum; using LightlessSync.FileCache; using LightlessSync.Interop.Ipc; @@ -11,6 +12,8 @@ using LightlessSync.Services.Mediator; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.Diagnostics; +using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; namespace LightlessSync.PlayerData.Factories; @@ -123,22 +126,38 @@ public class PlayerDataFactory { if (playerPointer == IntPtr.Zero) return true; + + if (!IsPointerValid(playerPointer)) + return true; + + var character = (Character*)playerPointer; + if (character == null) + return true; + + var gameObject = &character->GameObject; + if (gameObject == null) + return true; + + if (!IsPointerValid((IntPtr)gameObject)) + return true; + + return gameObject->DrawObject == null; + } + + private static bool IsPointerValid(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + return false; + try { - var character = (Character*)playerPointer; - if (character == null) - return true; - - var gameObject = &character->GameObject; - if (gameObject == null) - return true; - - return gameObject->DrawObject == null; - } - catch (AccessViolationException) - { + _ = Marshal.ReadByte(ptr); return true; } + catch + { + return false; + } } private static bool IsCacheFresh(CacheEntry entry) @@ -537,6 +556,11 @@ public class PlayerDataFactory var hash = g.Key; + var resolvedPath = g.Select(f => f.ResolvedPath).Distinct(StringComparer.OrdinalIgnoreCase); + var papPathSummary = string.Join(", ", resolvedPath); + if (papPathSummary.IsNullOrEmpty()) + papPathSummary = ""; + Dictionary>? papIndices = null; await _papParseLimiter.WaitAsync(ct).ConfigureAwait(false); @@ -588,8 +612,8 @@ public class PlayerDataFactory noValidationFailed++; _logger.LogWarning( - "Animation PAP hash {hash} is not compatible with local skeletons; dropping all mappings for this hash. Reason: {reason}", - hash, + "Animation PAP is not compatible with local skeletons; dropping mappings for {papPath}. Reason: {reason}", + papPathSummary, reason); var removedGamePaths = fragment.FileReplacements diff --git a/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs b/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs index f3d1a4b..28b67b6 100644 --- a/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs +++ b/LightlessSync/PlayerData/Handlers/GameObjectHandler.cs @@ -78,7 +78,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP if (msg.Address == Address) { _haltProcessing = false; - Refresh(); } }); @@ -114,16 +113,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action 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) - { - act.Invoke(chara); - } - return false; - }).ConfigureAwait(false)) + { + EnsureLatestObjectState(); + if (CurrentDrawCondition != DrawCondition.None) return true; + var gameObj = _dalamudUtil.CreateGameObject(Address); + if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara) + { + act.Invoke(chara); + } + return false; + }).ConfigureAwait(false)) { await Task.Delay(250, token).ConfigureAwait(false); } @@ -170,19 +169,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP return $"{owned}/{ObjectKind}:{Name} ({Address:X},{DrawObjectAddress:X})"; } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - Mediator.Publish(new GameObjectHandlerDestroyedMessage(this, _isOwnedObject)); - } - private void CheckAndUpdateObject() => CheckAndUpdateObject(allowPublish: true); - private unsafe void CheckAndUpdateObject(bool allowPublish = true) + private unsafe void CheckAndUpdateObject(bool allowPublish) { var prevAddr = Address; var prevDrawObj = DrawObjectAddress; + string? nameString = null; Address = _getAddress(); @@ -193,10 +186,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP EntityId = gameObject->EntityId; var chara = (Character*)Address; - var newName = chara->GameObject.NameString; - - if (!string.IsNullOrEmpty(newName) && !string.Equals(newName, Name, StringComparison.Ordinal)) - Name = newName; + nameString = chara->GameObject.NameString; + if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal)) + Name = nameString; } else { @@ -214,16 +206,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero) { var chara = (Character*)Address; - var name = chara->GameObject.NameString; - bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal); - if (nameChange) - { - Name = name; - } + 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); + if (nameChange) Name = nameString; + bool equipDiff = false; - if (((DrawObject*)DrawObjectAddress)->Object.GetObjectType() == ObjectType.CharacterBase - && ((CharacterBase*)DrawObjectAddress)->GetModelType() == CharacterBase.ModelType.Human) + if (isHuman) { var classJob = chara->CharacterData.ClassJob; if (classJob != _classJob) @@ -233,7 +227,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP Mediator.Publish(new ClassJobChangedMessage(this)); } - equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)DrawObjectAddress)->Head); + equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)drawObj)->Head); ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand); ref var oh = ref chara->DrawData.Weapon(WeaponSlot.OffHand); @@ -258,12 +252,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP bool customizeDiff = false; - if (((DrawObject*)DrawObjectAddress)->Object.GetObjectType() == ObjectType.CharacterBase - && ((CharacterBase*)DrawObjectAddress)->GetModelType() == CharacterBase.ModelType.Human) + if (isHuman) { - var gender = ((Human*)DrawObjectAddress)->Customize.Sex; - var raceId = ((Human*)DrawObjectAddress)->Customize.Race; - var tribeId = ((Human*)DrawObjectAddress)->Customize.Tribe; + 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)) @@ -274,7 +267,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP TribeId = tribeId; } - customizeDiff = CompareAndUpdateCustomizeData(((Human*)DrawObjectAddress)->Customize.Data); + customizeDiff = CompareAndUpdateCustomizeData(((Human*)drawObj)->Customize.Data); if (customizeDiff) Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff); } diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index 9ae2a39..5da96bb 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -666,7 +666,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber var location = new LocationInfo(); location.ServerId = _playerState.CurrentWorld.RowId; - location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; + location.InstanceId = UIState.Instance()->PublicInstance.InstanceId; location.TerritoryId = _clientState.TerritoryType; location.MapId = _clientState.MapId; if (houseMan != null) @@ -699,13 +699,13 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber } return location; } - + public string LocationToString(LocationInfo location) { if (location.ServerId is 0 || location.TerritoryId is 0) return String.Empty; var str = WorldData.Value[(ushort)location.ServerId]; - if (ContentFinderData.Value.TryGetValue(location.TerritoryId , out var dutyName)) + if (ContentFinderData.Value.TryGetValue(location.TerritoryId, out var dutyName)) { str += $" - [In Duty]{dutyName}"; } @@ -856,7 +856,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber var token = ct ?? CancellationToken.None; - const int tick = 250; + const int tick = 250; const int initialSettle = 50; var sw = Stopwatch.StartNew(); @@ -881,7 +881,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber { // ignore } - catch (AccessViolationException ex) + catch (Exception ex) { logger.LogWarning(ex, "Error accessing {handler}, object does not exist anymore?", handler); } @@ -922,11 +922,11 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber public string? GetWorldNameFromPlayerAddress(nint address) { if (address == nint.Zero) return null; - + EnsureIsOnFramework(); var playerCharacter = _objectTable.OfType().FirstOrDefault(p => p.Address == address); if (playerCharacter == null) return null; - + var worldId = (ushort)playerCharacter.HomeWorld.RowId; return WorldData.Value.TryGetValue(worldId, out var worldName) ? worldName : null; } @@ -953,105 +953,108 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber }); } + [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; - try + if (!IsValidPointer(address)) { - var gameObj = (GameObject*)address; + _logger.LogDebug("Invalid pointer for character {name} at {addr}", characterName, address.ToString("X")); + return; + } - if (gameObj == null) - return; + var gameObj = (GameObject*)address; - if (!_objectTable.Any(o => o?.Address == 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)) + { + isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000; + + if (!isDrawing) { - _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) - { - try + var charBase = (CharacterBase*)drawObj; + if (charBase != null && IsValidPointer((nint)charBase)) { - isDrawing = gameObj->RenderFlags == (VisibilityFlags)0b100000000000; - } - catch (AccessViolationException) - { - return; - } - - if (!isDrawing) - { - try + isDrawing = charBase->HasModelInSlotLoaded != 0; + if (!isDrawing) { - var charBase = (CharacterBase*)drawObj; - if (charBase != null) + isDrawing = charBase->HasModelFilesInSlotLoaded != 0; + if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) { - isDrawing = charBase->HasModelInSlotLoaded != 0; - if (!isDrawing) - { - isDrawing = charBase->HasModelFilesInSlotLoaded != 0; - if (isDrawing && !string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "HasModelFilesInSlotLoaded", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = characterName; - _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; - isDrawingChanged = true; - } - } - else - { - if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = characterName; - _lastGlobalBlockReason = "HasModelInSlotLoaded"; - isDrawingChanged = true; - } - } + _lastGlobalBlockPlayer = characterName; + _lastGlobalBlockReason = "HasModelFilesInSlotLoaded"; + isDrawingChanged = true; } } - catch (AccessViolationException) + else { - return; - } - } - else - { - if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) - && !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal)) - { - _lastGlobalBlockPlayer = characterName; - _lastGlobalBlockReason = "RenderFlags"; - isDrawingChanged = true; + if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "HasModelInSlotLoaded", StringComparison.Ordinal)) + { + _lastGlobalBlockPlayer = characterName; + _lastGlobalBlockReason = "HasModelInSlotLoaded"; + isDrawingChanged = true; + } } } } - - if (isDrawingChanged) + else { - _logger.LogTrace("Global draw block: START => {name} ({reason})", characterName, _lastGlobalBlockReason); + if (!string.Equals(_lastGlobalBlockPlayer, characterName, StringComparison.Ordinal) + && !string.Equals(_lastGlobalBlockReason, "RenderFlags", StringComparison.Ordinal)) + { + _lastGlobalBlockPlayer = characterName; + _lastGlobalBlockReason = "RenderFlags"; + isDrawingChanged = true; + } } + } - IsAnythingDrawing |= isDrawing; - } - catch (AccessViolationException ex) + if (isDrawingChanged) { - _logger.LogDebug(ex, "Memory access violation checking character {name} at {addr}", characterName, address.ToString("X")); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Unexpected error checking character {name} at {addr}", characterName, address.ToString("X")); + _logger.LogTrace("Global draw block: START => {name} ({reason})", characterName, _lastGlobalBlockReason); } + + IsAnythingDrawing |= isDrawing; } private void FrameworkOnUpdate(IFramework framework) @@ -1061,6 +1064,11 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber private unsafe void FrameworkOnUpdateInternal() { + if (!_clientState.IsLoggedIn || _objectTable.LocalPlayer == null) + { + return; + } + if ((_objectTable.LocalPlayer?.IsDead ?? false) && _condition[ConditionFlag.BoundByDuty]) { return; @@ -1084,70 +1092,38 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber for (var i = 0; i < descriptorCount; i++) { - try + 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) { - if (i >= playerDescriptors.Count) + _logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X")); + continue; + } + + if (!IsAnythingDrawing) + { + if (!_objectTable.Any(o => o?.Address == playerAddress)) + { + continue; + } + + CheckCharacterForDrawing(playerAddress, actor.Name); + + if (IsAnythingDrawing) break; - - var actor = playerDescriptors[i]; - - var playerAddress = actor.Address; - if (playerAddress == nint.Zero) - continue; - - if (actor.ObjectIndex >= 200) - continue; - - if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime) - { - _logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X")); - continue; - } - - if (!IsAnythingDrawing) - { - try - { - var gameObj = (GameObject*)playerAddress; - - if (gameObj == null || gameObj->ObjectKind == 0) - { - continue; - } - - string currentName; - try - { - currentName = gameObj->NameString ?? string.Empty; - } - catch (AccessViolationException) - { - currentName = string.Empty; - } - - var charaName = string.IsNullOrEmpty(currentName) ? actor.Name : currentName; - - CheckCharacterForDrawing(playerAddress, charaName); - - if (IsAnythingDrawing) - break; - } - catch (AccessViolationException ex) - { - _logger.LogDebug(ex, "Access violation on GameObject pointer for actor {index} at {addr}", i, playerAddress.ToString("X")); - } - } - } - catch (AccessViolationException ex) - { - _logger.LogDebug(ex, "Access violation processing actor {index} - object likely destroyed", i); - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Unexpected error processing actor {index}", i); } } - }); + }); if (!IsAnythingDrawing && !string.IsNullOrEmpty(_lastGlobalBlockPlayer)) { @@ -1214,7 +1190,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber }); // Cutscene - HandleStateTransition(() => IsInCutscene,v => IsInCutscene = v, shouldBeInCutscene, "Cutscene", + HandleStateTransition(() => IsInCutscene, v => IsInCutscene = v, shouldBeInCutscene, "Cutscene", onEnter: () => { Mediator.Publish(new CutsceneStartMessage()); @@ -1257,7 +1233,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new ZoneSwitchEndMessage()); Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas))); } - + //Map if (!_sentBetweenAreas) { @@ -1268,7 +1244,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber Mediator.Publish(new MapChangedMessage(mapid)); } } - + var localPlayer = _objectTable.LocalPlayer; if (localPlayer != null) @@ -1354,4 +1330,4 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber onExit(); } } -} +} \ No newline at end of file