Skip to content

Commit

Permalink
Merge branch 'restree-less-io'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ottermandias committed Sep 3, 2023
2 parents ecfe88f + 2a2fa3b commit 176956a
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 159 deletions.
2 changes: 1 addition & 1 deletion OtterGui
Submodule OtterGui updated 1 files
+19 −2 ArrayExtensions.cs
246 changes: 193 additions & 53 deletions Penumbra/Interop/ResourceTree/ResolveContext.cs

Large diffs are not rendered by default.

53 changes: 38 additions & 15 deletions Penumbra/Interop/ResourceTree/ResourceNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,83 @@
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 SourceAddress;
public readonly nint ObjectAddress;
public readonly nint ResourceHandle;
public readonly Utf8GamePath GamePath;
public readonly Utf8GamePath[] PossibleGamePaths;
public readonly FullPath FullPath;
public readonly ulong Length;
public readonly bool Internal;
public readonly List<ResourceNode> Children;

public ResourceNode(string? name, ResourceType type, nint sourceAddress, Utf8GamePath gamePath, FullPath fullPath, bool @internal)
public ResourceNode(UIData uiData, ResourceType type, nint objectAddress, nint resourceHandle, Utf8GamePath gamePath, FullPath fullPath,
ulong length, bool @internal)
{
Name = name;
Type = type;
SourceAddress = sourceAddress;
GamePath = gamePath;
Name = uiData.Name;
Icon = uiData.Icon;
Type = type;
ObjectAddress = objectAddress;
ResourceHandle = resourceHandle;
GamePath = gamePath;
PossibleGamePaths = new[]
{
gamePath,
};
FullPath = fullPath;
Length = length;
Internal = @internal;
Children = new List<ResourceNode>();
}

public ResourceNode(string? name, ResourceType type, nint sourceAddress, Utf8GamePath[] possibleGamePaths, FullPath fullPath,
bool @internal)
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;
SourceAddress = sourceAddress;
ObjectAddress = objectAddress;
ResourceHandle = resourceHandle;
GamePath = possibleGamePaths.Length == 1 ? possibleGamePaths[0] : Utf8GamePath.Empty;
PossibleGamePaths = possibleGamePaths;
FullPath = fullPath;
Length = length;
Internal = @internal;
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;
SourceAddress = originalResourceNode.SourceAddress;
ObjectAddress = originalResourceNode.ObjectAddress;
ResourceHandle = originalResourceNode.ResourceHandle;
GamePath = originalResourceNode.GamePath;
PossibleGamePaths = originalResourceNode.PossibleGamePaths;
FullPath = originalResourceNode.FullPath;
Length = originalResourceNode.Length;
Internal = originalResourceNode.Internal;
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);
}
}
76 changes: 47 additions & 29 deletions Penumbra/Interop/ResourceTree/ResourceTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
Expand All @@ -12,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;
Expand All @@ -51,13 +56,15 @@ 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);

if (character->GameObject.GetObjectKind() == (byte)ObjectKind.Pc)
AddHumanResources(globalContext, (HumanExt*)model);
Expand Down Expand Up @@ -85,17 +92,19 @@ 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);
}
}

AddSkeleton(subObjectNodes, subObjectContext, subObject->Skeleton, $"{subObjectNamePrefix} #{subObjectIndex}, ");

subObject = (CharacterBase*)subObject->DrawObject.Object.NextSiblingObject;
++subObjectIndex;
Expand All @@ -106,16 +115,25 @@ private unsafe void AddHumanResources(GlobalResolveContext globalContext, HumanE

var context = globalContext.CreateContext(EquipSlot.Unknown, default);

var skeletonNode = context.CreateHumanSkeletonNode((GenderRace)human->Human.RaceSexId);
if (skeletonNode != null)
Nodes.Add(globalContext.WithNames ? skeletonNode.WithName(skeletonNode.Name ?? "Skeleton") : skeletonNode);

var decalNode = context.CreateNodeFromTex(human->Decal);
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(human->LegacyBodyDecal);
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 = "")
{
if (skeleton == null)
return;

for (var i = 0; i < skeleton->PartialSkeletonCount; ++i)
{
var sklbNode = context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i]);
if (sklbNode != null)
nodes.Add(context.WithUiData ? sklbNode.WithUIData($"{prefix}Skeleton #{i}", sklbNode.Icon) : sklbNode);
}
}
}
23 changes: 13 additions & 10 deletions Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,51 +29,54 @@ public ResourceTreeFactory(IDataManager gameData, IObjectTable objects, Collecti
_actors = actors;
}

