mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Resource Tree: Deduplicate nodes, add skp
This commit is contained in:
parent
ccc0b51a99
commit
db521dd21c
4 changed files with 114 additions and 34 deletions
|
|
@ -11,21 +11,27 @@ using Penumbra.GameData.Structs;
|
|||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.String;
|
||||
using Penumbra.String.Classes;
|
||||
using static Penumbra.GameData.Files.ShpkFile;
|
||||
|
||||
namespace Penumbra.Interop.ResourceTree;
|
||||
|
||||
internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames)
|
||||
{
|
||||
public readonly Dictionary<nint, ResourceNode> Nodes = new(128);
|
||||
|
||||
public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment)
|
||||
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithNames, slot, equipment);
|
||||
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithNames, Nodes, slot, equipment);
|
||||
}
|
||||
|
||||
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames, EquipSlot Slot,
|
||||
CharacterArmor Equipment)
|
||||
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames,
|
||||
Dictionary<nint, ResourceNode> Nodes, EquipSlot Slot, CharacterArmor Equipment)
|
||||
{
|
||||
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
|
||||
private unsafe ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, ByteString gamePath, bool @internal)
|
||||
{
|
||||
if (Nodes.TryGetValue((nint)resourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
if (gamePath.IsEmpty)
|
||||
return null;
|
||||
if (!Utf8GamePath.FromByteString(ByteString.Join((byte)'/', ShpkPrefix, gamePath), out var path, false))
|
||||
|
|
@ -36,6 +42,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
|
||||
private unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, ByteString gamePath, bool @internal, bool dx11)
|
||||
{
|
||||
if (Nodes.TryGetValue((nint)resourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
if (dx11)
|
||||
{
|
||||
var lastDirectorySeparator = gamePath.LastIndexOf((byte)'/');
|
||||
|
|
@ -69,7 +78,11 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (fullPath.InternalName.IsEmpty)
|
||||
fullPath = Collection.ResolvePath(gamePath) ?? new FullPath(gamePath);
|
||||
|
||||
return new(null, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
|
||||
var node = new ResourceNode(null, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
|
||||
if (resourceHandle != null)
|
||||
Nodes.Add((nint)resourceHandle, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private unsafe ResourceNode? CreateNodeFromResourceHandle(ResourceType type, nint objectAddress, ResourceHandle* handle, bool @internal,
|
||||
|
|
@ -95,6 +108,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
|
||||
return new ResourceNode(null, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal);
|
||||
}
|
||||
|
||||
public unsafe ResourceNode? CreateHumanSkeletonNode(GenderRace gr)
|
||||
{
|
||||
var raceSexIdStr = gr.ToRaceCode();
|
||||
|
|
@ -108,6 +122,9 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
|
||||
public unsafe ResourceNode? CreateNodeFromImc(ResourceHandle* imc)
|
||||
{
|
||||
if (Nodes.TryGetValue((nint)imc, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Imc, 0, imc, true, false);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
|
@ -118,17 +135,31 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
node = node.WithName(name != null ? $"IMC: {name}" : null);
|
||||
}
|
||||
|
||||
Nodes.Add((nint)imc, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public unsafe ResourceNode? CreateNodeFromTex(TextureResourceHandle* tex)
|
||||
=> CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithNames);
|
||||
{
|
||||
if (Nodes.TryGetValue((nint)tex, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithNames);
|
||||
if (node != null)
|
||||
Nodes.Add((nint)tex, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public unsafe ResourceNode? CreateNodeFromRenderModel(RenderModel* mdl)
|
||||
{
|
||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||
return null;
|
||||
|
||||
if (Nodes.TryGetValue((nint)mdl->ResourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Mdl, (nint) mdl, mdl->ResourceHandle, false, false);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
|
@ -148,6 +179,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
: mtrlNode);
|
||||
}
|
||||
|
||||
Nodes.Add((nint)mdl->ResourceHandle, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +221,10 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (mtrl == null)
|
||||
return null;
|
||||
|
||||
var resource = mtrl->ResourceHandle;
|
||||
var resource = mtrl->ResourceHandle;
|
||||
if (Nodes.TryGetValue((nint)resource, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithNames);
|
||||
if (node == null)
|
||||
return null;
|
||||
|
|
@ -231,6 +267,8 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
}
|
||||
}
|
||||
|
||||
Nodes.Add((nint)resource, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +277,29 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
if (sklb->SkeletonResourceHandle == null)
|
||||
return null;
|
||||
|
||||
return CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithNames);
|
||||
if (Nodes.TryGetValue((nint)sklb->SkeletonResourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithNames);
|
||||
if (node != null)
|
||||
Nodes.Add((nint)sklb->SkeletonResourceHandle, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public unsafe ResourceNode? CreateParameterNodeFromPartialSkeleton(FFXIVClientStructs.FFXIV.Client.Graphics.Render.PartialSkeleton* sklb)
|
||||
{
|
||||
if (sklb->SkeletonParameterResourceHandle == null)
|
||||
return null;
|
||||
|
||||
if (Nodes.TryGetValue((nint)sklb->SkeletonParameterResourceHandle, out var cached))
|
||||
return cached;
|
||||
|
||||
var node = CreateNodeFromResourceHandle(ResourceType.Skp, (nint)sklb, (ResourceHandle*)sklb->SkeletonParameterResourceHandle, true, WithNames);
|
||||
if (node != null)
|
||||
Nodes.Add((nint)sklb->SkeletonParameterResourceHandle, node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private FullPath FilterFullPath(FullPath fullPath)
|
||||
|
|
@ -356,7 +416,7 @@ internal record class ResolveContext(Configuration Config, IObjectIdentifier Ide
|
|||
return name;
|
||||
}
|
||||
|
||||
static unsafe ulong GetResourceHandleLength(ResourceHandle* handle)
|
||||
private static unsafe ulong GetResourceHandleLength(ResourceHandle* handle)
|
||||
{
|
||||
if (handle == null)
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -13,29 +13,33 @@ namespace Penumbra.Interop.ResourceTree;
|
|||
|
||||
public class ResourceTree
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly nint SourceAddress;
|
||||
public readonly bool PlayerRelated;
|
||||
public readonly string CollectionName;
|
||||
public readonly List<ResourceNode> Nodes;
|
||||
public readonly string Name;
|
||||
public readonly nint GameObjectAddress;
|
||||
public readonly nint DrawObjectAddress;
|
||||
public readonly bool PlayerRelated;
|
||||
public readonly string CollectionName;
|
||||
public readonly List<ResourceNode> Nodes;
|
||||
public readonly HashSet<ResourceNode> FlatNodes;
|
||||
|
||||
public int ModelId;
|
||||
public CustomizeData CustomizeData;
|
||||
public GenderRace RaceCode;
|
||||
|
||||
public ResourceTree(string name, nint sourceAddress, bool playerRelated, string collectionName)
|
||||
public ResourceTree(string name, nint gameObjectAddress, nint drawObjectAddress, bool playerRelated, string collectionName)
|
||||
{
|
||||
Name = name;
|
||||
SourceAddress = sourceAddress;
|
||||
PlayerRelated = playerRelated;
|
||||
CollectionName = collectionName;
|
||||
Nodes = new List<ResourceNode>();
|
||||
Name = name;
|
||||
GameObjectAddress = gameObjectAddress;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
PlayerRelated = playerRelated;
|
||||
CollectionName = collectionName;
|
||||
Nodes = new List<ResourceNode>();
|
||||
FlatNodes = new HashSet<ResourceNode>();
|
||||
}
|
||||
|
||||
internal unsafe void LoadResources(GlobalResolveContext globalContext)
|
||||
{
|
||||
var character = (Character*)SourceAddress;
|
||||
var model = (CharacterBase*)character->GameObject.GetDrawObject();
|
||||
var character = (Character*)GameObjectAddress;
|
||||
var model = (CharacterBase*)DrawObjectAddress;
|
||||
var equipment = new ReadOnlySpan<CharacterArmor>(&character->DrawData.Head, 10);
|
||||
// var customize = new ReadOnlySpan<byte>( character->CustomizeData, 26 );
|
||||
ModelId = character->CharacterData.ModelCharaId;
|
||||
|
|
@ -130,6 +134,10 @@ public class ResourceTree
|
|||
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
|
||||
if (sklbNode != null)
|
||||
nodes.Add(context.WithNames ? sklbNode.WithName($"{prefix}Skeleton #{i}") : sklbNode);
|
||||
|
||||
var skpNode = context.CreateParameterNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
|
||||
if (skpNode != null)
|
||||
nodes.Add(context.WithNames ? skpNode.WithName($"{prefix}Skeleton #{i} Parameters") : skpNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ public class ResourceTreeFactory
|
|||
return null;
|
||||
|
||||
var gameObjStruct = (GameObject*)character.Address;
|
||||
if (gameObjStruct->GetDrawObject() == null)
|
||||
var drawObjStruct = gameObjStruct->GetDrawObject();
|
||||
if (drawObjStruct == null)
|
||||
return null;
|
||||
|
||||
var collectionResolveData = _collectionResolver.IdentifyCollection(gameObjStruct, true);
|
||||
|
|
@ -70,10 +71,11 @@ public class ResourceTreeFactory
|
|||
return null;
|
||||
|
||||
var (name, related) = GetCharacterName(character, cache);
|
||||
var tree = new ResourceTree(name, (nint)gameObjStruct, related, collectionResolveData.ModCollection.Name);
|
||||
var tree = new ResourceTree(name, (nint)gameObjStruct, (nint)drawObjStruct, related, collectionResolveData.ModCollection.Name);
|
||||
var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection,
|
||||
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withNames);
|
||||
tree.LoadResources(globalContext);
|
||||
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
|
||||
return tree;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class ResourceTreeViewer
|
|||
private readonly int _actionCapacity;
|
||||
private readonly Action _onRefresh;
|
||||
private readonly Action<ResourceNode, Vector2> _drawActions;
|
||||
private readonly HashSet<ResourceNode> _unfolded;
|
||||
private readonly HashSet<nint> _unfolded;
|
||||
|
||||
private Task<ResourceTree[]>? _task;
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ public class ResourceTreeViewer
|
|||
_actionCapacity = actionCapacity;
|
||||
_onRefresh = onRefresh;
|
||||
_drawActions = drawActions;
|
||||
_unfolded = new HashSet<ResourceNode>();
|
||||
_unfolded = new HashSet<nint>();
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -82,7 +82,7 @@ public class ResourceTreeViewer
|
|||
(_actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + _actionCapacity * ImGui.GetFrameHeight());
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
DrawNodes(tree.Nodes, 0);
|
||||
DrawNodes(tree.Nodes, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ public class ResourceTreeViewer
|
|||
}
|
||||
});
|
||||
|
||||
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level)
|
||||
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash)
|
||||
{
|
||||
var debugMode = _config.DebugMode;
|
||||
var frameHeight = ImGui.GetFrameHeight();
|
||||
|
|
@ -109,26 +109,36 @@ public class ResourceTreeViewer
|
|||
foreach (var (resourceNode, index) in resourceNodes.WithIndex())
|
||||
{
|
||||
if (resourceNode.Internal && !debugMode)
|
||||
continue;
|
||||
continue;
|
||||
|
||||
var textColor = ImGui.GetColorU32(ImGuiCol.Text);
|
||||
var textColorInternal = (textColor & 0x00FFFFFFu) | ((textColor & 0xFE000000u) >> 1); // Half opacity
|
||||
|
||||
using var mutedColor = ImRaii.PushColor(ImGuiCol.Text, textColorInternal, resourceNode.Internal);
|
||||
|
||||
var nodePathHash = unchecked(pathHash + resourceNode.ResourceHandle);
|
||||
|
||||
using var id = ImRaii.PushId(index);
|
||||
ImGui.TableNextColumn();
|
||||
var unfolded = _unfolded.Contains(resourceNode);
|
||||
var unfolded = _unfolded.Contains(nodePathHash);
|
||||
using (var indent = ImRaii.PushIndent(level))
|
||||
{
|
||||
ImGui.TableHeader((resourceNode.Children.Count > 0 ? unfolded ? "[-] " : "[+] " : string.Empty) + resourceNode.Name);
|
||||
if (ImGui.IsItemClicked() && resourceNode.Children.Count > 0)
|
||||
{
|
||||
if (unfolded)
|
||||
_unfolded.Remove(resourceNode);
|
||||
_unfolded.Remove(nodePathHash);
|
||||
else
|
||||
_unfolded.Add(resourceNode);
|
||||
_unfolded.Add(nodePathHash);
|
||||
unfolded = !unfolded;
|
||||
}
|
||||
|
||||
if (debugMode)
|
||||
if (debugMode)
|
||||
{
|
||||
using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGuiUtil.HoverTooltip(
|
||||
$"Resource Type: {resourceNode.Type}\nObject Address: 0x{resourceNode.ObjectAddress:X16}\nResource Handle: 0x{resourceNode.ResourceHandle:X16}\nLength: 0x{resourceNode.Length:X}");
|
||||
$"Resource Type: {resourceNode.Type}\nObject Address: 0x{resourceNode.ObjectAddress:X16}\nResource Handle: 0x{resourceNode.ResourceHandle:X16}\nLength: 0x{resourceNode.Length:X16}");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -171,7 +181,7 @@ public class ResourceTreeViewer
|
|||
}
|
||||
|
||||
if (unfolded)
|
||||
DrawNodes(resourceNode.Children, level + 1);
|
||||
DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue