Dalamud/Dalamud/Interface/Internal/UiDebug2/Browsing/AddonTree.FieldNames.cs
ItsBexy ee63f60877
Update UIDebug2, ImGuiComponents, ImGuiHelpers (#2081)
* Update ImGuiComponents & ImGuiHelpers
Took some helper functions created for `UiDebug2`, and incorporated them into `ImGuiComponents` / `ImGuiHelpers` instead

- `IconButton()` (and its various overloads) now includes an optional size parameter
- `IconButtonSelect()` has been added, allowing a row or grid of IconButtons to serve as a radio-like input
- `HelpMarker()` now includes an optional color parameter
- `ClickToCopyText()` now includes an optional color parameter, and includes the `FontAwesome.Copy` icon in the tooltip.
- Implemented ImRaii in these files

These changes are intended not to break any existing calls in plugins or within Dalamud itself.

* Fix ambiguous overloads

* UiDebug2 Updates
- Fixed XY coordinate display
- Added AtkValue table to AddonTree display
- Restored old behaviour wherein the Addon display initially only shows the Root node and a collapsed node list
- The above should also fix the Element Selector / Search behaviour, allowing it to scroll correctly to the searched node
- Now displays field offsets for any node/component whose pointer exists in the addon struct
- Tidied up node tree headers by removing memory addresses (they're still readable in the opened tree, of course)
2024-11-14 15:36:27 -08:00

207 lines
7.1 KiB
C#

using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using FFXIVClientStructs.Attributes;
using FFXIVClientStructs.FFXIV.Component.GUI;
using static System.Reflection.BindingFlags;
using static Dalamud.Interface.Internal.UiDebug2.UiDebug2;
namespace Dalamud.Interface.Internal.UiDebug2.Browsing;
/// <inheritdoc cref="AddonTree"/>
public unsafe partial class AddonTree
{
private static readonly Dictionary<string, Type?> AddonTypeDict = [];
private static readonly Assembly? ClientStructsAssembly = typeof(AddonAttribute).Assembly;
/// <summary>
/// Gets or sets a collection of names for field offsets that have been documented in FFXIVClientStructs.
/// </summary>
internal Dictionary<nint, List<string>> FieldNames { get; set; } = [];
/// <summary>
/// Gets or sets the size of the addon according to its Attributes in FFXIVClientStructs.
/// </summary>
internal int AddonSize { get; set; }
private object? GetAddonObj(AtkUnitBase* addon)
{
if (addon == null)
{
return null;
}
if (AddonTypeDict.TryAdd(this.AddonName, null) && ClientStructsAssembly != null)
{
try
{
foreach (var t in from t in ClientStructsAssembly.GetTypes()
where t.IsPublic
let xivAddonAttr = (AddonAttribute?)t.GetCustomAttribute(typeof(AddonAttribute), false)
where xivAddonAttr != null
where xivAddonAttr.AddonIdentifiers.Contains(this.AddonName)
select t)
{
AddonTypeDict[this.AddonName] = t;
var size = t.StructLayoutAttribute?.Size;
if (size != null)
{
this.AddonSize = size.Value;
}
break;
}
}
catch
{
// ignored
}
}
return AddonTypeDict.TryGetValue(this.AddonName, out var result) && result != null ? Marshal.PtrToStructure(new(addon), result) : *addon;
}
private void PopulateFieldNames(nint ptr)
{
this.PopulateFieldNames(this.GetAddonObj((AtkUnitBase*)ptr), ptr);
}
private void PopulateFieldNames(object? obj, nint baseAddr, List<string>? path = null)
{
if (obj == null)
{
return;
}
path ??= [];
var baseType = obj.GetType();
foreach (var field in baseType.GetFields(Static | Public | NonPublic | Instance))
{
if (field.GetCustomAttribute(typeof(FieldOffsetAttribute)) is FieldOffsetAttribute offset)
{
try
{
var fieldAddr = baseAddr + offset.Value;
var name = field.Name[0] == '_' ? char.ToUpperInvariant(field.Name[1]) + field.Name[2..] : field.Name;
var fieldType = field.FieldType;
if (!field.IsStatic && fieldType.IsPointer)
{
var pointer = (nint)Pointer.Unbox((Pointer)field.GetValue(obj)!);
var itemType = fieldType.GetElementType();
ParsePointer(fieldAddr, pointer, itemType, name);
}
else if (fieldType.IsExplicitLayout)
{
ParseExplicitField(fieldAddr, field, fieldType, name);
}
else if (fieldType.Name.Contains("FixedSizeArray"))
{
ParseFixedSizeArray(fieldAddr, fieldType, name);
}
}
catch (Exception ex)
{
Log.Warning($"Failed to parse field at {offset.Value:X} in {baseType}!\n{ex}");
}
}
}
return;
void ParseExplicitField(nint fieldAddr, FieldInfo field, MemberInfo fieldType, string name)
{
try
{
if (this.FieldNames.TryAdd(fieldAddr, [..path, name]) && fieldType.DeclaringType == baseType)
{
this.PopulateFieldNames(field.GetValue(obj), fieldAddr, [..path, name]);
}
}
catch (Exception ex)
{
Log.Warning($"Failed to parse explicit field: {fieldType} {name} in {baseType}!\n{ex}");
}
}
void ParseFixedSizeArray(nint fieldAddr, Type fieldType, string name)
{
try
{
var spanLength = (int)(fieldType.CustomAttributes.ToArray()[0].ConstructorArguments[0].Value ?? 0);
if (spanLength <= 0)
{
return;
}
var itemType = fieldType.UnderlyingSystemType.GenericTypeArguments[0];
if (!itemType.IsGenericType)
{
var size = Marshal.SizeOf(itemType);
for (var i = 0; i < spanLength; i++)
{
var itemAddr = fieldAddr + (size * i);
var itemName = $"{name}[{i}]";
this.FieldNames.TryAdd(itemAddr, [..path, itemName]);
var item = Marshal.PtrToStructure(itemAddr, itemType);
if (itemType.DeclaringType == baseType)
{
this.PopulateFieldNames(item, itemAddr, [..path, itemName]);
}
}
}
else if (itemType.Name.Contains("Pointer"))
{
itemType = itemType.GenericTypeArguments[0];
for (var i = 0; i < spanLength; i++)
{
var itemAddr = fieldAddr + (0x08 * i);
var pointer = Marshal.ReadIntPtr(itemAddr);
ParsePointer(itemAddr, pointer, itemType, $"{name}[{i}]");
}
}
}
catch (Exception ex)
{
Log.Warning($"Failed to parse fixed size array: {fieldType} {name} in {baseType}!\n{ex}");
}
}
void ParsePointer(nint fieldAddr, nint pointer, Type? itemType, string name)
{
try
{
if (pointer == 0)
{
return;
}
this.FieldNames.TryAdd(fieldAddr, [..path, name]);
this.FieldNames.TryAdd(pointer, [..path, name]);
if (itemType?.DeclaringType != baseType || itemType.IsPointer)
{
return;
}
var item = Marshal.PtrToStructure(pointer, itemType);
this.PopulateFieldNames(item, pointer, [..path, name]);
}
catch (Exception ex)
{
Log.Warning($"Failed to parse pointer: {itemType}* {name} in {baseType}!\n{ex}");
}
}
}
}