From 2852562a03d4a09043691eeb2f5dad28ba956c30 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Thu, 2 Nov 2023 22:41:20 +0100 Subject: [PATCH] ResourceTree: Use ResolveXXXPath where possible --- Penumbra.GameData | 2 +- .../Interop/ResourceTree/ResolveContext.cs | 35 +++++++---- Penumbra/Interop/ResourceTree/ResourceTree.cs | 59 ++++++++++++------ .../Interop/Structs/CharacterBaseUtility.cs | 62 +++++++++++++++++++ 4 files changed, 125 insertions(+), 33 deletions(-) create mode 100644 Penumbra/Interop/Structs/CharacterBaseUtility.cs diff --git a/Penumbra.GameData b/Penumbra.GameData index 04ddadb4..b141301c 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 04ddadb44600a382e26661e1db08fd16c3b671d8 +Subproject commit b141301c4ee65422d6802f3038c8f344911d4ae2 diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.cs b/Penumbra/Interop/ResourceTree/ResolveContext.cs index 26d64afe..d700131d 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -1,6 +1,8 @@ using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using FFXIVClientStructs.Interop; using OtterGui; using Penumbra.Api.Enums; using Penumbra.GameData; @@ -9,6 +11,7 @@ using Penumbra.GameData.Structs; using Penumbra.String; using Penumbra.String.Classes; using Penumbra.UI; +using static Penumbra.Interop.Structs.CharacterBaseUtility; using static Penumbra.Interop.Structs.StructExtensions; namespace Penumbra.Interop.ResourceTree; @@ -18,11 +21,11 @@ internal record GlobalResolveContext(IObjectIdentifier Identifier, TreeBuildCach { public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128); - public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment) - => new(this, slot, equipment); + public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex, EquipSlot slot, CharacterArmor equipment) + => new(this, characterBase, slotIndex, slot, equipment); } -internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, CharacterArmor Equipment) +internal record ResolveContext(GlobalResolveContext Global, Pointer CharacterBase, uint SlotIndex, EquipSlot Slot, CharacterArmor Equipment) { private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true); @@ -112,7 +115,8 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char if (eid == null) return null; - var path = Utf8GamePath.Empty; // TODO + if (!Utf8GamePath.FromByteString(ResolveEidPath(CharacterBase), out var path)) + return null; return GetOrCreateNode(ResourceType.Eid, 0, eid, path); } @@ -122,17 +126,19 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char if (imc == null) return null; - var path = Utf8GamePath.Empty; // TODO + if (!Utf8GamePath.FromByteString(ResolveImcPath(CharacterBase, SlotIndex), out var path)) + return null; return GetOrCreateNode(ResourceType.Imc, 0, imc, path); } - public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex) + public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, string gamePath) { if (tex == null) return null; - var path = Utf8GamePath.Empty; // TODO + if (!Utf8GamePath.FromString(gamePath, out var path)) + return null; return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, path); } @@ -142,7 +148,8 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char if (mdl == null || mdl->ModelResourceHandle == null) return null; - var path = Utf8GamePath.Empty; // TODO + if (!Utf8GamePath.FromByteString(ResolveMdlPath(CharacterBase, SlotIndex), out var path)) + return null; if (Global.Nodes.TryGetValue((path, (nint)mdl->ModelResourceHandle), out var cached)) return cached; @@ -253,12 +260,13 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char return node; } - public unsafe ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb) + public unsafe ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex) { if (sklb == null || sklb->SkeletonResourceHandle == null) return null; - var path = Utf8GamePath.Empty; // TODO + if (!Utf8GamePath.FromByteString(ResolveSklbPath(CharacterBase, partialSkeletonIndex), out var path)) + return null; if (Global.Nodes.TryGetValue((path, (nint)sklb->SkeletonResourceHandle), out var cached)) return cached; @@ -266,7 +274,7 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char var node = CreateNode(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, path, false); if (node != null) { - var skpNode = CreateParameterNodeFromPartialSkeleton(sklb); + var skpNode = CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex); if (skpNode != null) node.Children.Add(skpNode); Global.Nodes.Add((path, (nint)sklb->SkeletonResourceHandle), node); @@ -275,12 +283,13 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char return node; } - private unsafe ResourceNode? CreateParameterNodeFromPartialSkeleton(PartialSkeleton* sklb) + private unsafe ResourceNode? CreateParameterNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex) { if (sklb == null || sklb->SkeletonParameterResourceHandle == null) return null; - var path = Utf8GamePath.Empty; // TODO + if (!Utf8GamePath.FromByteString(ResolveSkpPath(CharacterBase, partialSkeletonIndex), out var path)) + return null; if (Global.Nodes.TryGetValue((path, (nint)sklb->SkeletonParameterResourceHandle), out var cached)) return cached; diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 687c14ec..7c58d6a8 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -1,7 +1,9 @@ +using Dalamud.Game.ClientState.Objects.Enums; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle; +using Penumbra.GameData.Data; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.UI; @@ -62,8 +64,10 @@ public class ResourceTree CustomizeData = character->DrawData.CustomizeData; RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown; + var genericContext = globalContext.CreateContext(model, 0xFFFFFFFFu, EquipSlot.Unknown, default); + var eid = (ResourceHandle*)model->EID; - var eidNode = globalContext.CreateContext(EquipSlot.Unknown, default).CreateNodeFromEid(eid); + var eidNode = genericContext.CreateNodeFromEid(eid); if (eidNode != null) { if (globalContext.WithUiData) @@ -73,13 +77,15 @@ public class ResourceTree for (var i = 0; i < model->SlotCount; ++i) { - var context = globalContext.CreateContext( + var slotContext = globalContext.CreateContext( + model, + (uint)i, i < equipment.Length ? ((uint)i).ToEquipSlot() : EquipSlot.Unknown, i < equipment.Length ? equipment[i] : default ); var imc = (ResourceHandle*)model->IMCArray[i]; - var imcNode = context.CreateNodeFromImc(imc); + var imcNode = slotContext.CreateNodeFromImc(imc); if (imcNode != null) { if (globalContext.WithUiData) @@ -88,7 +94,7 @@ public class ResourceTree } var mdl = model->Models[i]; - var mdlNode = context.CreateNodeFromRenderModel(mdl); + var mdlNode = slotContext.CreateNodeFromRenderModel(mdl); if (mdlNode != null) { if (globalContext.WithUiData) @@ -97,18 +103,20 @@ public class ResourceTree } } - AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton); + AddSkeleton(Nodes, genericContext, model->Skeleton); + + AddSubObjects(globalContext, model); if (human != null) AddHumanResources(globalContext, human); } - private unsafe void AddHumanResources(GlobalResolveContext globalContext, Human* human) + private unsafe void AddSubObjects(GlobalResolveContext globalContext, CharacterBase* model) { var subObjectIndex = 0; var weaponIndex = 0; var subObjectNodes = new List(); - foreach (var baseSubObject in human->CharacterBase.DrawObject.Object.ChildObjects) + foreach (var baseSubObject in model->DrawObject.Object.ChildObjects) { if (baseSubObject->GetObjectType() != FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase) continue; @@ -117,13 +125,13 @@ public class ResourceTree var weapon = subObject->GetModelType() == CharacterBase.ModelType.Weapon ? (Weapon*)subObject : null; var subObjectNamePrefix = weapon != null ? "Weapon" : "Fashion Acc."; // This way to tell apart MainHand and OffHand is not always accurate, but seems good enough for what we're doing with it. - var subObjectContext = globalContext.CreateContext( - weapon != null ? (weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand) : EquipSlot.Unknown, - weapon != null ? new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, (byte)weapon->ModelUnknown) : default - ); + var slot = weapon != null ? (weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand) : EquipSlot.Unknown; + var equipment = weapon != null ? new CharacterArmor(weapon->ModelSetId, (byte)weapon->Variant, (byte)weapon->ModelUnknown) : default; + + var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment); var eid = (ResourceHandle*)subObject->EID; - var eidNode = subObjectContext.CreateNodeFromEid(eid); + var eidNode = genericContext.CreateNodeFromEid(eid); if (eidNode != null) { if (globalContext.WithUiData) @@ -133,8 +141,10 @@ public class ResourceTree for (var i = 0; i < subObject->SlotCount; ++i) { + var slotContext = globalContext.CreateContext(subObject, (uint)i, slot, equipment); + var imc = (ResourceHandle*)subObject->IMCArray[i]; - var imcNode = subObjectContext.CreateNodeFromImc(imc); + var imcNode = slotContext.CreateNodeFromImc(imc); if (imcNode != null) { if (globalContext.WithUiData) @@ -143,7 +153,7 @@ public class ResourceTree } var mdl = subObject->Models[i]; - var mdlNode = subObjectContext.CreateNodeFromRenderModel(mdl); + var mdlNode = slotContext.CreateNodeFromRenderModel(mdl); if (mdlNode != null) { if (globalContext.WithUiData) @@ -152,17 +162,24 @@ public class ResourceTree } } - AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, "); + AddSkeleton(subObjectNodes, genericContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, "); ++subObjectIndex; if (weapon != null) ++weaponIndex; } Nodes.InsertRange(0, subObjectNodes); + } - var context = globalContext.CreateContext(EquipSlot.Unknown, default); + private unsafe void AddHumanResources(GlobalResolveContext globalContext, Human* human) + { + var genericContext = globalContext.CreateContext(&human->CharacterBase, 0xFFFFFFFFu, EquipSlot.Unknown, default); - var decalNode = context.CreateNodeFromTex(human->Decal); + var decalId = (byte)(human->Customize[(int)CustomizeIndex.Facepaint] & 0x7F); + var decalPath = decalId != 0 + ? GamePaths.Human.Decal.FaceDecalPath(decalId) + : GamePaths.Tex.TransparentPath; + var decalNode = genericContext.CreateNodeFromTex(human->Decal, decalPath); if (decalNode != null) { if (globalContext.WithUiData) @@ -174,7 +191,11 @@ public class ResourceTree Nodes.Add(decalNode); } - var legacyDecalNode = context.CreateNodeFromTex(human->LegacyBodyDecal); + var hasLegacyDecal = (human->Customize[(int)CustomizeIndex.FaceFeatures] & 0x80) != 0; + var legacyDecalPath = hasLegacyDecal + ? GamePaths.Human.Decal.LegacyDecalPath + : GamePaths.Tex.TransparentPath; + var legacyDecalNode = genericContext.CreateNodeFromTex(human->LegacyBodyDecal, legacyDecalPath); if (legacyDecalNode != null) { if (globalContext.WithUiData) @@ -194,7 +215,7 @@ public class ResourceTree for (var i = 0; i < skeleton->PartialSkeletonCount; ++i) { - var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]); + var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], (uint)i); if (sklbNode != null) { if (context.Global.WithUiData) diff --git a/Penumbra/Interop/Structs/CharacterBaseUtility.cs b/Penumbra/Interop/Structs/CharacterBaseUtility.cs new file mode 100644 index 00000000..c29f44a3 --- /dev/null +++ b/Penumbra/Interop/Structs/CharacterBaseUtility.cs @@ -0,0 +1,62 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.String; + +namespace Penumbra.Interop.Structs; + +// TODO submit these to ClientStructs +public static unsafe class CharacterBaseUtility +{ + private const int PathBufferSize = 260; + + private const uint ResolveSklbPathVf = 72; + private const uint ResolveMdlPathVf = 73; + private const uint ResolveSkpPathVf = 74; + private const uint ResolveImcPathVf = 81; + private const uint ResolveMtrlPathVf = 82; + private const uint ResolveEidPathVf = 85; + + private static void* GetVFunc(CharacterBase* characterBase, uint vfIndex) + => ((void**)characterBase->VTable)[vfIndex]; + + private static ByteString? ResolvePath(CharacterBase* characterBase, uint vfIndex) + { + var vFunc = (delegate* unmanaged)GetVFunc(characterBase, vfIndex); + var pathBuffer = stackalloc byte[PathBufferSize]; + var path = vFunc(characterBase, pathBuffer, PathBufferSize); + return path != null ? new ByteString(path).Clone() : null; + } + + private static ByteString? ResolvePath(CharacterBase* characterBase, uint vfIndex, uint slotIndex) + { + var vFunc = (delegate* unmanaged)GetVFunc(characterBase, vfIndex); + var pathBuffer = stackalloc byte[PathBufferSize]; + var path = vFunc(characterBase, pathBuffer, PathBufferSize, slotIndex); + return path != null ? new ByteString(path).Clone() : null; + } + + private static ByteString? ResolvePath(CharacterBase* characterBase, uint vfIndex, uint slotIndex, byte* name) + { + var vFunc = (delegate* unmanaged)GetVFunc(characterBase, vfIndex); + var pathBuffer = stackalloc byte[PathBufferSize]; + var path = vFunc(characterBase, pathBuffer, PathBufferSize, slotIndex, name); + return path != null ? new ByteString(path).Clone() : null; + } + + public static ByteString? ResolveEidPath(CharacterBase* characterBase) + => ResolvePath(characterBase, ResolveEidPathVf); + + public static ByteString? ResolveImcPath(CharacterBase* characterBase, uint slotIndex) + => ResolvePath(characterBase, ResolveImcPathVf, slotIndex); + + public static ByteString? ResolveMdlPath(CharacterBase* characterBase, uint slotIndex) + => ResolvePath(characterBase, ResolveMdlPathVf, slotIndex); + + public static ByteString? ResolveMtrlPath(CharacterBase* characterBase, uint slotIndex, byte* mtrlFileName) + => ResolvePath(characterBase, ResolveMtrlPathVf, slotIndex, mtrlFileName); + + public static ByteString? ResolveSklbPath(CharacterBase* characterBase, uint partialSkeletonIndex) + => ResolvePath(characterBase, ResolveSklbPathVf, partialSkeletonIndex); + + public static ByteString? ResolveSkpPath(CharacterBase* characterBase, uint partialSkeletonIndex) + => ResolvePath(characterBase, ResolveSkpPathVf, partialSkeletonIndex); +}