mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
* 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)
207 lines
7.1 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|
|
}
|