diff --git a/LightlessSync/FileCache/CacheMonitor.cs b/LightlessSync/FileCache/CacheMonitor.cs index 83c3b96..fde9b6d 100644 --- a/LightlessSync/FileCache/CacheMonitor.cs +++ b/LightlessSync/FileCache/CacheMonitor.cs @@ -6,6 +6,7 @@ using LightlessSync.Utils; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; using System.Collections.Immutable; +using System.IO; namespace LightlessSync.FileCache; @@ -21,6 +22,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase private CancellationTokenSource _scanCancellationTokenSource = new(); private readonly CancellationTokenSource _periodicCalculationTokenSource = new(); public static readonly IImmutableList AllowedFileExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk", ".kdb"]; + private static readonly HashSet AllowedFileExtensionSet = new(AllowedFileExtensions, StringComparer.OrdinalIgnoreCase); public CacheMonitor(ILogger logger, IpcManager ipcManager, LightlessConfigService configService, FileCacheManager fileDbManager, LightlessMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil, @@ -163,7 +165,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase { Logger.LogTrace("Lightless FSW: FileChanged: {change} => {path}", e.ChangeType, e.FullPath); - if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; + if (!HasAllowedExtension(e.FullPath)) return; lock (_watcherChanges) { @@ -207,7 +209,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase private void Fs_Changed(object sender, FileSystemEventArgs e) { if (Directory.Exists(e.FullPath)) return; - if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; + if (!HasAllowedExtension(e.FullPath)) return; if (e.ChangeType is not (WatcherChangeTypes.Changed or WatcherChangeTypes.Deleted or WatcherChangeTypes.Created)) return; @@ -231,7 +233,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase { foreach (var file in directoryFiles) { - if (!AllowedFileExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) continue; + if (!HasAllowedExtension(file)) continue; var oldPath = file.Replace(e.FullPath, e.OldFullPath, StringComparison.OrdinalIgnoreCase); _watcherChanges.Remove(oldPath); @@ -243,7 +245,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase } else { - if (!AllowedFileExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return; + if (!HasAllowedExtension(e.FullPath)) return; lock (_watcherChanges) { @@ -263,6 +265,17 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase public FileSystemWatcher? PenumbraWatcher { get; private set; } public FileSystemWatcher? LightlessWatcher { get; private set; } + private static bool HasAllowedExtension(string path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + var extension = Path.GetExtension(path); + return !string.IsNullOrEmpty(extension) && AllowedFileExtensionSet.Contains(extension); + } + private async Task LightlessWatcherExecution() { _lightlessFswCts = _lightlessFswCts.CancelRecreate(); @@ -606,7 +619,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase [ .. Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories) .AsParallel() - .Where(f => AllowedFileExtensions.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase)) + .Where(f => HasAllowedExtension(f) && !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase) && !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase) && !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)), diff --git a/LightlessSync/FileCache/TransientResourceManager.cs b/LightlessSync/FileCache/TransientResourceManager.cs index 6d77a97..2ef8f22 100644 --- a/LightlessSync/FileCache/TransientResourceManager.cs +++ b/LightlessSync/FileCache/TransientResourceManager.cs @@ -372,6 +372,9 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor) { + if (descriptor.IsInGpose) + return; + if (!TryResolveObjectKind(descriptor, out var resolvedKind)) return; diff --git a/LightlessSync/Interop/Ipc/IpcCallerMoodles.cs b/LightlessSync/Interop/Ipc/IpcCallerMoodles.cs index e8b1b76..1bbbfda 100644 --- a/LightlessSync/Interop/Ipc/IpcCallerMoodles.cs +++ b/LightlessSync/Interop/Ipc/IpcCallerMoodles.cs @@ -1,5 +1,4 @@ -using Dalamud.Game.ClientState.Objects.SubKinds; -using Dalamud.Plugin; +using Dalamud.Plugin; using Dalamud.Plugin.Ipc; using LightlessSync.Interop.Ipc.Framework; using LightlessSync.Services; @@ -13,7 +12,7 @@ public sealed class IpcCallerMoodles : IpcServiceBase private static readonly IpcServiceDescriptor MoodlesDescriptor = new("Moodles", "Moodles", new Version(0, 0, 0, 0)); private readonly ICallGateSubscriber _moodlesApiVersion; - private readonly ICallGateSubscriber _moodlesOnChange; + private readonly ICallGateSubscriber _moodlesOnChange; private readonly ICallGateSubscriber _moodlesGetStatus; private readonly ICallGateSubscriber _moodlesSetStatus; private readonly ICallGateSubscriber _moodlesRevertStatus; @@ -29,7 +28,7 @@ public sealed class IpcCallerMoodles : IpcServiceBase _lightlessMediator = lightlessMediator; _moodlesApiVersion = pi.GetIpcSubscriber("Moodles.Version"); - _moodlesOnChange = pi.GetIpcSubscriber("Moodles.StatusManagerModified"); + _moodlesOnChange = pi.GetIpcSubscriber("Moodles.StatusManagerModified"); _moodlesGetStatus = pi.GetIpcSubscriber("Moodles.GetStatusManagerByPtrV2"); _moodlesSetStatus = pi.GetIpcSubscriber("Moodles.SetStatusManagerByPtrV2"); _moodlesRevertStatus = pi.GetIpcSubscriber("Moodles.ClearStatusManagerByPtrV2"); @@ -39,9 +38,9 @@ public sealed class IpcCallerMoodles : IpcServiceBase CheckAPI(); } - private void OnMoodlesChange(IPlayerCharacter character) + private void OnMoodlesChange(nint address) { - _lightlessMediator.Publish(new MoodlesMessage(character.Address)); + _lightlessMediator.Publish(new MoodlesMessage(address)); } protected override void Dispose(bool disposing) @@ -107,7 +106,7 @@ public sealed class IpcCallerMoodles : IpcServiceBase try { - return _moodlesApiVersion.InvokeFunc() == 3 + return _moodlesApiVersion.InvokeFunc() >= 4 ? IpcConnectionState.Available : IpcConnectionState.VersionMismatch; } diff --git a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs index f752051..39aa6c8 100644 --- a/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs +++ b/LightlessSync/PlayerData/Factories/PlayerDataFactory.cs @@ -119,6 +119,7 @@ public class PlayerDataFactory CharacterDataFragment fragment = objectKind == ObjectKind.Player ? new CharacterDataFragmentPlayer() : new(); _logger.LogDebug("Building character data for {obj}", playerRelatedObject); + var logDebug = _logger.IsEnabled(LogLevel.Debug); // wait until chara is not drawing and present so nothing spontaneously explodes await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct).ConfigureAwait(false); @@ -132,11 +133,6 @@ public class PlayerDataFactory ct.ThrowIfCancellationRequested(); - Dictionary>? boneIndices = - objectKind != ObjectKind.Player - ? null - : await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetSkeletonBoneIndices(playerRelatedObject)).ConfigureAwait(false); - DateTime start = DateTime.UtcNow; // penumbra call, it's currently broken @@ -154,11 +150,21 @@ public class PlayerDataFactory ct.ThrowIfCancellationRequested(); - _logger.LogDebug("== Static Replacements =="); - foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase)) + if (logDebug) { - _logger.LogDebug("=> {repl}", replacement); - ct.ThrowIfCancellationRequested(); + _logger.LogDebug("== Static Replacements =="); + foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase)) + { + _logger.LogDebug("=> {repl}", replacement); + ct.ThrowIfCancellationRequested(); + } + } + else + { + foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement)) + { + ct.ThrowIfCancellationRequested(); + } } await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false); @@ -190,11 +196,21 @@ public class PlayerDataFactory var transientPaths = ManageSemiTransientData(objectKind); var resolvedTransientPaths = await GetFileReplacementsFromPaths(transientPaths, new HashSet(StringComparer.Ordinal)).ConfigureAwait(false); - _logger.LogDebug("== Transient Replacements =="); - foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal)) + if (logDebug) { - _logger.LogDebug("=> {repl}", replacement); - fragment.FileReplacements.Add(replacement); + _logger.LogDebug("== Transient Replacements =="); + foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal)) + { + _logger.LogDebug("=> {repl}", replacement); + fragment.FileReplacements.Add(replacement); + } + } + else + { + foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key))) + { + fragment.FileReplacements.Add(replacement); + } } // clean up all semi transient resources that don't have any file replacement (aka null resolve) @@ -252,11 +268,26 @@ public class PlayerDataFactory ct.ThrowIfCancellationRequested(); + Dictionary>? boneIndices = null; + var hasPapFiles = false; + if (objectKind == ObjectKind.Player) + { + hasPapFiles = fragment.FileReplacements.Any(f => + !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)); + if (hasPapFiles) + { + boneIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetSkeletonBoneIndices(playerRelatedObject)).ConfigureAwait(false); + } + } + if (objectKind == ObjectKind.Player) { try { - await VerifyPlayerAnimationBones(boneIndices, (fragment as CharacterDataFragmentPlayer)!, ct).ConfigureAwait(false); + if (hasPapFiles) + { + await VerifyPlayerAnimationBones(boneIndices, (fragment as CharacterDataFragmentPlayer)!, ct).ConfigureAwait(false); + } } catch (OperationCanceledException e) { @@ -278,12 +309,16 @@ public class PlayerDataFactory { if (boneIndices == null) return; - foreach (var kvp in boneIndices) + if (_logger.IsEnabled(LogLevel.Debug)) { - _logger.LogDebug("Found {skellyname} ({idx} bone indices) on player: {bones}", kvp.Key, kvp.Value.Any() ? kvp.Value.Max() : 0, string.Join(',', kvp.Value)); + foreach (var kvp in boneIndices) + { + _logger.LogDebug("Found {skellyname} ({idx} bone indices) on player: {bones}", kvp.Key, kvp.Value.Any() ? kvp.Value.Max() : 0, string.Join(',', kvp.Value)); + } } - if (boneIndices.All(u => u.Value.Count == 0)) return; + var maxPlayerBoneIndex = boneIndices.SelectMany(kvp => kvp.Value).DefaultIfEmpty().Max(); + if (maxPlayerBoneIndex <= 0) return; int noValidationFailed = 0; foreach (var file in fragment.FileReplacements.Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList()) @@ -303,12 +338,13 @@ public class PlayerDataFactory _logger.LogDebug("Verifying bone indices for {path}, found {x} skeletons", file.ResolvedPath, skeletonIndices.Count); - foreach (var boneCount in skeletonIndices.Select(k => k).ToList()) + foreach (var boneCount in skeletonIndices) { - if (boneCount.Value.Max() > boneIndices.SelectMany(b => b.Value).Max()) + var maxAnimationIndex = boneCount.Value.DefaultIfEmpty().Max(); + if (maxAnimationIndex > maxPlayerBoneIndex) { _logger.LogWarning("Found more bone indices on the animation {path} skeleton {skl} (max indice {idx}) than on any player related skeleton (max indice {idx2})", - file.ResolvedPath, boneCount.Key, boneCount.Value.Max(), boneIndices.SelectMany(b => b.Value).Max()); + file.ResolvedPath, boneCount.Key, maxAnimationIndex, maxPlayerBoneIndex); validationFailed = true; break; }