diff --git a/MareSynchronos/FileCache/FileCacheManager.cs b/MareSynchronos/FileCache/FileCacheManager.cs index 92ae29a..66f8609 100644 --- a/MareSynchronos/FileCache/FileCacheManager.cs +++ b/MareSynchronos/FileCache/FileCacheManager.cs @@ -193,7 +193,7 @@ public sealed class FileCacheManager : IHostedService foreach (var entry in cleanedPaths) { - _logger.LogDebug("Checking {path}", entry.Value); + //_logger.LogDebug("Checking {path}", entry.Value); if (dict.TryGetValue(entry.Value, out var entity)) { @@ -321,7 +321,7 @@ public sealed class FileCacheManager : IHostedService if (!entries.Exists(u => string.Equals(u.PrefixedFilePath, fileCache.PrefixedFilePath, StringComparison.OrdinalIgnoreCase))) { - _logger.LogTrace("Adding to DB: {hash} => {path}", fileCache.Hash, fileCache.PrefixedFilePath); + //_logger.LogTrace("Adding to DB: {hash} => {path}", fileCache.Hash, fileCache.PrefixedFilePath); entries.Add(fileCache); } } @@ -344,7 +344,7 @@ public sealed class FileCacheManager : IHostedService private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache) { var resultingFileCache = ReplacePathPrefixes(fileCache); - _logger.LogTrace("Validating {path}", fileCache.PrefixedFilePath); + //_logger.LogTrace("Validating {path}", fileCache.PrefixedFilePath); resultingFileCache = Validate(resultingFileCache); return resultingFileCache; } diff --git a/MareSynchronos/Interop/DalamudLogger.cs b/MareSynchronos/Interop/DalamudLogger.cs index 0b72ec9..9a1f0f6 100644 --- a/MareSynchronos/Interop/DalamudLogger.cs +++ b/MareSynchronos/Interop/DalamudLogger.cs @@ -38,7 +38,7 @@ internal sealed class DalamudLogger : ILogger else { StringBuilder sb = new(); - sb.AppendLine($"{unsupported}[{_name}]{{{(int)logLevel}}} {state}{(_hasModifiedGameFiles ? "." : string.Empty)}: {exception?.Message}"); + sb.AppendLine($"{unsupported}[{_name}]{{{(int)logLevel}}} {state}{(_hasModifiedGameFiles ? "." : string.Empty)} {exception?.Message}"); sb.AppendLine(exception?.StackTrace); var innerException = exception?.InnerException; while (innerException != null) diff --git a/MareSynchronos/MareConfiguration/Configurations/XivDataStorageConfig.cs b/MareSynchronos/MareConfiguration/Configurations/XivDataStorageConfig.cs index f9225e1..4d56a9d 100644 --- a/MareSynchronos/MareConfiguration/Configurations/XivDataStorageConfig.cs +++ b/MareSynchronos/MareConfiguration/Configurations/XivDataStorageConfig.cs @@ -5,6 +5,6 @@ namespace MareSynchronos.MareConfiguration.Configurations; public class XivDataStorageConfig : IMareConfiguration { public ConcurrentDictionary TriangleDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase); - public ConcurrentDictionary>> BoneDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase); + public ConcurrentDictionary>> BonesDictionary { get; set; } = new(StringComparer.OrdinalIgnoreCase); public int Version { get; set; } = 0; } \ No newline at end of file diff --git a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs index 4f26b7e..3042f9a 100644 --- a/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs +++ b/MareSynchronos/PlayerData/Factories/PlayerDataFactory.cs @@ -249,28 +249,37 @@ public class PlayerDataFactory var boneIndices = _modelAnalyzer.GetSkeletonBoneIndices(charaPointer); if (boneIndices != null) { - _logger.LogDebug("Found {idx} bone indices on player: {bones}", boneIndices.Count, string.Join(',', boneIndices)); + foreach (var kvp in boneIndices) + { + _logger.LogDebug("Found {skellyname} ({idx} bone indices) on player: {bones}", kvp.Key, kvp.Value.Max(), string.Join(',', kvp.Value)); + } + + if (boneIndices.All(u => u.Value.Count == 0)) return; int noValidationFailed = 0; foreach (var file in previousData.FileReplacements[objectKind].Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList()) { - var animationIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(file.Hash)).ConfigureAwait(false); + var skeletonIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(file.Hash)).ConfigureAwait(false); bool validationFailed = false; - if (animationIndices != null) + if (skeletonIndices != null) { - _logger.LogDebug("Verifying bone indices for {path}, found {x} animations", file.ResolvedPath, animationIndices.Count); - for (int i = 0; i < animationIndices.Count; i++) + // 105 is the maximum vanilla skellington spoopy bone index + if (skeletonIndices.All(k => k.Value.Max() <= 105)) { - _logger.LogTrace("Verifying animation set {i}, found {idx} bone indeces", i, animationIndices[i].Count); - if (animationIndices[i].Count > boneIndices.Count) + _logger.LogTrace("All indices of {path} are <= 105, ignoring", file.ResolvedPath); + continue; + } + + _logger.LogDebug("Verifying bone indices for {path}, found {x} skeletons", file.ResolvedPath, skeletonIndices.Count); + + foreach (var boneCount in skeletonIndices.Select(k => k).ToList()) + { + if (boneCount.Value.Max() > boneIndices.SelectMany(b => b.Value).Max()) { - _logger.LogWarning("Found more bone indeces on the animation {path} ({idx}) than on player skeleton ({idx2})", file.ResolvedPath, animationIndices[i].Count, boneIndices.Count); - validationFailed = true; - } - else if (animationIndices[i].Exists(idx => !boneIndices.Contains(idx))) - { - _logger.LogWarning("Found bone indices referred on animation {path} that are not on the player skeleton", file.ResolvedPath); + _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()); validationFailed = true; + break; } } } @@ -287,6 +296,7 @@ public class PlayerDataFactory } } + if (noValidationFailed > 0) { _mareMediator.Publish(new NotificationMessage("Invalid Skeleton Setup", diff --git a/MareSynchronos/Services/XivDataAnalyzer.cs b/MareSynchronos/Services/XivDataAnalyzer.cs index 6d845a1..ad7465b 100644 --- a/MareSynchronos/Services/XivDataAnalyzer.cs +++ b/MareSynchronos/Services/XivDataAnalyzer.cs @@ -27,38 +27,35 @@ public sealed class XivDataAnalyzer _luminaGameData = new GameData(gameData.GameData.DataPath.FullName); } - public unsafe List? GetSkeletonBoneIndices(nint charaPtr) + public unsafe Dictionary>? GetSkeletonBoneIndices(nint charaPtr) { if (charaPtr == nint.Zero) return null; var chara = (CharacterBase*)(((Character*)charaPtr)->GameObject.DrawObject); + if (chara->GetModelType() != CharacterBase.ModelType.Human) return null; var resHandles = chara->Skeleton->SkeletonResourceHandles; - int i = -1; - uint maxBones = 0; - List outputIndices = new(); - while (*(resHandles + ++i) != null) + int i = 0; + Dictionary> outputIndices = new(); + while (*(resHandles + i) != null) { var handle = *(resHandles + i); var curBones = handle->BoneCount; - List indices = new(); + var skeletonName = handle->ResourceHandle.FileName.ToString(); + outputIndices[skeletonName] = new(); for (ushort boneIdx = 0; boneIdx < curBones; boneIdx++) { var boneName = handle->HavokSkeleton->Bones[boneIdx].Name.String; if (boneName == null) continue; - indices.Add(boneIdx); - } - if (curBones > maxBones) - { - maxBones = curBones; - outputIndices = indices; + outputIndices[skeletonName].Add(boneIdx); } + i++; } return outputIndices; } - public unsafe List>? GetBoneIndicesFromPap(string hash) + public unsafe Dictionary>? GetBoneIndicesFromPap(string hash) { - if (_configService.Current.BoneDictionary.TryGetValue(hash, out var bones)) return bones; + if (_configService.Current.BonesDictionary.TryGetValue(hash, out var bones)) return bones; var cacheEntity = _fileCacheManager.GetFileCacheByHash(hash); if (cacheEntity == null) return null; @@ -82,7 +79,7 @@ public sealed class XivDataAnalyzer var havokData = reader.ReadBytes(havokDataSize); if (havokData.Length <= 8) return null; // no havok data - var output = new List>(); + var output = new Dictionary>(StringComparer.OrdinalIgnoreCase); var tempHavokDataPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) + ".hkx"; var tempHavokDataPathAnsi = Marshal.StringToHGlobalAnsi(tempHavokDataPath); @@ -114,14 +111,15 @@ public sealed class XivDataAnalyzer var animContainer = (hkaAnimationContainer*)container->findObjectByName(n2, null); for (int i = 0; i < animContainer->Bindings.Length; i++) { - var boneTransform = animContainer->Bindings[i].ptr->TransformTrackToBoneIndices; - List boneIndices = []; + var binding = animContainer->Bindings[i].ptr; + var boneTransform = binding->TransformTrackToBoneIndices; + string name = binding->OriginalSkeletonName.String! + "_" + i; + output[name] = []; for (int boneIdx = 0; boneIdx < boneTransform.Length; boneIdx++) { - boneIndices.Add((ushort)boneTransform[boneIdx]); + output[name].Add((ushort)boneTransform[boneIdx]); } - - output.Add(boneIndices); + output[name].Sort(); } } @@ -137,7 +135,7 @@ public sealed class XivDataAnalyzer File.Delete(tempHavokDataPath); } - _configService.Current.BoneDictionary[hash] = output; + _configService.Current.BonesDictionary[hash] = output; _configService.Save(); return output; }