mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
ResourceTree: Use ResolveXXXPath where possible
This commit is contained in:
parent
c024d7e826
commit
2852562a03
4 changed files with 125 additions and 33 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 04ddadb44600a382e26661e1db08fd16c3b671d8
|
Subproject commit b141301c4ee65422d6802f3038c8f344911d4ae2
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using FFXIVClientStructs.Interop;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
|
|
@ -9,6 +11,7 @@ using Penumbra.GameData.Structs;
|
||||||
using Penumbra.String;
|
using Penumbra.String;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
|
using static Penumbra.Interop.Structs.CharacterBaseUtility;
|
||||||
using static Penumbra.Interop.Structs.StructExtensions;
|
using static Penumbra.Interop.Structs.StructExtensions;
|
||||||
|
|
||||||
namespace Penumbra.Interop.ResourceTree;
|
namespace Penumbra.Interop.ResourceTree;
|
||||||
|
|
@ -18,11 +21,11 @@ internal record GlobalResolveContext(IObjectIdentifier Identifier, TreeBuildCach
|
||||||
{
|
{
|
||||||
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
public readonly Dictionary<(Utf8GamePath, nint), ResourceNode> Nodes = new(128);
|
||||||
|
|
||||||
public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment)
|
public unsafe ResolveContext CreateContext(CharacterBase* characterBase, uint slotIndex, EquipSlot slot, CharacterArmor equipment)
|
||||||
=> new(this, slot, 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);
|
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)
|
if (eid == null)
|
||||||
return 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);
|
return GetOrCreateNode(ResourceType.Eid, 0, eid, path);
|
||||||
}
|
}
|
||||||
|
|
@ -122,17 +126,19 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char
|
||||||
if (imc == null)
|
if (imc == null)
|
||||||
return 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);
|
return GetOrCreateNode(ResourceType.Imc, 0, imc, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex)
|
public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex, string gamePath)
|
||||||
{
|
{
|
||||||
if (tex == null)
|
if (tex == null)
|
||||||
return 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);
|
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)
|
if (mdl == null || mdl->ModelResourceHandle == null)
|
||||||
return 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))
|
if (Global.Nodes.TryGetValue((path, (nint)mdl->ModelResourceHandle), out var cached))
|
||||||
return cached;
|
return cached;
|
||||||
|
|
@ -253,12 +260,13 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb)
|
public unsafe ResourceNode? CreateNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex)
|
||||||
{
|
{
|
||||||
if (sklb == null || sklb->SkeletonResourceHandle == null)
|
if (sklb == null || sklb->SkeletonResourceHandle == null)
|
||||||
return 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))
|
if (Global.Nodes.TryGetValue((path, (nint)sklb->SkeletonResourceHandle), out var cached))
|
||||||
return 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);
|
var node = CreateNode(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, path, false);
|
||||||
if (node != null)
|
if (node != null)
|
||||||
{
|
{
|
||||||
var skpNode = CreateParameterNodeFromPartialSkeleton(sklb);
|
var skpNode = CreateParameterNodeFromPartialSkeleton(sklb, partialSkeletonIndex);
|
||||||
if (skpNode != null)
|
if (skpNode != null)
|
||||||
node.Children.Add(skpNode);
|
node.Children.Add(skpNode);
|
||||||
Global.Nodes.Add((path, (nint)sklb->SkeletonResourceHandle), node);
|
Global.Nodes.Add((path, (nint)sklb->SkeletonResourceHandle), node);
|
||||||
|
|
@ -275,12 +283,13 @@ internal record ResolveContext(GlobalResolveContext Global, EquipSlot Slot, Char
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe ResourceNode? CreateParameterNodeFromPartialSkeleton(PartialSkeleton* sklb)
|
private unsafe ResourceNode? CreateParameterNodeFromPartialSkeleton(PartialSkeleton* sklb, uint partialSkeletonIndex)
|
||||||
{
|
{
|
||||||
if (sklb == null || sklb->SkeletonParameterResourceHandle == null)
|
if (sklb == null || sklb->SkeletonParameterResourceHandle == null)
|
||||||
return 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))
|
if (Global.Nodes.TryGetValue((path, (nint)sklb->SkeletonParameterResourceHandle), out var cached))
|
||||||
return cached;
|
return cached;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
|
|
@ -62,8 +64,10 @@ public class ResourceTree
|
||||||
CustomizeData = character->DrawData.CustomizeData;
|
CustomizeData = character->DrawData.CustomizeData;
|
||||||
RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown;
|
RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown;
|
||||||
|
|
||||||
|
var genericContext = globalContext.CreateContext(model, 0xFFFFFFFFu, EquipSlot.Unknown, default);
|
||||||
|
|
||||||
var eid = (ResourceHandle*)model->EID;
|
var eid = (ResourceHandle*)model->EID;
|
||||||
var eidNode = globalContext.CreateContext(EquipSlot.Unknown, default).CreateNodeFromEid(eid);
|
var eidNode = genericContext.CreateNodeFromEid(eid);
|
||||||
if (eidNode != null)
|
if (eidNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
|
|
@ -73,13 +77,15 @@ public class ResourceTree
|
||||||
|
|
||||||
for (var i = 0; i < model->SlotCount; ++i)
|
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 ? ((uint)i).ToEquipSlot() : EquipSlot.Unknown,
|
||||||
i < equipment.Length ? equipment[i] : default
|
i < equipment.Length ? equipment[i] : default
|
||||||
);
|
);
|
||||||
|
|
||||||
var imc = (ResourceHandle*)model->IMCArray[i];
|
var imc = (ResourceHandle*)model->IMCArray[i];
|
||||||
var imcNode = context.CreateNodeFromImc(imc);
|
var imcNode = slotContext.CreateNodeFromImc(imc);
|
||||||
if (imcNode != null)
|
if (imcNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
|
|
@ -88,7 +94,7 @@ public class ResourceTree
|
||||||
}
|
}
|
||||||
|
|
||||||
var mdl = model->Models[i];
|
var mdl = model->Models[i];
|
||||||
var mdlNode = context.CreateNodeFromRenderModel(mdl);
|
var mdlNode = slotContext.CreateNodeFromRenderModel(mdl);
|
||||||
if (mdlNode != null)
|
if (mdlNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
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)
|
if (human != null)
|
||||||
AddHumanResources(globalContext, human);
|
AddHumanResources(globalContext, human);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void AddHumanResources(GlobalResolveContext globalContext, Human* human)
|
private unsafe void AddSubObjects(GlobalResolveContext globalContext, CharacterBase* model)
|
||||||
{
|
{
|
||||||
var subObjectIndex = 0;
|
var subObjectIndex = 0;
|
||||||
var weaponIndex = 0;
|
var weaponIndex = 0;
|
||||||
var subObjectNodes = new List<ResourceNode>();
|
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)
|
if (baseSubObject->GetObjectType() != FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType.CharacterBase)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -117,13 +125,13 @@ public class ResourceTree
|
||||||
var weapon = subObject->GetModelType() == CharacterBase.ModelType.Weapon ? (Weapon*)subObject : null;
|
var weapon = subObject->GetModelType() == CharacterBase.ModelType.Weapon ? (Weapon*)subObject : null;
|
||||||
var subObjectNamePrefix = weapon != null ? "Weapon" : "Fashion Acc.";
|
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.
|
// 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(
|
var slot = weapon != null ? (weaponIndex > 0 ? EquipSlot.OffHand : EquipSlot.MainHand) : EquipSlot.Unknown;
|
||||||
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;
|
||||||
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 eid = (ResourceHandle*)subObject->EID;
|
||||||
var eidNode = subObjectContext.CreateNodeFromEid(eid);
|
var eidNode = genericContext.CreateNodeFromEid(eid);
|
||||||
if (eidNode != null)
|
if (eidNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
|
|
@ -133,8 +141,10 @@ public class ResourceTree
|
||||||
|
|
||||||
for (var i = 0; i < subObject->SlotCount; ++i)
|
for (var i = 0; i < subObject->SlotCount; ++i)
|
||||||
{
|
{
|
||||||
|
var slotContext = globalContext.CreateContext(subObject, (uint)i, slot, equipment);
|
||||||
|
|
||||||
var imc = (ResourceHandle*)subObject->IMCArray[i];
|
var imc = (ResourceHandle*)subObject->IMCArray[i];
|
||||||
var imcNode = subObjectContext.CreateNodeFromImc(imc);
|
var imcNode = slotContext.CreateNodeFromImc(imc);
|
||||||
if (imcNode != null)
|
if (imcNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
|
|
@ -143,7 +153,7 @@ public class ResourceTree
|
||||||
}
|
}
|
||||||
|
|
||||||
var mdl = subObject->Models[i];
|
var mdl = subObject->Models[i];
|
||||||
var mdlNode = subObjectContext.CreateNodeFromRenderModel(mdl);
|
var mdlNode = slotContext.CreateNodeFromRenderModel(mdl);
|
||||||
if (mdlNode != null)
|
if (mdlNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
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;
|
++subObjectIndex;
|
||||||
if (weapon != null)
|
if (weapon != null)
|
||||||
++weaponIndex;
|
++weaponIndex;
|
||||||
}
|
}
|
||||||
Nodes.InsertRange(0, subObjectNodes);
|
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 (decalNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
|
|
@ -174,7 +191,11 @@ public class ResourceTree
|
||||||
Nodes.Add(decalNode);
|
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 (legacyDecalNode != null)
|
||||||
{
|
{
|
||||||
if (globalContext.WithUiData)
|
if (globalContext.WithUiData)
|
||||||
|
|
@ -194,7 +215,7 @@ public class ResourceTree
|
||||||
|
|
||||||
for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
|
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 (sklbNode != null)
|
||||||
{
|
{
|
||||||
if (context.Global.WithUiData)
|
if (context.Global.WithUiData)
|
||||||
|
|
|
||||||
62
Penumbra/Interop/Structs/CharacterBaseUtility.cs
Normal file
62
Penumbra/Interop/Structs/CharacterBaseUtility.cs
Normal 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);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue