Attempt fix on crash.
This commit is contained in:
@@ -566,7 +566,8 @@ public class PlayerDataFactory
|
||||
await _papParseLimiter.WaitAsync(ct).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
papIndices = await Task.Run(() => _modelAnalyzer.GetBoneIndicesFromPap(hash), ct)
|
||||
papIndices = await _dalamudUtil
|
||||
.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(hash, persistToConfig: false))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
@@ -577,9 +578,6 @@ public class PlayerDataFactory
|
||||
if (papIndices == null || papIndices.Count == 0)
|
||||
continue;
|
||||
|
||||
if (papIndices.All(k => k.Value.DefaultIfEmpty().Max() <= 105))
|
||||
continue;
|
||||
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
var papBuckets = papIndices
|
||||
@@ -658,8 +656,8 @@ public class PlayerDataFactory
|
||||
return new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase).AsReadOnly();
|
||||
}
|
||||
|
||||
var forwardPathsLower = forwardPaths.Length == 0 ? Array.Empty<string>() : forwardPaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
var reversePathsLower = reversePaths.Length == 0 ? Array.Empty<string>() : reversePaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
var forwardPathsLower = forwardPaths.Length == 0 ? [] : forwardPaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
var reversePathsLower = reversePaths.Length == 0 ? [] : reversePaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
|
||||
Dictionary<string, List<string>> resolvedPaths = new(forwardPaths.Length + reversePaths.Length, StringComparer.Ordinal);
|
||||
if (handler.ObjectKind != ObjectKind.Player)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.Havok.Animation;
|
||||
using FFXIVClientStructs.Havok.Common.Base.Types;
|
||||
using FFXIVClientStructs.Havok.Common.Serialize.Resource;
|
||||
using FFXIVClientStructs.Havok.Common.Serialize.Util;
|
||||
using LightlessSync.FileCache;
|
||||
using LightlessSync.Interop.GameModel;
|
||||
@@ -9,6 +10,7 @@ using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.PlayerData.Factories;
|
||||
using LightlessSync.PlayerData.Handlers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OtterGui.Text.EndObjects;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
@@ -172,13 +174,18 @@ public sealed partial class XivDataAnalyzer
|
||||
|
||||
reader.BaseStream.Position = havokPosition;
|
||||
var havokData = reader.ReadBytes(havokDataSize);
|
||||
if (havokData.Length <= 8)
|
||||
if (havokData.Length != havokDataSize)
|
||||
return null;
|
||||
|
||||
if (havokPosition < 0 || footerPosition < 0) return null;
|
||||
if (havokPosition >= fs.Length) return null;
|
||||
if (footerPosition > fs.Length) return null;
|
||||
if (havokPosition + havokDataSizeLong > fs.Length) return null;
|
||||
|
||||
var tempSets = new Dictionary<string, HashSet<ushort>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var tempHavokDataPath = Path.Combine(Path.GetTempPath(), $"lightless_{Guid.NewGuid():N}.hkx");
|
||||
IntPtr tempHavokDataPathAnsi = IntPtr.Zero;
|
||||
var tempHavokDataPathAnsi = Marshal.StringToHGlobalAnsi(tempHavokDataPath);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -190,63 +197,63 @@ public sealed partial class XivDataAnalyzer
|
||||
return null;
|
||||
}
|
||||
|
||||
tempHavokDataPathAnsi = Marshal.StringToHGlobalAnsi(tempHavokDataPath);
|
||||
var pathBytes = System.Text.Encoding.ASCII.GetBytes(tempHavokDataPath + "\0");
|
||||
|
||||
var loadoptions = stackalloc hkSerializeUtil.LoadOptions[1];
|
||||
loadoptions->TypeInfoRegistry = hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry();
|
||||
loadoptions->ClassNameRegistry = hkBuiltinTypeRegistry.Instance()->GetClassNameRegistry();
|
||||
loadoptions->Flags = new hkFlags<hkSerializeUtil.LoadOptionBits, int>
|
||||
hkSerializeUtil.LoadOptions loadOptions = default;
|
||||
loadOptions.TypeInfoRegistry = hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry();
|
||||
loadOptions.ClassNameRegistry = hkBuiltinTypeRegistry.Instance()->GetClassNameRegistry();
|
||||
loadOptions.Flags = new hkFlags<hkSerializeUtil.LoadOptionBits, int>
|
||||
{
|
||||
Storage = (int)hkSerializeUtil.LoadOptionBits.Default
|
||||
};
|
||||
|
||||
var resource = hkSerializeUtil.LoadFromFile((byte*)tempHavokDataPathAnsi, null, loadoptions);
|
||||
if (resource == null)
|
||||
fixed (byte* pPath = pathBytes)
|
||||
{
|
||||
_logger.LogWarning("Havok resource was null after loading from {path}", tempHavokDataPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
var rootLevelName = @"hkRootLevelContainer"u8;
|
||||
fixed (byte* n1 = rootLevelName)
|
||||
{
|
||||
var container = (hkRootLevelContainer*)resource->GetContentsPointer(n1, hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry());
|
||||
if (container == null)
|
||||
var resource = hkSerializeUtil.LoadFromFile(pPath, errorResult: null, &loadOptions);
|
||||
if (resource == null)
|
||||
return null;
|
||||
|
||||
var animationName = @"hkaAnimationContainer"u8;
|
||||
fixed (byte* n2 = animationName)
|
||||
var rootLevelName = @"hkRootLevelContainer"u8;
|
||||
fixed (byte* n1 = rootLevelName)
|
||||
{
|
||||
var animContainer = (hkaAnimationContainer*)container->findObjectByName(n2, null);
|
||||
if (animContainer == null)
|
||||
var container = (hkRootLevelContainer*)resource->GetContentsPointer(n1, hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry());
|
||||
if (container == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < animContainer->Bindings.Length; i++)
|
||||
var animationName = @"hkaAnimationContainer"u8;
|
||||
fixed (byte* n2 = animationName)
|
||||
{
|
||||
var binding = animContainer->Bindings[i].ptr;
|
||||
if (binding == null)
|
||||
continue;
|
||||
var animContainer = (hkaAnimationContainer*)container->findObjectByName(n2, null);
|
||||
if (animContainer == null)
|
||||
return null;
|
||||
|
||||
var rawSkel = binding->OriginalSkeletonName.String;
|
||||
var skeletonKey = CanonicalizeSkeletonKey(rawSkel);
|
||||
if (string.IsNullOrEmpty(skeletonKey))
|
||||
continue;
|
||||
|
||||
var boneTransform = binding->TransformTrackToBoneIndices;
|
||||
if (boneTransform.Length <= 0)
|
||||
continue;
|
||||
|
||||
if (!tempSets.TryGetValue(skeletonKey, out var set))
|
||||
for (int i = 0; i < animContainer->Bindings.Length; i++)
|
||||
{
|
||||
set = [];
|
||||
tempSets[skeletonKey] = set;
|
||||
}
|
||||
var binding = animContainer->Bindings[i].ptr;
|
||||
if (binding == null)
|
||||
continue;
|
||||
|
||||
for (int boneIdx = 0; boneIdx < boneTransform.Length; boneIdx++)
|
||||
{
|
||||
var v = boneTransform[boneIdx];
|
||||
if (v < 0) continue;
|
||||
set.Add((ushort)v);
|
||||
var rawSkel = binding->OriginalSkeletonName.String;
|
||||
var skeletonKey = CanonicalizeSkeletonKey(rawSkel);
|
||||
if (string.IsNullOrEmpty(skeletonKey) || string.Equals(skeletonKey, "skeleton", StringComparison.OrdinalIgnoreCase))
|
||||
skeletonKey = "__any__";
|
||||
|
||||
var boneTransform = binding->TransformTrackToBoneIndices;
|
||||
if (boneTransform.Length <= 0)
|
||||
continue;
|
||||
|
||||
if (!tempSets.TryGetValue(skeletonKey, out var set))
|
||||
{
|
||||
set = [];
|
||||
tempSets[skeletonKey] = set;
|
||||
}
|
||||
|
||||
for (int boneIdx = 0; boneIdx < boneTransform.Length; boneIdx++)
|
||||
{
|
||||
var v = boneTransform[boneIdx];
|
||||
if (v < 0) continue;
|
||||
set.Add((ushort)v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,7 +304,6 @@ public sealed partial class XivDataAnalyzer
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
public static string CanonicalizeSkeletonKey(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
@@ -375,41 +381,56 @@ public sealed partial class XivDataAnalyzer
|
||||
if (mode == AnimationValidationMode.Unsafe)
|
||||
return true;
|
||||
|
||||
var papBuckets = papBoneIndices.Keys
|
||||
.Select(CanonicalizeSkeletonKey)
|
||||
.Where(k => !string.IsNullOrEmpty(k))
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
var papByBucket = new Dictionary<string, List<ushort>>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (papBuckets.Count == 0)
|
||||
foreach (var (rawKey, list) in papBoneIndices)
|
||||
{
|
||||
var key = CanonicalizeSkeletonKey(rawKey);
|
||||
if (string.IsNullOrEmpty(key))
|
||||
continue;
|
||||
|
||||
if (string.Equals(key, "skeleton", StringComparison.OrdinalIgnoreCase))
|
||||
key = "__any__";
|
||||
|
||||
if (!papByBucket.TryGetValue(key, out var acc))
|
||||
papByBucket[key] = acc = [];
|
||||
|
||||
if (list is { Count: > 0 })
|
||||
acc.AddRange(list);
|
||||
}
|
||||
|
||||
foreach (var k in papByBucket.Keys.ToList())
|
||||
papByBucket[k] = papByBucket[k].Distinct().ToList();
|
||||
|
||||
if (papByBucket.Count == 0)
|
||||
{
|
||||
reason = "No skeleton bucket bindings found in the PAP";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode == AnimationValidationMode.Safe)
|
||||
static bool AllIndicesOk(
|
||||
HashSet<ushort> available,
|
||||
List<ushort> indices,
|
||||
bool papLikelyOneBased,
|
||||
bool allowOneBasedShift,
|
||||
bool allowNeighborTolerance,
|
||||
out ushort missing)
|
||||
{
|
||||
if (papBuckets.Any(b => localBoneSets.ContainsKey(b)))
|
||||
return true;
|
||||
|
||||
reason = $"No matching skeleton bucket between PAP [{string.Join(", ", papBuckets)}] and local [{string.Join(", ", localBoneSets.Keys.Order())}].";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var bucket in papBuckets)
|
||||
{
|
||||
if (!localBoneSets.TryGetValue(bucket, out var available))
|
||||
foreach (var idx in indices)
|
||||
{
|
||||
reason = $"Missing skeleton bucket '{bucket}' on local actor.";
|
||||
return false;
|
||||
if (!ContainsIndexCompat(available, idx, papLikelyOneBased, allowOneBasedShift, allowNeighborTolerance))
|
||||
{
|
||||
missing = idx;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var indices = papBoneIndices
|
||||
.Where(kvp => string.Equals(CanonicalizeSkeletonKey(kvp.Key), bucket, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(kvp => kvp.Value ?? Enumerable.Empty<ushort>())
|
||||
.Distinct()
|
||||
.ToList();
|
||||
missing = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var (bucket, indices) in papByBucket)
|
||||
{
|
||||
if (indices.Count == 0)
|
||||
continue;
|
||||
|
||||
@@ -423,14 +444,32 @@ public sealed partial class XivDataAnalyzer
|
||||
}
|
||||
bool papLikelyOneBased = allowOneBasedShift && (min == 1) && has1 && !has0;
|
||||
|
||||
foreach (var idx in indices)
|
||||
if (string.Equals(bucket, "__any__", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!ContainsIndexCompat(available, idx, papLikelyOneBased, allowOneBasedShift, allowNeighborTolerance))
|
||||
foreach (var (lk, ls) in localBoneSets)
|
||||
{
|
||||
reason = $"No compatible local skeleton for PAP '{bucket}': missing bone index {idx}.";
|
||||
return false;
|
||||
if (AllIndicesOk(ls, indices, papLikelyOneBased, allowOneBasedShift, allowNeighborTolerance, out _))
|
||||
goto nextBucket;
|
||||
}
|
||||
|
||||
reason = $"No compatible local skeleton bucket for generic PAP skeleton '{bucket}'. Local buckets: {string.Join(", ", localBoneSets.Keys)}";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!localBoneSets.TryGetValue(bucket, out var available))
|
||||
{
|
||||
reason = $"Missing skeleton bucket '{bucket}' on local actor.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!AllIndicesOk(available, indices, papLikelyOneBased, allowOneBasedShift, allowNeighborTolerance, out var missing))
|
||||
{
|
||||
reason = $"No compatible local skeleton for PAP '{bucket}': missing bone index {missing}.";
|
||||
return false;
|
||||
}
|
||||
|
||||
nextBucket:
|
||||
;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user