public ResourceTree[] FromObjectTable(bool withNames = true)
public ResourceTree[] FromObjectTable(bool withNames = true, bool redactExternalPaths = true)
{
var cache = new TreeBuildCache(_objects, _gameData);

return cache.Characters
.Select(c => FromCharacter(c, cache, withNames))
.Select(c => FromCharacter(c, cache, withNames, redactExternalPaths))
.OfType<ResourceTree>()
.ToArray();
}

public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters(
IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> characters,
bool withNames = true)
bool withUIData = true, bool redactExternalPaths = true)
{
var cache = new TreeBuildCache(_objects, _gameData);
foreach (var character in characters)
{
var tree = FromCharacter(character, cache, withNames);
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths);
if (tree != null)
yield return (character, tree);
}
}

public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withNames = true)
=> FromCharacter(character, new TreeBuildCache(_objects, _gameData), withNames);
public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withUIData = true,
bool redactExternalPaths = true)
=> FromCharacter(character, new TreeBuildCache(_objects, _gameData), withUIData, redactExternalPaths);

private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache,
bool withNames = true)
bool withUIData = true, bool redactExternalPaths = true)
{
if (!character.IsValid())
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);
if (!collectionResolveData.Valid)
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);
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withUIData, redactExternalPaths);
tree.LoadResources(globalContext);
tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
return tree;
}

Expand Down
5 changes: 0 additions & 5 deletions Penumbra/Interop/ResourceTree/TreeBuildCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace Penumbra.Interop.ResourceTree;
internal class TreeBuildCache
{
private readonly IDataManager _dataManager;
private readonly Dictionary<FullPath, MtrlFile?> _materials = new();
private readonly Dictionary<FullPath, ShpkFile?> _shaderPackages = new();
public readonly List<Character> Characters;
public readonly Dictionary<uint, Character> CharactersById;
Expand All @@ -27,10 +26,6 @@ public TreeBuildCache(IObjectTable objects, IDataManager dataManager)
.ToDictionary(c => c.Key, c => c.First());
}

/// <summary> Try to read a material file from the given path and cache it on success. </summary>
public MtrlFile? ReadMaterial(FullPath path)
=> ReadFile(_dataManager, path, _materials, bytes => new MtrlFile(bytes));

/// <summary> Try to read a shpk file from the given path and cache it on success. </summary>
public ShpkFile? ReadShaderPackage(FullPath path)
=> ReadFile(_dataManager, path, _shaderPackages, bytes => new ShpkFile(bytes));
Expand Down
4 changes: 4 additions & 0 deletions Penumbra/Interop/Structs/Material.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;

Expand Down Expand Up @@ -41,4 +42,7 @@ public struct TextureEntry
[FieldOffset( 0x10 )]
public uint SamplerFlags;
}

public ReadOnlySpan<TextureEntry> TextureSpan
=> new(Textures, TextureCount);
}
5 changes: 3 additions & 2 deletions Penumbra/UI/AdvancedWindow/ModEditWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,8 @@ private HashSet<Utf8GamePath> FindPathsStartingWith(ByteString prefix)
public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialog, ItemSwapTab itemSwapTab, IDataManager gameData,
Configuration config, ModEditor editor, ResourceTreeFactory resourceTreeFactory, MetaFileManager metaFileManager,
StainService stainService, ActiveCollections activeCollections, DalamudServices dalamud, ModMergeTab modMergeTab,
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager, GameEventManager gameEvents)
CommunicatorService communicator, TextureManager textures, IDragDropManager dragDropManager, GameEventManager gameEvents,
ChangedItemDrawer changedItemDrawer)
: base(WindowBaseLabel)
{
_performance = performance;
Expand All @@ -581,7 +582,7 @@ public ModEditWindow(PerformanceTracker performance, FileDialogService fileDialo
(bytes, _, _) => new ShpkTab(_fileDialog, bytes));
_center = new CombinedTexture(_left, _right);
_textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor);
_quickImportViewer = new ResourceTreeViewer(_config, resourceTreeFactory, 2, OnQuickImportRefresh, DrawQuickImportActions);
_quickImportViewer = new ResourceTreeViewer(_config, resourceTreeFactory, changedItemDrawer, 2, OnQuickImportRefresh, DrawQuickImportActions);
_communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ModEditWindow);
}

Expand Down
Loading

0 comments on commit 176956a

Please sign in to comment.