From a97d9e49531ace985eeef1c305bdd123b11dfa38 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Sat, 5 Jul 2025 04:37:37 +0200 Subject: [PATCH 1/2] Add Human skin material handling --- .../Hooks/Resources/ResolvePathHooksBase.cs | 40 ++++++++----- .../Interop/ResourceTree/ResolveContext.cs | 9 ++- Penumbra/Interop/ResourceTree/ResourceTree.cs | 12 +++- Penumbra/Interop/Structs/StructExtensions.cs | 60 +++++++++++-------- 4 files changed, 76 insertions(+), 45 deletions(-) diff --git a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs index 8a45ec2c..85fb1098 100644 --- a/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs +++ b/Penumbra/Interop/Hooks/Resources/ResolvePathHooksBase.cs @@ -35,6 +35,7 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable private readonly Hook _resolveMPapPathHook; private readonly Hook _resolveMdlPathHook; private readonly Hook _resolveMtrlPathHook; + private readonly Hook _resolveSkinMtrlPathHook; private readonly Hook _resolvePapPathHook; private readonly Hook _resolveKdbPathHook; private readonly Hook _resolvePhybPathHook; @@ -52,22 +53,23 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable { _parent = parent; // @formatter:off - _resolveSklbPathHook = Create($"{name}.{nameof(ResolveSklb)}", hooks, vTable[76], type, ResolveSklb, ResolveSklbHuman); - _resolveMdlPathHook = Create($"{name}.{nameof(ResolveMdl)}", hooks, vTable[77], type, ResolveMdl, ResolveMdlHuman); - _resolveSkpPathHook = Create($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman); - _resolvePhybPathHook = Create($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[79], type, ResolvePhyb, ResolvePhybHuman); - _resolveKdbPathHook = Create($"{name}.{nameof(ResolveKdb)}", hooks, vTable[80], type, ResolveKdb, ResolveKdbHuman); - _vFunc81Hook = Create( $"{name}.{nameof(VFunc81)}", hooks, vTable[81], type, null, VFunc81); - _resolveBnmbPathHook = Create($"{name}.{nameof(ResolveBnmb)}", hooks, vTable[82], type, ResolveBnmb, ResolveBnmbHuman); - _vFunc83Hook = Create( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83); - _resolvePapPathHook = Create( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman); - _resolveTmbPathHook = Create( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb); - _resolveMPapPathHook = Create( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap); - _resolveImcPathHook = Create($"{name}.{nameof(ResolveImc)}", hooks, vTable[89], ResolveImc); - _resolveMtrlPathHook = Create( $"{name}.{nameof(ResolveMtrl)}", hooks, vTable[90], ResolveMtrl); - _resolveDecalPathHook = Create($"{name}.{nameof(ResolveDecal)}", hooks, vTable[92], ResolveDecal); - _resolveVfxPathHook = Create( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[93], type, ResolveVfx, ResolveVfxHuman); - _resolveEidPathHook = Create( $"{name}.{nameof(ResolveEid)}", hooks, vTable[94], ResolveEid); + _resolveSklbPathHook = Create($"{name}.{nameof(ResolveSklb)}", hooks, vTable[76], type, ResolveSklb, ResolveSklbHuman); + _resolveMdlPathHook = Create($"{name}.{nameof(ResolveMdl)}", hooks, vTable[77], type, ResolveMdl, ResolveMdlHuman); + _resolveSkpPathHook = Create($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman); + _resolvePhybPathHook = Create($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[79], type, ResolvePhyb, ResolvePhybHuman); + _resolveKdbPathHook = Create($"{name}.{nameof(ResolveKdb)}", hooks, vTable[80], type, ResolveKdb, ResolveKdbHuman); + _vFunc81Hook = Create( $"{name}.{nameof(VFunc81)}", hooks, vTable[81], type, null, VFunc81); + _resolveBnmbPathHook = Create($"{name}.{nameof(ResolveBnmb)}", hooks, vTable[82], type, ResolveBnmb, ResolveBnmbHuman); + _vFunc83Hook = Create( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83); + _resolvePapPathHook = Create( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman); + _resolveTmbPathHook = Create( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb); + _resolveMPapPathHook = Create( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap); + _resolveImcPathHook = Create($"{name}.{nameof(ResolveImc)}", hooks, vTable[89], ResolveImc); + _resolveMtrlPathHook = Create( $"{name}.{nameof(ResolveMtrl)}", hooks, vTable[90], ResolveMtrl); + _resolveSkinMtrlPathHook = Create($"{name}.{nameof(ResolveSkinMtrl)}", hooks, vTable[91], ResolveSkinMtrl); + _resolveDecalPathHook = Create($"{name}.{nameof(ResolveDecal)}", hooks, vTable[92], ResolveDecal); + _resolveVfxPathHook = Create( $"{name}.{nameof(ResolveVfx)}", hooks, vTable[93], type, ResolveVfx, ResolveVfxHuman); + _resolveEidPathHook = Create( $"{name}.{nameof(ResolveEid)}", hooks, vTable[94], ResolveEid); // @formatter:on @@ -83,6 +85,7 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable _resolveMPapPathHook.Enable(); _resolveMdlPathHook.Enable(); _resolveMtrlPathHook.Enable(); + _resolveSkinMtrlPathHook.Enable(); _resolvePapPathHook.Enable(); _resolveKdbPathHook.Enable(); _resolvePhybPathHook.Enable(); @@ -103,6 +106,7 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable _resolveMPapPathHook.Disable(); _resolveMdlPathHook.Disable(); _resolveMtrlPathHook.Disable(); + _resolveSkinMtrlPathHook.Disable(); _resolvePapPathHook.Disable(); _resolveKdbPathHook.Disable(); _resolvePhybPathHook.Disable(); @@ -123,6 +127,7 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable _resolveMPapPathHook.Dispose(); _resolveMdlPathHook.Dispose(); _resolveMtrlPathHook.Dispose(); + _resolveSkinMtrlPathHook.Dispose(); _resolvePapPathHook.Dispose(); _resolveKdbPathHook.Dispose(); _resolvePhybPathHook.Dispose(); @@ -153,6 +158,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable private nint ResolveMtrl(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex, nint mtrlFileName) => ResolvePath(drawObject, _resolveMtrlPathHook.Original(drawObject, pathBuffer, pathBufferSize, slotIndex, mtrlFileName)); + private nint ResolveSkinMtrl(nint drawObject, nint pathBuffer, nint pathBufferSize, uint slotIndex) + => ResolvePath(drawObject, _resolveSkinMtrlPathHook.Original(drawObject, pathBuffer, pathBufferSize, slotIndex)); + private nint ResolvePap(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName) => ResolvePath(drawObject, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName)); diff --git a/Penumbra/Interop/ResourceTree/ResolveContext.cs b/Penumbra/Interop/ResourceTree/ResolveContext.cs index 013d7db7..64a91302 100644 --- a/Penumbra/Interop/ResourceTree/ResolveContext.cs +++ b/Penumbra/Interop/ResourceTree/ResolveContext.cs @@ -188,7 +188,8 @@ internal unsafe partial record ResolveContext( return GetOrCreateNode(ResourceType.Tex, (nint)tex->Texture, &tex->ResourceHandle, gamePath); } - public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc, TextureResourceHandle* decalHandle, ResourceHandle* mpapHandle) + public ResourceNode? CreateNodeFromModel(Model* mdl, ResourceHandle* imc, TextureResourceHandle* decalHandle, + MaterialResourceHandle* skinMtrlHandle, ResourceHandle* mpapHandle) { if (mdl is null || mdl->ModelResourceHandle is null) return null; @@ -218,6 +219,12 @@ internal unsafe partial record ResolveContext( } } + if (skinMtrlHandle is not null + && Utf8GamePath.FromByteString(CharacterBase->ResolveSkinMtrlPathAsByteString(SlotIndex), out var skinMtrlPath) + && CreateNodeFromMaterial(skinMtrlHandle->Material, skinMtrlPath) is + { } skinMaaterialNode) + node.Children.Add(skinMaaterialNode); + if (CreateNodeFromDecal(decalHandle, imc) is { } decalNode) node.Children.Add(decalNode); diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 7be8694a..97a926ad 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -73,6 +73,12 @@ public class ResourceTree( // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) var mpapArrayPtr = *(ResourceHandle***)((nint)model + 0x948); var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, model->SlotCount) : []; + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1474) + var skinMtrlArray = modelType switch + { + ModelType.Human => new ReadOnlySpan>((MaterialResourceHandle**)((nint)model + 0xB48), 5), + _ => [], + }; var decalArray = modelType switch { ModelType.Human => human->SlotDecalsSpan, @@ -108,7 +114,8 @@ public class ResourceTree( var mdl = model->Models[i]; if (slotContext.CreateNodeFromModel(mdl, imc, i < decalArray.Length ? decalArray[(int)i].Value : null, - i < mpapArray.Length ? mpapArray[(int)i].Value : null) is { } mdlNode) + i < skinMtrlArray.Length ? skinMtrlArray[(int)i].Value : null, i < mpapArray.Length ? mpapArray[(int)i].Value : null) is + { } mdlNode) { if (globalContext.WithUiData) mdlNode.FallbackName = $"Model #{i}"; @@ -166,7 +173,8 @@ public class ResourceTree( } var mdl = subObject->Models[i]; - if (slotContext.CreateNodeFromModel(mdl, imc, weapon->Decal, i < mpapArray.Length ? mpapArray[i].Value : null) is { } mdlNode) + if (slotContext.CreateNodeFromModel(mdl, imc, weapon->Decal, null, i < mpapArray.Length ? mpapArray[i].Value : null) is + { } mdlNode) { if (globalContext.WithUiData) mdlNode.FallbackName = $"Weapon #{weaponIndex}, Model #{i}"; diff --git a/Penumbra/Interop/Structs/StructExtensions.cs b/Penumbra/Interop/Structs/StructExtensions.cs index 03b4cf36..62dca02e 100644 --- a/Penumbra/Interop/Structs/StructExtensions.cs +++ b/Penumbra/Interop/Structs/StructExtensions.cs @@ -10,28 +10,36 @@ internal static class StructExtensions public static CiByteString AsByteString(in this StdString str) => CiByteString.FromSpanUnsafe(str.AsSpan(), true); - public static CiByteString ResolveEidPathAsByteString(ref this CharacterBase character) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveEidPath(pathBuffer)); - } - - public static CiByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex)); + public static CiByteString ResolveEidPathAsByteString(ref this CharacterBase character) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveEidPath(pathBuffer)); } - public static CiByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex)); + public static CiByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex)); } - public static unsafe CiByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName) - { - var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); + public static CiByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex)); + } + + public static unsafe CiByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName) + { + var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); + } + + public static unsafe CiByteString ResolveSkinMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex) + { + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1474) + var vf91 = (delegate* unmanaged)((nint*)character.VirtualTable)[91]; + var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(vf91((CharacterBase*)Unsafe.AsPointer(ref character), pathBuffer, CharacterBase.PathBufferSize, slotIndex)); } public static CiByteString ResolveMaterialPapPathAsByteString(ref this CharacterBase character, uint slotIndex, uint unkSId) @@ -40,16 +48,16 @@ internal static class StructExtensions return ToOwnedByteString(character.ResolveMaterialPapPath(pathBuffer, slotIndex, unkSId)); } - public static CiByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex)); + public static CiByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex)); } - public static CiByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) - { - Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); + public static CiByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) + { + Span pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; + return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); } public static CiByteString ResolvePhybPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) From 278bf43b29809ff4c0657921311f8581c820b9b8 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Sat, 5 Jul 2025 05:20:24 +0200 Subject: [PATCH 2/2] ClientStructs-ify ResourceTree stuff --- Penumbra/Interop/ResourceTree/ResourceTree.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 97a926ad..e7c4b11b 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -70,8 +70,7 @@ public class ResourceTree( var genericContext = globalContext.CreateContext(model); - // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) - var mpapArrayPtr = *(ResourceHandle***)((nint)model + 0x948); + var mpapArrayPtr = model->MaterialAnimationPacks; var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, model->SlotCount) : []; // TODO ClientStructs-ify (aers/FFXIVClientStructs#1474) var skinMtrlArray = modelType switch @@ -124,8 +123,7 @@ public class ResourceTree( } AddSkeleton(Nodes, genericContext, model->EID, model->Skeleton, model->BonePhysicsModule); - // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) - AddMaterialAnimationSkeleton(Nodes, genericContext, *(SkeletonResourceHandle**)((nint)model + 0x940)); + AddMaterialAnimationSkeleton(Nodes, genericContext, model->MaterialAnimationSkeleton); AddWeapons(globalContext, model); @@ -156,8 +154,7 @@ public class ResourceTree( var genericContext = globalContext.CreateContext(subObject, 0xFFFFFFFFu, slot, equipment, weaponType); - // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) - var mpapArrayPtr = *(ResourceHandle***)((nint)subObject + 0x948); + var mpapArrayPtr = subObject->MaterialAnimationPacks; var mpapArray = mpapArrayPtr is not null ? new ReadOnlySpan>(mpapArrayPtr, subObject->SlotCount) : []; for (var i = 0; i < subObject->SlotCount; ++i) @@ -184,8 +181,7 @@ public class ResourceTree( AddSkeleton(weaponNodes, genericContext, subObject->EID, subObject->Skeleton, subObject->BonePhysicsModule, $"Weapon #{weaponIndex}, "); - // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) - AddMaterialAnimationSkeleton(weaponNodes, genericContext, *(SkeletonResourceHandle**)((nint)subObject + 0x940), + AddMaterialAnimationSkeleton(weaponNodes, genericContext, subObject->MaterialAnimationSkeleton, $"Weapon #{weaponIndex}, "); ++weaponIndex; @@ -263,7 +259,7 @@ public class ResourceTree( for (var i = 0; i < skeleton->PartialSkeletonCount; ++i) { - // TODO ClientStructs-ify (aers/FFXIVClientStructs#1312) + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1475) var phybHandle = physics != null ? ((ResourceHandle**)((nint)physics + 0x190))[i] : null; if (context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], phybHandle, (uint)i) is { } sklbNode) {