ResourceTree: Use ResolveXXXPath where possible

This commit is contained in:
Exter-N 2023-11-02 22:41:20 +01:00
parent c024d7e826
commit 2852562a03
4 changed files with 125 additions and 33 deletions

@ -1 +1 @@
Subproject commit 04ddadb44600a382e26661e1db08fd16c3b671d8
Subproject commit b141301c4ee65422d6802f3038c8f344911d4ae2

View file

@ -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> 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;

View file

@ -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<ResourceNode>();
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)

View file

@ -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<CharacterBase*, byte*, nint, byte*>)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<CharacterBase*, byte*, nint, uint, byte*>)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<CharacterBase*, byte*, nint, uint, byte*, byte*>)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);
}