Skip to content

Commit

Permalink
Resource Tree: Add ChangedItem-like icons, make UI prettier
Browse files Browse the repository at this point in the history
  • Loading branch information
Exter-N committed Sep 2, 2023
1 parent db521dd commit 30c622c
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 95 deletions.
72 changes: 37 additions & 35 deletions Penumbra/Interop/ResourceTree/ResolveContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,21 @@
using Penumbra.Interop.Structs;
using Penumbra.String;
using Penumbra.String.Classes;
using static Penumbra.GameData.Files.ShpkFile;
using Penumbra.UI;

namespace Penumbra.Interop.ResourceTree;

internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithNames)
internal record class GlobalResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithUIData,
bool RedactExternalPaths)
{
public readonly Dictionary<nint, ResourceNode> Nodes = new(128);

public ResolveContext CreateContext(EquipSlot slot, CharacterArmor equipment)
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithNames, Nodes, slot, equipment);
=> new(Config, Identifier, TreeBuildCache, Collection, Skeleton, WithUIData, RedactExternalPaths, Nodes, slot, 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)
internal record class ResolveContext(Configuration Config, IObjectIdentifier Identifier, TreeBuildCache TreeBuildCache, ModCollection Collection, int Skeleton, bool WithUIData,
bool RedactExternalPaths, 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)
Expand Down Expand Up @@ -78,7 +79,7 @@ private unsafe ResourceNode CreateNodeFromGamePath(ResourceType type, nint objec
if (fullPath.InternalName.IsEmpty)
fullPath = Collection.ResolvePath(gamePath) ?? new FullPath(gamePath);

var node = new ResourceNode(null, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
var node = new ResourceNode(default, type, objectAddress, (nint)resourceHandle, gamePath, FilterFullPath(fullPath), GetResourceHandleLength(resourceHandle), @internal);
if (resourceHandle != null)
Nodes.Add((nint)resourceHandle, node);

Expand All @@ -99,14 +100,14 @@ private unsafe ResourceNode CreateNodeFromGamePath(ResourceType type, nint objec
gamePaths = Filter(gamePaths);

if (gamePaths.Count == 1)
return new ResourceNode(withName ? GuessNameFromPath(gamePaths[0]) : null, type, objectAddress, (nint)handle, gamePaths[0], fullPath,
return new ResourceNode(withName ? GuessUIDataFromPath(gamePaths[0]) : default, type, objectAddress, (nint)handle, gamePaths[0], fullPath,
GetResourceHandleLength(handle), @internal);

Penumbra.Log.Information($"Found {gamePaths.Count} game paths while reverse-resolving {fullPath} in {Collection.Name}:");
foreach (var gamePath in gamePaths)
Penumbra.Log.Information($"Game path: {gamePath}");

return new ResourceNode(null, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal);
return new ResourceNode(default, type, objectAddress, (nint)handle, gamePaths.ToArray(), fullPath, GetResourceHandleLength(handle), @internal);
}

public unsafe ResourceNode? CreateHumanSkeletonNode(GenderRace gr)
Expand All @@ -129,10 +130,10 @@ private unsafe ResourceNode CreateNodeFromGamePath(ResourceType type, nint objec
if (node == null)
return null;

if (WithNames)
if (WithUIData)
{
var name = GuessModelName(node.GamePath);
node = node.WithName(name != null ? $"IMC: {name}" : null);
var uiData = GuessModelUIData(node.GamePath);
node = node.WithUIData(uiData.PrependName("IMC: "));
}

Nodes.Add((nint)imc, node);
Expand All @@ -145,7 +146,7 @@ private unsafe ResourceNode CreateNodeFromGamePath(ResourceType type, nint objec
if (Nodes.TryGetValue((nint)tex, out var cached))
return cached;

var node = CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithNames);
var node = CreateNodeFromResourceHandle(ResourceType.Tex, (nint)tex->KernelTexture, &tex->Handle, false, WithUIData);
if (node != null)
Nodes.Add((nint)tex, node);

Expand All @@ -164,18 +165,18 @@ private unsafe ResourceNode CreateNodeFromGamePath(ResourceType type, nint objec
if (node == null)
return null;

if (WithNames)
node = node.WithName(GuessModelName(node.GamePath));
if (WithUIData)
node = node.WithUIData(GuessModelUIData(node.GamePath));

for (var i = 0; i < mdl->MaterialCount; i++)
{
var mtrl = (Material*)mdl->Materials[i];
var mtrlNode = CreateNodeFromMaterial(mtrl);
if (mtrlNode != null)
// Don't keep the material's name if it's redundant with the model's name.
node.Children.Add(WithNames
? mtrlNode.WithName((string.Equals(mtrlNode.Name, node.Name, StringComparison.Ordinal) ? null : mtrlNode.Name)
?? $"Material #{i}")
node.Children.Add(WithUIData
? mtrlNode.WithUIData((string.Equals(mtrlNode.Name, node.Name, StringComparison.Ordinal) ? null : mtrlNode.Name)
?? $"Material #{i}", mtrlNode.Icon)
: mtrlNode);
}

Expand Down Expand Up @@ -225,23 +226,23 @@ static ushort GetTextureIndex(ushort texFlags)
if (Nodes.TryGetValue((nint)resource, out var cached))
return cached;

var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithNames);
var node = CreateNodeFromResourceHandle(ResourceType.Mtrl, (nint) mtrl, &resource->Handle, false, WithUIData);
if (node == null)
return null;

var shpkNode = CreateNodeFromShpk(resource->ShpkResourceHandle, new ByteString(resource->ShpkString), false);
if (shpkNode != null)
node.Children.Add(WithNames ? shpkNode.WithName("Shader Package") : shpkNode);
var shpkFile = WithNames && shpkNode != null ? TreeBuildCache.ReadShaderPackage(shpkNode.FullPath) : null;
var shpk = WithNames && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null;
node.Children.Add(WithUIData ? shpkNode.WithUIData("Shader Package", 0) : shpkNode);
var shpkFile = WithUIData && shpkNode != null ? TreeBuildCache.ReadShaderPackage(shpkNode.FullPath) : null;
var shpk = WithUIData && shpkNode != null ? (ShaderPackage*)shpkNode.ObjectAddress : null;

for (var i = 0; i < resource->NumTex; i++)
{
var texNode = CreateNodeFromTex(resource->TexSpace[i].ResourceHandle, new ByteString(resource->TexString(i)), false, resource->TexIsDX11(i));
if (texNode == null)
continue;

if (WithNames)
if (WithUIData)
{
string? name = null;
if (shpk != null)
Expand All @@ -259,7 +260,7 @@ static ushort GetTextureIndex(ushort texFlags)
name = shpkFile?.GetSamplerById(samplerCrc.Value)?.Name ?? $"Texture 0x{samplerCrc.Value:X8}";
}
}
node.Children.Add(texNode.WithName(name ?? $"Texture #{i}"));
node.Children.Add(texNode.WithUIData(name ?? $"Texture #{i}", 0));
}
else
{
Expand All @@ -280,7 +281,7 @@ static ushort GetTextureIndex(ushort texFlags)
if (Nodes.TryGetValue((nint)sklb->SkeletonResourceHandle, out var cached))
return cached;

var node = CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithNames);
var node = CreateNodeFromResourceHandle(ResourceType.Sklb, (nint)sklb, (ResourceHandle*)sklb->SkeletonResourceHandle, false, WithUIData);
if (node != null)
Nodes.Add((nint)sklb->SkeletonResourceHandle, node);

Expand All @@ -295,7 +296,7 @@ static ushort GetTextureIndex(ushort texFlags)
if (Nodes.TryGetValue((nint)sklb->SkeletonParameterResourceHandle, out var cached))
return cached;

var node = CreateNodeFromResourceHandle(ResourceType.Skp, (nint)sklb, (ResourceHandle*)sklb->SkeletonParameterResourceHandle, true, WithNames);
var node = CreateNodeFromResourceHandle(ResourceType.Skp, (nint)sklb, (ResourceHandle*)sklb->SkeletonParameterResourceHandle, true, WithUIData);
if (node != null)
Nodes.Add((nint)sklb->SkeletonParameterResourceHandle, node);

Expand All @@ -308,7 +309,7 @@ private FullPath FilterFullPath(FullPath fullPath)
return fullPath;

var relPath = Path.GetRelativePath(Config.ModDirectory, fullPath.FullName);
if (relPath == "." || !relPath.StartsWith('.') && !Path.IsPathRooted(relPath))
if (!RedactExternalPaths || relPath == "." || !relPath.StartsWith('.') && !Path.IsPathRooted(relPath))
return fullPath.Exists ? fullPath : FullPath.Empty;

return FullPath.Empty;
Expand Down Expand Up @@ -351,42 +352,43 @@ private List<Utf8GamePath> Filter(List<Utf8GamePath> gamePaths)
}
: false;

private string? GuessModelName(Utf8GamePath gamePath)
private ResourceNode.UIData GuessModelUIData(Utf8GamePath gamePath)
{
var path = gamePath.ToString().Split('/', StringSplitOptions.RemoveEmptyEntries);
// Weapons intentionally left out.
var isEquipment = SafeGet(path, 0) == "chara" && SafeGet(path, 1) is "accessory" or "equipment";
if (isEquipment)
foreach (var item in Identifier.Identify(Equipment.Set, Equipment.Variant, Slot.ToSlot()))
{
return Slot switch
var name = Slot switch
{
EquipSlot.RFinger => "R: ",
EquipSlot.LFinger => "L: ",
_ => string.Empty,
}
+ item.Name.ToString();
return new(name, ChangedItemDrawer.GetCategoryIcon(item.Name, item));
}

var nameFromPath = GuessNameFromPath(gamePath);
if (nameFromPath != null)
return nameFromPath;
var dataFromPath = GuessUIDataFromPath(gamePath);
if (dataFromPath.Name != null)
return dataFromPath;

return isEquipment ? Slot.ToName() : null;
return isEquipment ? new(Slot.ToName(), ChangedItemDrawer.GetCategoryIcon(Slot.ToSlot())) : new(null, ChangedItemDrawer.ChangedItemIcon.Unknown);
}

private string? GuessNameFromPath(Utf8GamePath gamePath)
private ResourceNode.UIData GuessUIDataFromPath(Utf8GamePath gamePath)
{
foreach (var obj in Identifier.Identify(gamePath.ToString()))
{
var name = obj.Key;
if (name.StartsWith("Customization:"))
name = name[14..].Trim();
if (name != "Unknown")
return name;
return new(name, ChangedItemDrawer.GetCategoryIcon(obj.Key, obj.Value));
}

return null;
return new(null, ChangedItemDrawer.ChangedItemIcon.Unknown);
}

private static string? SafeGet(ReadOnlySpan<string> array, Index index)
Expand Down
31 changes: 23 additions & 8 deletions Penumbra/Interop/ResourceTree/ResourceNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
using System.Collections.Generic;
using Penumbra.GameData.Enums;
using Penumbra.String.Classes;
using ChangedItemIcon = Penumbra.UI.ChangedItemDrawer.ChangedItemIcon;

namespace Penumbra.Interop.ResourceTree;

public class ResourceNode
{
public readonly string? Name;
public readonly ChangedItemIcon Icon;
public readonly ResourceType Type;
public readonly nint ObjectAddress;
public readonly nint ResourceHandle;
Expand All @@ -18,9 +20,11 @@ public class ResourceNode
public readonly bool Internal;
public readonly List<ResourceNode> Children;

public ResourceNode(string? name, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath gamePath, FullPath fullPath, ulong length, bool @internal)
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath gamePath, FullPath fullPath,
ulong length, bool @internal)
{
Name = name;
Name = uiData.Name;
Icon = uiData.Icon;
Type = type;
ObjectAddress = objectAddress;
ResourceHandle = resourceHandle;
Expand All @@ -35,10 +39,11 @@ public ResourceNode(string? name, ResourceType type, nint objectAddress, nint re
Children = new List<ResourceNode>();
}

public ResourceNode(string? name, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath[] possibleGamePaths, FullPath fullPath,
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath[] possibleGamePaths, FullPath fullPath,
ulong length, bool @internal)
{
Name = name;
Name = uiData.Name;
Icon = uiData.Icon;
Type = type;
ObjectAddress = objectAddress;
ResourceHandle = resourceHandle;
Expand All @@ -50,9 +55,10 @@ public ResourceNode(string? name, ResourceType type, nint objectAddress, nint re
Children = new List<ResourceNode>();
}

private ResourceNode(string? name, ResourceNode originalResourceNode)
private ResourceNode(UIData uiData, ResourceNode originalResourceNode)
{
Name = name;
Name = uiData.Name;
Icon = uiData.Icon;
Type = originalResourceNode.Type;
ObjectAddress = originalResourceNode.ObjectAddress;
ResourceHandle = originalResourceNode.ResourceHandle;
Expand All @@ -64,6 +70,15 @@ private ResourceNode(string? name, ResourceNode originalResourceNode)
Children = originalResourceNode.Children;
}

public ResourceNode WithName(string? name)
=> string.Equals(Name, name, StringComparison.Ordinal) ? this : new ResourceNode(name, this);
public ResourceNode WithUIData(string? name, ChangedItemIcon icon)
=> string.Equals(Name, name, StringComparison.Ordinal) && Icon == icon ? this : new ResourceNode(new(name, icon), this);

public ResourceNode WithUIData(UIData uiData)
=> string.Equals(Name, uiData.Name, StringComparison.Ordinal) && Icon == uiData.Icon ? this : new ResourceNode(uiData, this);

public readonly record struct UIData(string? Name, ChangedItemIcon Icon)
{
public readonly UIData PrependName(string prefix)
=> Name == null ? this : new(prefix + Name, Icon);
}
}
20 changes: 10 additions & 10 deletions Penumbra/Interop/ResourceTree/ResourceTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ internal unsafe void LoadResources(GlobalResolveContext globalContext)
var imc = (ResourceHandle*)model->IMCArray[i];
var imcNode = context.CreateNodeFromImc(imc);
if (imcNode != null)
Nodes.Add(globalContext.WithNames ? imcNode.WithName(imcNode.Name ?? $"IMC #{i}") : imcNode);
Nodes.Add(globalContext.WithUIData ? imcNode.WithUIData(imcNode.Name ?? $"IMC #{i}", imcNode.Icon) : imcNode);

var mdl = (RenderModel*)model->Models[i];
var mdlNode = context.CreateNodeFromRenderModel(mdl);
if (mdlNode != null)
Nodes.Add(globalContext.WithNames ? mdlNode.WithName(mdlNode.Name ?? $"Model #{i}") : mdlNode);
Nodes.Add(globalContext.WithUIData ? mdlNode.WithUIData(mdlNode.Name ?? $"Model #{i}", mdlNode.Icon) : mdlNode);
}

AddSkeleton(Nodes, globalContext.CreateContext(EquipSlot.Unknown, default), model->Skeleton);
Expand Down Expand Up @@ -92,15 +92,15 @@ private unsafe void AddHumanResources(GlobalResolveContext globalContext, HumanE
var imc = (ResourceHandle*)subObject->IMCArray[i];
var imcNode = subObjectContext.CreateNodeFromImc(imc);
if (imcNode != null)
subObjectNodes.Add(globalContext.WithNames
? imcNode.WithName(imcNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, IMC #{i}")
subObjectNodes.Add(globalContext.WithUIData
? imcNode.WithUIData(imcNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, IMC #{i}", imcNode.Icon)
: imcNode);

var mdl = (RenderModel*)subObject->Models[i];
var mdlNode = subObjectContext.CreateNodeFromRenderModel(mdl);
if (mdlNode != null)
subObjectNodes.Add(globalContext.WithNames
? mdlNode.WithName(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}")
subObjectNodes.Add(globalContext.WithUIData
? mdlNode.WithUIData(mdlNode.Name ?? $"{subObjectNamePrefix} #{subObjectIndex}, Model #{i}", mdlNode.Icon)
: mdlNode);
}

Expand All @@ -117,11 +117,11 @@ private unsafe void AddHumanResources(GlobalResolveContext globalContext, HumanE

var decalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->Decal);
if (decalNode != null)
Nodes.Add(globalContext.WithNames ? decalNode.WithName(decalNode.Name ?? "Face Decal") : decalNode);
Nodes.Add(globalContext.WithUIData ? decalNode.WithUIData(decalNode.Name ?? "Face Decal", decalNode.Icon) : decalNode);

var legacyDecalNode = context.CreateNodeFromTex((TextureResourceHandle*)human->LegacyBodyDecal);
if (legacyDecalNode != null)
Nodes.Add(globalContext.WithNames ? legacyDecalNode.WithName(legacyDecalNode.Name ?? "Legacy Body Decal") : legacyDecalNode);
Nodes.Add(globalContext.WithUIData ? legacyDecalNode.WithUIData(legacyDecalNode.Name ?? "Legacy Body Decal", legacyDecalNode.Icon) : legacyDecalNode);
}

private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context, Skeleton* skeleton, string prefix = "")
Expand All @@ -133,11 +133,11 @@ private unsafe void AddSkeleton(List<ResourceNode> nodes, ResolveContext context
{
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (sklbNode != null)
nodes.Add(context.WithNames ? sklbNode.WithName($"{prefix}Skeleton #{i}") : sklbNode);
nodes.Add(context.WithUIData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);

var skpNode = context.CreateParameterNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (skpNode != null)
nodes.Add(context.WithNames ? skpNode.WithName($"{prefix}Skeleton #{i} Parameters") : skpNode);
nodes.Add(context.WithUIData ? skpNode.WithUIData($"{prefix}Skeleton #{i} Parameters", skpNode.Icon) : skpNode);
}
}
}
Loading

0 comments on commit 30c622c

Please sign in to comment.