Attempt fix on crash.

This commit is contained in:
cake
2026-01-06 13:50:58 +01:00
parent 032201ed9e
commit 5161c6bad3
2 changed files with 116 additions and 79 deletions

View File

@@ -566,7 +566,8 @@ public class PlayerDataFactory
await _papParseLimiter.WaitAsync(ct).ConfigureAwait(false); await _papParseLimiter.WaitAsync(ct).ConfigureAwait(false);
try try
{ {
papIndices = await Task.Run(() => _modelAnalyzer.GetBoneIndicesFromPap(hash), ct) papIndices = await _dalamudUtil
.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(hash, persistToConfig: false))
.ConfigureAwait(false); .ConfigureAwait(false);
} }
finally finally
@@ -577,9 +578,6 @@ public class PlayerDataFactory
if (papIndices == null || papIndices.Count == 0) if (papIndices == null || papIndices.Count == 0)
continue; continue;
if (papIndices.All(k => k.Value.DefaultIfEmpty().Max() <= 105))
continue;
if (_logger.IsEnabled(LogLevel.Debug)) if (_logger.IsEnabled(LogLevel.Debug))
{ {
var papBuckets = papIndices var papBuckets = papIndices
@@ -658,8 +656,8 @@ public class PlayerDataFactory
return new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase).AsReadOnly(); return new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase).AsReadOnly();
} }
var forwardPathsLower = forwardPaths.Length == 0 ? Array.Empty<string>() : forwardPaths.Select(p => p.ToLowerInvariant()).ToArray(); var forwardPathsLower = forwardPaths.Length == 0 ? [] : forwardPaths.Select(p => p.ToLowerInvariant()).ToArray();
var reversePathsLower = reversePaths.Length == 0 ? Array.Empty<string>() : reversePaths.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); Dictionary<string, List<string>> resolvedPaths = new(forwardPaths.Length + reversePaths.Length, StringComparer.Ordinal);
if (handler.ObjectKind != ObjectKind.Player) if (handler.ObjectKind != ObjectKind.Player)

View File

