|
|
|
@ -1,280 +0,0 @@
|
|
|
|
using Dalamud.Logging;
|
|
|
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
using System.Numerics;
|
|
|
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace MgAl2O4.Utils
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// Dalamud.Interface.UIDebug is amazing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class GUINodeUtils
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public static unsafe AtkResNode* PickChildNode(AtkResNode* maybeCompNode, int childIdx, int expectedNumChildren)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (maybeCompNode != null && (int)maybeCompNode->Type >= 1000)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var compNode = (AtkComponentNode*)maybeCompNode;
|
|
|
|
|
|
|
|
if (compNode->Component->UldManager.NodeListCount == expectedNumChildren && childIdx < expectedNumChildren)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return compNode->Component->UldManager.NodeList[childIdx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe AtkResNode* PickChildNode(AtkComponentBase* compPtr, int childIdx, int expectedNumChildren)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (compPtr != null && compPtr->UldManager.NodeListCount == expectedNumChildren && childIdx < expectedNumChildren)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return compPtr->UldManager.NodeList[childIdx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe AtkResNode*[] GetImmediateChildNodes(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var listAddr = new List<ulong>();
|
|
|
|
|
|
|
|
if (node != null && node->ChildNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
listAddr.Add((ulong)node->ChildNode);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
node = node->ChildNode;
|
|
|
|
|
|
|
|
while (node->PrevSiblingNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
listAddr.Add((ulong)node->PrevSiblingNode);
|
|
|
|
|
|
|
|
node = node->PrevSiblingNode;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ConvertToNodeArr(listAddr);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe AtkResNode*[] GetAllChildNodes(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (node != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var list = new List<ulong>();
|
|
|
|
|
|
|
|
RecursiveAppendChildNodes(node, list);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return ConvertToNodeArr(list);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static unsafe void RecursiveAppendChildNodes(AtkResNode* node, List<ulong> listAddr)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (node != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
listAddr.Add((ulong)node);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// step inside
|
|
|
|
|
|
|
|
if (node->ChildNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
RecursiveAppendChildNodes(node->ChildNode, listAddr);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AtkResNode* linkNode = node->ChildNode;
|
|
|
|
|
|
|
|
while (linkNode->PrevSiblingNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
RecursiveAppendChildNodes(linkNode->PrevSiblingNode, listAddr);
|
|
|
|
|
|
|
|
linkNode = linkNode->PrevSiblingNode;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// no need to check next siblings here?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static unsafe AtkResNode*[] ConvertToNodeArr(List<ulong> listAddr)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (listAddr.Count > 0)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var typedArr = new AtkResNode*[listAddr.Count];
|
|
|
|
|
|
|
|
for (int idx = 0; idx < listAddr.Count; idx++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
typedArr[idx] = (AtkResNode*)listAddr[idx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return typedArr;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe AtkResNode* PickNode(AtkResNode*[] nodes, int nodeIdx, int expectedNumNodes)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (nodes != null && nodes.Length == expectedNumNodes && nodeIdx < expectedNumNodes)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return nodes[nodeIdx];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe AtkResNode* GetChildNode(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return node != null ? node->ChildNode : null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe string GetNodeTexturePath(AtkResNode* maybeImageNode)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (maybeImageNode != null && maybeImageNode->Type == NodeType.Image)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var imageNode = (AtkImageNode*)maybeImageNode;
|
|
|
|
|
|
|
|
if (imageNode->PartsList != null && imageNode->PartId <= imageNode->PartsList->PartCount)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].UldAsset;
|
|
|
|
|
|
|
|
var texType = textureInfo->AtkTexture.TextureType;
|
|
|
|
|
|
|
|
if (texType == TextureType.Resource)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var texFileNameStdString = &textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName;
|
|
|
|
|
|
|
|
var texString = texFileNameStdString->Length < 16
|
|
|
|
|
|
|
|
? Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->Buffer)
|
|
|
|
|
|
|
|
: Marshal.PtrToStringAnsi((IntPtr)texFileNameStdString->BufferPtr);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return texString;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe string GetNodeText(AtkResNode* maybeTextNode)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (maybeTextNode != null && maybeTextNode->Type == NodeType.Text)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var textNode = (AtkTextNode*)maybeTextNode;
|
|
|
|
|
|
|
|
var text = Marshal.PtrToStringUTF8(new IntPtr(textNode->NodeText.StringPtr));
|
|
|
|
|
|
|
|
return text;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe Vector2 GetNodePosition(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var pos = new Vector2(node->X, node->Y);
|
|
|
|
|
|
|
|
var par = node->ParentNode;
|
|
|
|
|
|
|
|
while (par != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
pos *= new Vector2(par->ScaleX, par->ScaleY);
|
|
|
|
|
|
|
|
pos += new Vector2(par->X, par->Y);
|
|
|
|
|
|
|
|
par = par->ParentNode;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return pos;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe Vector2 GetNodeScale(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (node == null) return new Vector2(1, 1);
|
|
|
|
|
|
|
|
var scale = new Vector2(node->ScaleX, node->ScaleY);
|
|
|
|
|
|
|
|
while (node->ParentNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
node = node->ParentNode;
|
|
|
|
|
|
|
|
scale *= new Vector2(node->ScaleX, node->ScaleY);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return scale;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe (Vector2, Vector2) GetNodePosAndSize(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (node != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var pos = GetNodePosition(node);
|
|
|
|
|
|
|
|
var scale = GetNodeScale(node);
|
|
|
|
|
|
|
|
var size = new Vector2(node->Width * scale.X, node->Height * scale.Y);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (pos, size);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (Vector2.Zero, Vector2.Zero);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#if DEBUG
|
|
|
|
|
|
|
|
private class ParsableNode
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
public ulong nodeAddr;
|
|
|
|
|
|
|
|
public string content;
|
|
|
|
|
|
|
|
public int childIdx;
|
|
|
|
|
|
|
|
public int numChildren;
|
|
|
|
|
|
|
|
public int depth;
|
|
|
|
|
|
|
|
public NodeType type;
|
|
|
|
|
|
|
|
public string debugPath;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static unsafe bool RecursiveAppendParsableChildNodes(AtkResNode* node, int depth, int childIdx, List<ParsableNode> list, string debugPath)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
bool hasParsableChildNodes = false;
|
|
|
|
|
|
|
|
bool hasContent = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (node != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// check if this node is interesting for parser (empty string is still interesting)
|
|
|
|
|
|
|
|
string content = GetNodeText(node);
|
|
|
|
|
|
|
|
content = (content != null) ? content : GetNodeTexturePath(node);
|
|
|
|
|
|
|
|
hasContent = (content != null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int numChildNodes = 0;
|
|
|
|
|
|
|
|
int insertIdx = list.Count;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ((int)node->Type < 1000)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
// step inside
|
|
|
|
|
|
|
|
if (node->ChildNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
hasParsableChildNodes = RecursiveAppendParsableChildNodes(node->ChildNode, depth + 1, numChildNodes, list, debugPath + "," + numChildNodes);
|
|
|
|
|
|
|
|
numChildNodes++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AtkResNode* linkNode = node->ChildNode;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (linkNode->PrevSiblingNode != null)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var hasParsableSibling = RecursiveAppendParsableChildNodes(linkNode->PrevSiblingNode, depth + 1, numChildNodes, list, debugPath + "," + numChildNodes);
|
|
|
|
|
|
|
|
hasParsableChildNodes = hasParsableChildNodes || hasParsableSibling;
|
|
|
|
|
|
|
|
linkNode = linkNode->PrevSiblingNode;
|
|
|
|
|
|
|
|
numChildNodes++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// no need to check next siblings here?
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var compNode = (AtkComponentNode*)node;
|
|
|
|
|
|
|
|
for (int idx = 0; idx < compNode->Component->UldManager.NodeListCount; idx++)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
hasParsableChildNodes = RecursiveAppendParsableChildNodes(compNode->Component->UldManager.NodeList[idx], depth + 1, numChildNodes, list, debugPath + "," + numChildNodes);
|
|
|
|
|
|
|
|
numChildNodes++;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (hasParsableChildNodes || hasContent)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
list.Insert(insertIdx, new ParsableNode() { nodeAddr = (ulong)node, content = content, childIdx = childIdx, numChildren = numChildNodes, depth = depth, debugPath = debugPath, type = node->Type });
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return hasParsableChildNodes || hasContent;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static unsafe void LogParsableNodes(AtkResNode* node)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var list = new List<ParsableNode>();
|
|
|
|
|
|
|
|
RecursiveAppendParsableChildNodes(node, 0, 0, list, "");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var entry in list)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
var prefix = entry.depth > 0 ? new string(' ', entry.depth * 2) : "";
|
|
|
|
|
|
|
|
PluginLog.Log($"{prefix}> '{entry.content}' idx:{entry.childIdx}, children:{entry.numChildren}, type:{entry.type}, addr:{entry.nodeAddr:X}, path:{entry.debugPath}");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // DEBUG
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|