@@ -2,6 +2,7 @@
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.Havok.Animation; using FFXIVClientStructs.Havok.Animation;
using FFXIVClientStructs.Havok.Common.Base.Types; using FFXIVClientStructs.Havok.Common.Base.Types;
using FFXIVClientStructs.Havok.Common.Serialize.Resource;
using FFXIVClientStructs.Havok.Common.Serialize.Util; using FFXIVClientStructs.Havok.Common.Serialize.Util;
using LightlessSync.FileCache; using LightlessSync.FileCache;
using LightlessSync.Interop.GameModel; using LightlessSync.Interop.GameModel;
@@ -9,6 +10,7 @@ using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Factories; using LightlessSync.PlayerData.Factories;
using LightlessSync.PlayerData.Handlers; using LightlessSync.PlayerData.Handlers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using OtterGui.Text.EndObjects;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -172,13 +174,18 @@ public sealed partial class XivDataAnalyzer
reader.BaseStream.Position = havokPosition; reader.BaseStream.Position = havokPosition;
var havokData = reader.ReadBytes(havokDataSize); var havokData = reader.ReadBytes(havokDataSize);
if (havokData.Length <= 8) if (havokData.Length != havokDataSize)
return null; 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 tempSets = new Dictionary<string, HashSet<ushort>>(StringComparer.OrdinalIgnoreCase);
var tempHavokDataPath = Path.Combine(Path.GetTempPath(), $"lightless_{Guid.NewGuid():N}.hkx"); var tempHavokDataPath = Path.Combine(Path.GetTempPath(), $"lightless_{Guid.NewGuid():N}.hkx");
IntPtr tempHavokDataPathAnsi = IntPtr.Zero; var tempHavokDataPathAnsi = Marshal.StringToHGlobalAnsi(tempHavokDataPath);
try try
{ {
@@ -190,22 +197,21 @@ public sealed partial class XivDataAnalyzer
return null; return null;
} }
tempHavokDataPathAnsi = Marshal.StringToHGlobalAnsi(tempHavokDataPath); var pathBytes = System.Text.Encoding.ASCII.GetBytes(tempHavokDataPath + "\0");
var loadoptions = stackalloc hkSerializeUtil.LoadOptions[1]; hkSerializeUtil.LoadOptions loadOptions = default;
loadoptions->TypeInfoRegistry = hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry(); loadOptions.TypeInfoRegistry = hkBuiltinTypeRegistry.Instance()->GetTypeInfoRegistry();
loadoptions->ClassNameRegistry = hkBuiltinTypeRegistry.Instance()->GetClassNameRegistry(); loadOptions.ClassNameRegistry = hkBuiltinTypeRegistry.Instance()->GetClassNameRegistry();
loadoptions->Flags = new hkFlags<hkSerializeUtil.LoadOptionBits, int> loadOptions.Flags = new hkFlags<hkSerializeUtil.LoadOptionBits, int>
{ {
Storage = (int)hkSerializeUtil.LoadOptionBits.Default Storage = (int)hkSerializeUtil.LoadOptionBits.Default
}; };
var resource = hkSerializeUtil.LoadFromFile((byte*)tempHavokDataPathAnsi, null, loadoptions); fixed (byte* pPath = pathBytes)
if (resource == null)
{ {
_logger.LogWarning("Havok resource was null after loading from {path}", tempHavokDataPath); var resource = hkSerializeUtil.LoadFromFile(pPath, errorResult: null, &loadOptions);
if (resource == null)
return null; return null;
}
var rootLevelName = @"hkRootLevelContainer"u8; var rootLevelName = @"hkRootLevelContainer"u8;
fixed (byte* n1 = rootLevelName) fixed (byte* n1 = rootLevelName)
@@ -229,8 +235,8 @@ public sealed partial class XivDataAnalyzer
var rawSkel = binding->OriginalSkeletonName.String; var rawSkel = binding->OriginalSkeletonName.String;
var skeletonKey = CanonicalizeSkeletonKey(rawSkel); var skeletonKey = CanonicalizeSkeletonKey(rawSkel);
if (string.IsNullOrEmpty(skeletonKey)) if (string.IsNullOrEmpty(skeletonKey) || string.Equals(skeletonKey, "skeleton", StringComparison.OrdinalIgnoreCase))
continue; skeletonKey = "__any__";
var boneTransform = binding->TransformTrackToBoneIndices; var boneTransform = binding->TransformTrackToBoneIndices;
if (boneTransform.Length <= 0) if (boneTransform.Length <= 0)
@@ -252,6 +258,7 @@ public sealed partial class XivDataAnalyzer
} }
} }
} }
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning(ex, "Could not load havok file in {path}", tempHavokDataPath); _logger.LogWarning(ex, "Could not load havok file in {path}", tempHavokDataPath);
@@ -297,7 +304,6 @@ public sealed partial class XivDataAnalyzer
return output; return output;
} }
public static string CanonicalizeSkeletonKey(string? raw) public static string CanonicalizeSkeletonKey(string? raw)
{ {
if (string.IsNullOrWhiteSpace(raw)) if (string.IsNullOrWhiteSpace(raw))
@@ -375,41 +381,56 @@ public sealed partial class XivDataAnalyzer
if (mode == AnimationValidationMode.Unsafe) if (mode == AnimationValidationMode.Unsafe)
return true; return true;
var papBuckets = papBoneIndices.Keys var papByBucket = new Dictionary<string, List<ushort>>(StringComparer.OrdinalIgnoreCase);
.Select(CanonicalizeSkeletonKey)
.Where(k => !string.IsNullOrEmpty(k))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
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"; reason = "No skeleton bucket bindings found in the PAP";
return false; 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))) foreach (var idx in indices)
{
if (!ContainsIndexCompat(available, idx, papLikelyOneBased, allowOneBasedShift, allowNeighborTolerance))
{
missing = idx;
return false;
}
}
missing = 0;
return true; 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) foreach (var (bucket, indices) in papByBucket)
{ {
if (!localBoneSets.TryGetValue(bucket, out var available))
{
reason = $"Missing skeleton bucket '{bucket}' on local actor.";
return false;
}
var indices = papBoneIndices
.Where(kvp => string.Equals(CanonicalizeSkeletonKey(kvp.Key), bucket, StringComparison.OrdinalIgnoreCase))
.SelectMany(kvp => kvp.Value ?? Enumerable.Empty<ushort>())
.Distinct()
.ToList();
if (indices.Count == 0) if (indices.Count == 0)
continue; continue;
@@ -423,14 +444,32 @@ public sealed partial class XivDataAnalyzer
} }
bool papLikelyOneBased = allowOneBasedShift && (min == 1) && has1 && !has0; 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}."; 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; 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; return true;