Merge pull request #245 from Caraxi/ui-debug

Add UiDebug to data window
This commit is contained in:
goaaats 2021-01-16 17:41:07 +01:00 committed by GitHub
commit 87bac19d0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 627 additions and 2 deletions

View file

@ -36,6 +36,8 @@ namespace Dalamud.Interface
private bool resolveGameData = false;
private UIDebug UIDebug = null;
public DalamudDataWindow(Dalamud dalamud) {
this.dalamud = dalamud;
@ -66,8 +68,8 @@ namespace Dalamud.Interface
ImGui.SameLine();
var copy = ImGui.Button("Copy all");
ImGui.SameLine();
ImGui.Combo("Data kind", ref this.currentKind, new[] {"ServerOpCode", "Address", "Actor Table", "Font Test", "Party List", "Plugin IPC", "Condition", "Gauge", "Command", "Addon", "StartInfo", "Target"},
12);
ImGui.Combo("Data kind", ref this.currentKind, new[] {"ServerOpCode", "Address", "Actor Table", "Font Test", "Party List", "Plugin IPC", "Condition", "Gauge", "Command", "Addon", "StartInfo", "Target", "UI Debug"},
13);
ImGui.Checkbox("Resolve GameData", ref this.resolveGameData);
ImGui.BeginChild("scrolling", new Vector2(0, 0), false, ImGuiWindowFlags.HorizontalScrollbar);
@ -361,6 +363,11 @@ namespace Dalamud.Interface
}
break;
case 12: {
this.UIDebug ??= new UIDebug(this.dalamud);
this.UIDebug.Draw();
break;
}
}
else
ImGui.TextUnformatted("Data not ready.");

View file

@ -0,0 +1,618 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FFXIVClientStructs.Component.GUI;
using FFXIVClientStructs.Component.GUI.ULD;
using ImGuiNET;
// Customised version of https://github.com/aers/FFXIVUIDebug
namespace Dalamud.Interface {
internal unsafe class UIDebug {
private AtkUnitBase* selectedUnitBase = null;
private delegate AtkStage* GetAtkStageSingleton();
private readonly GetAtkStageSingleton getAtkStageSingleton;
private const int UnitListCount = 18;
private readonly bool[] selectedInList = new bool[UnitListCount];
private readonly string[] listNames = new string[UnitListCount]{
"Depth Layer 1",
"Depth Layer 2",
"Depth Layer 3",
"Depth Layer 4",
"Depth Layer 5",
"Depth Layer 6",
"Depth Layer 7",
"Depth Layer 8",
"Depth Layer 9",
"Depth Layer 10",
"Depth Layer 11",
"Depth Layer 12",
"Depth Layer 13",
"Loaded Units",
"Focused Units",
"Units 16",
"Units 17",
"Units 18"
};
private string searchInput = string.Empty;
public UIDebug(Dalamud dalamud) {
var getSingletonAddr = dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 41 B8 01 00 00 00 48 8D 15 ?? ?? ?? ?? 48 8B 48 20 E8 ?? ?? ?? ?? 48 8B CF");
this.getAtkStageSingleton = Marshal.GetDelegateForFunctionPointer<GetAtkStageSingleton>(getSingletonAddr);
}
public void Draw() {
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2));
ImGui.BeginChild("st_uiDebug_unitBaseSelect", new Vector2(250, -1), true);
ImGui.SetNextItemWidth(-1);
ImGui.InputTextWithHint("###atkUnitBaseSearch", "Search", ref this.searchInput, 0x20);
DrawUnitBaseList();
ImGui.EndChild();
if (selectedUnitBase != null) {
ImGui.SameLine();
ImGui.BeginChild("st_uiDebug_selectedUnitBase", new Vector2(-1, -1), true);
DrawUnitBase(selectedUnitBase);
ImGui.EndChild();
}
ImGui.PopStyleVar();
}
private void DrawUnitBase(AtkUnitBase* atkUnitBase) {
var isVisible = (atkUnitBase->Flags & 0x20) == 0x20;
var addonName = Marshal.PtrToStringAnsi(new IntPtr(atkUnitBase->Name));
ImGui.Text($"{addonName}");
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, isVisible ? 0xFF00FF00 : 0xFF0000FF);
ImGui.Text(isVisible ? "Visible" : "Not Visible");
ImGui.PopStyleColor();
ImGui.SameLine(ImGui.GetWindowContentRegionWidth() - 25);
if (ImGui.SmallButton("V")) {
atkUnitBase->Flags ^= 0x20;
}
ImGui.Separator();
ClickToCopyText($"Address: {(ulong) atkUnitBase:X}", $"{(ulong) atkUnitBase:X}");
ImGui.Separator();
ImGui.Text($"Position: [ {atkUnitBase->X} , {atkUnitBase->Y} ]");
ImGui.Text($"Scale: {atkUnitBase->Scale*100}%%");
ImGui.Text($"Widget Count {atkUnitBase->ULDData.ObjectCount}");
ImGui.Separator();
object addonObj = *atkUnitBase;
PrintOutObject(addonObj, (ulong) atkUnitBase, new List<string>());
ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale));
ImGui.Separator();
if (atkUnitBase->RootNode != null)
PrintNode(atkUnitBase->RootNode);
if (atkUnitBase->ULDData.NodeListCount > 0) {
ImGui.Dummy(new Vector2(25 * ImGui.GetIO().FontGlobalScale));
ImGui.Separator();
ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA);
if (ImGui.TreeNode($"Node List##{(ulong)atkUnitBase:X}")) {
ImGui.PopStyleColor();
for (var j = 0; j < atkUnitBase->ULDData.NodeListCount; j++) {
PrintNode(atkUnitBase->ULDData.NodeList[j], false, $"[{j}] ");
}
ImGui.TreePop();
} else {
ImGui.PopStyleColor();
}
}
}
private void PrintNode(AtkResNode* node, bool printSiblings = true, string treePrefix = "")
{
if (node == null)
return;
if ((int)node->Type < 1000)
PrintSimpleNode(node, treePrefix);
else
PrintComponentNode(node, treePrefix);
if (printSiblings)
{
var prevNode = node;
while ((prevNode = prevNode->PrevSiblingNode) != null)
PrintNode(prevNode, false, "prev ");
var nextNode = node;
while ((nextNode = nextNode->NextSiblingNode) != null)
PrintNode(nextNode, false, "next ");
}
}
private void PrintSimpleNode(AtkResNode* node, string treePrefix)
{
var popped = false;
var isVisible = (node->Flags & 0x10) == 0x10;
if (isVisible)
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255));
if (ImGui.TreeNode($"{treePrefix}{node->Type} Node (ptr = {(long)node:X})###{(long)node}"))
{
if (ImGui.IsItemHovered()) DrawOutline(node);
if (isVisible)
{
ImGui.PopStyleColor();
popped = true;
}
ImGui.Text("Node: ");
ImGui.SameLine();
ClickToCopyText($"{(ulong)node:X}");
ImGui.SameLine();
switch (node->Type) {
case NodeType.Text: PrintOutObject(*(AtkTextNode*)node, (ulong) node, new List<string>()); break;
case NodeType.Image: PrintOutObject(*(AtkImageNode*)node, (ulong) node, new List<string>()); break;
case NodeType.Collision: PrintOutObject(*(AtkCollisionNode*)node, (ulong) node, new List<string>()); break;
case NodeType.NineGrid: PrintOutObject(*(AtkNineGridNode*)node, (ulong) node, new List<string>()); break;
case NodeType.Counter: PrintOutObject(*(AtkCounterNode*)node, (ulong) node, new List<string>()); break;
default: PrintOutObject(*node, (ulong) node, new List<string>()); break;
}
PrintResNode(node);
if (node->ChildNode != null)
{
PrintNode(node->ChildNode);
}
switch (node->Type)
{
case NodeType.Text:
var textNode = (AtkTextNode*)node;
ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(textNode->NodeText.StringPtr))}");
ImGui.InputText($"Replace Text##{(ulong) textNode:X}", new IntPtr(textNode->NodeText.StringPtr), (uint) textNode->NodeText.BufSize);
ImGui.Text($"AlignmentType: {(AlignmentType)textNode->AlignmentFontType} FontSize: {textNode->FontSize}");
int b = textNode->AlignmentFontType;
if (ImGui.InputInt($"###setAlignment{(ulong) textNode:X}", ref b, 1)) {
while (b > byte.MaxValue) b -= byte.MaxValue;
while (b < byte.MinValue) b += byte.MaxValue;
textNode->AlignmentFontType = (byte) b;
textNode->AtkResNode.Flags_2 |= 0x1;
}
ImGui.Text($"Color: #{textNode->TextColor.R:X2}{textNode->TextColor.G:X2}{textNode->TextColor.B:X2}{textNode->TextColor.A:X2}");
ImGui.SameLine();
ImGui.Text($"EdgeColor: #{textNode->EdgeColor.R:X2}{textNode->EdgeColor.G:X2}{textNode->EdgeColor.B:X2}{textNode->EdgeColor.A:X2}");
ImGui.SameLine();
ImGui.Text($"BGColor: #{textNode->BackgroundColor.R:X2}{textNode->BackgroundColor.G:X2}{textNode->BackgroundColor.B:X2}{textNode->BackgroundColor.A:X2}");
ImGui.Text($"TextFlags: {textNode->TextFlags}");
ImGui.SameLine();
ImGui.Text($"TextFlags2: {textNode->TextFlags2}");
break;
case NodeType.Counter:
var counterNode = (AtkCounterNode*)node;
ImGui.Text($"text: {Marshal.PtrToStringAnsi(new IntPtr(counterNode->NodeText.StringPtr))}");
break;
case NodeType.Image:
var imageNode = (AtkImageNode*)node;
if (imageNode->PartsList != null) {
if (imageNode->PartId > imageNode->PartsList->PartCount) {
ImGui.Text("part id > part count?");
} else {
var textureInfo = imageNode->PartsList->Parts[imageNode->PartId].ULDTexture;
var texType = textureInfo->AtkTexture.TextureType;
ImGui.Text($"texture type: {texType} part_id={imageNode->PartId} part_id_count={imageNode->PartsList->PartCount}");
if (texType == TextureType.Resource) {
var texFileNamePtr = textureInfo->AtkTexture.Resource->TexFileResourceHandle->ResourceHandle.FileName;
var texString = Marshal.PtrToStringAnsi(new IntPtr(texFileNamePtr));
ImGui.Text($"texture path: {texString}");
var kernelTexture = textureInfo->AtkTexture.Resource->KernelTextureObject;
if (ImGui.TreeNode($"Texture##{(ulong) kernelTexture->D3D11ShaderResourceView:X}")) {
ImGui.Image(new IntPtr(kernelTexture->D3D11ShaderResourceView), new Vector2(kernelTexture->Width, kernelTexture->Height));
ImGui.TreePop();
}
} else if (texType == TextureType.KernelTexture) {
if (ImGui.TreeNode($"Texture##{(ulong) textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView:X}")) {
ImGui.Image(new IntPtr(textureInfo->AtkTexture.KernelTexture->D3D11ShaderResourceView), new Vector2(textureInfo->AtkTexture.KernelTexture->Width, textureInfo->AtkTexture.KernelTexture->Height));
ImGui.TreePop();
}
}
}
} else {
ImGui.Text("no texture loaded");
}
break;
}
ImGui.TreePop();
}
else if(ImGui.IsItemHovered()) DrawOutline(node);
if (isVisible && !popped)
ImGui.PopStyleColor();
}
private void PrintComponentNode(AtkResNode* node, string treePrefix)
{
var compNode = (AtkComponentNode*)node;
bool popped = false;
bool isVisible = (node->Flags & 0x10) == 0x10;
if (isVisible)
ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(0, 255, 0, 255));
var componentInfo = compNode->Component->ULDData;
var childCount = componentInfo.NodeListCount;
var objectInfo = (ULDComponentInfo*)componentInfo.Objects;
if (ImGui.TreeNode($"{treePrefix}{objectInfo->ComponentType} Component Node (ptr = {(long)node:X}, component ptr = {(long)compNode->Component:X}) child count = {childCount} ###{(long)node}"))
{
if (ImGui.IsItemHovered()) DrawOutline(node);
if (isVisible)
{
ImGui.PopStyleColor();
popped = true;
}
ImGui.Text("Node: ");
ImGui.SameLine();
ClickToCopyText($"{(ulong)node:X}");
ImGui.SameLine();
PrintOutObject(*compNode, (ulong) compNode, new List<string>());
ImGui.Text("Component: ");
ImGui.SameLine();
ClickToCopyText($"{(ulong)compNode->Component:X}");
ImGui.SameLine();
switch (objectInfo->ComponentType) {
case ComponentType.Button: PrintOutObject(*(AtkComponentButton*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.Slider: PrintOutObject(*(AtkComponentSlider*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.Window: PrintOutObject(*(AtkComponentWindow*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.CheckBox: PrintOutObject(*(AtkComponentCheckBox*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.GaugeBar: PrintOutObject(*(AtkComponentGaugeBar*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.RadioButton: PrintOutObject(*(AtkComponentRadioButton*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.TextInput: PrintOutObject(*(AtkComponentTextInput*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
case ComponentType.Icon: PrintOutObject(*(AtkComponentIcon*)compNode->Component, (ulong) compNode->Component, new List<string>()); break;
default: PrintOutObject(*compNode->Component, (ulong) compNode->Component, new List<string>()); break;
}
PrintResNode(node);
PrintNode(componentInfo.RootNode);
switch (objectInfo->ComponentType)
{
case ComponentType.TextInput:
var textInputComponent = (AtkComponentTextInput*)compNode->Component;
ImGui.Text($"InputBase Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText1.StringPtr))}");
ImGui.Text($"InputBase Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->AtkComponentInputBase.UnkText2.StringPtr))}");
ImGui.Text($"Text1: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText1.StringPtr))}");
ImGui.Text($"Text2: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText2.StringPtr))}");
ImGui.Text($"Text3: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText3.StringPtr))}");
ImGui.Text($"Text4: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText4.StringPtr))}");
ImGui.Text($"Text5: {Marshal.PtrToStringAnsi(new IntPtr(textInputComponent->UnkText5.StringPtr))}");
break;
}
ImGui.PushStyleColor(ImGuiCol.Text, 0xFFFFAAAA);
if (ImGui.TreeNode($"Node List##{(ulong) node:X}")) {
ImGui.PopStyleColor();
for (var i = 0; i < compNode->Component->ULDData.NodeListCount; i++) {
PrintNode(compNode->Component->ULDData.NodeList[i], false, $"[{i}] ");
}
ImGui.TreePop();
} else {
ImGui.PopStyleColor();
}
ImGui.TreePop();
}
else if (ImGui.IsItemHovered()) DrawOutline(node);
if (isVisible && !popped)
ImGui.PopStyleColor();
}
private void PrintResNode(AtkResNode* node)
{
ImGui.Text($"NodeID: {node->NodeID}");
ImGui.SameLine();
if (ImGui.SmallButton($"T:Visible##{(ulong)node:X}")) {
node->Flags ^= 0x10;
}
ImGui.SameLine();
if (ImGui.SmallButton($"C:Ptr##{(ulong)node:X}")) {
ImGui.SetClipboardText($"{(ulong)node:X}");
}
ImGui.Text(
$"X: {node->X} Y: {node->Y} " +
$"ScaleX: {node->ScaleX} ScaleY: {node->ScaleY} " +
$"Rotation: {node->Rotation} " +
$"Width: {node->Width} Height: {node->Height} " +
$"OriginX: {node->OriginX} OriginY: {node->OriginY}");
ImGui.Text(
$"RGBA: 0x{node->Color.R:X2}{node->Color.G:X2}{node->Color.B:X2}{node->Color.A:X2} " +
$"AddRGB: {node->AddRed} {node->AddGreen} {node->AddBlue} " +
$"MultiplyRGB: {node->MultiplyRed} {node->MultiplyGreen} {node->MultiplyBlue}");
}
private bool doingSearch;
private bool DrawUnitListHeader(int index, uint count, ulong ptr, bool highlight) {
ImGui.PushStyleColor(ImGuiCol.Text, highlight ? 0xFFAAAA00 : 0xFFFFFFFF);
if (!string.IsNullOrEmpty(this.searchInput) && !doingSearch) {
ImGui.SetNextItemOpen(true, ImGuiCond.Always);
} else if (doingSearch && string.IsNullOrEmpty(this.searchInput)) {
ImGui.SetNextItemOpen(false, ImGuiCond.Always);
}
var treeNode = ImGui.TreeNode($"{listNames[index]}##unitList_{index}");
ImGui.PopStyleColor();
ImGui.SameLine();
ImGui.TextDisabled($"C:{count} {ptr:X}");
return treeNode;
}
private void DrawUnitBaseList() {
bool foundSelected = false;
bool noResults = true;
var stage = getAtkStageSingleton();
var unitManagers = &stage->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList;
var searchStr = this.searchInput;
var searching = !string.IsNullOrEmpty(searchStr);
for (var i = 0; i < UnitListCount; i++) {
var headerDrawn = false;
var highlight = selectedUnitBase != null && selectedInList[i];
selectedInList[i] = false;
var unitManager = &unitManagers[i];
var unitBaseArray = &(unitManager->AtkUnitEntries);
var headerOpen = true;
if (!searching) {
headerOpen = DrawUnitListHeader(i, unitManager->Count, (ulong) unitManager, highlight);
headerDrawn = true;
noResults = false;
}
for (var j = 0; j < unitManager->Count && headerOpen; j++) {
var unitBase = unitBaseArray[j];
if (selectedUnitBase != null && unitBase == selectedUnitBase) {
selectedInList[i] = true;
foundSelected = true;
}
var name = Marshal.PtrToStringAnsi(new IntPtr(unitBase->Name));
if (searching) {
if (name == null || !name.ToLower().Contains(searchStr.ToLower())) continue;
}
noResults = false;
if (!headerDrawn) {
headerOpen = DrawUnitListHeader(i, unitManager->Count, (ulong) unitManager, highlight);
headerDrawn = true;
}
if (headerOpen) {
var visible = (unitBase->Flags & 0x20) == 0x20;
ImGui.PushStyleColor(ImGuiCol.Text, visible ? 0xFF00FF00 : 0xFF999999);
if (ImGui.Selectable($"{name}##list{i}-{(ulong) unitBase:X}_{j}", selectedUnitBase == unitBase)) {
selectedUnitBase = unitBase;
foundSelected = true;
selectedInList[i] = true;
}
ImGui.PopStyleColor();
}
}
if (headerDrawn && headerOpen) {
ImGui.TreePop();
}
if (selectedInList[i] == false && selectedUnitBase != null) {
for (var j = 0; j < unitManager->Count; j++) {
if (selectedUnitBase == null || unitBaseArray[j] != selectedUnitBase) continue;
selectedInList[i] = true;
foundSelected = true;
}
}
}
if (noResults) {
ImGui.TextDisabled("No Results");
}
if (!foundSelected) {
selectedUnitBase = null;
}
if (doingSearch && string.IsNullOrEmpty(this.searchInput)) {
doingSearch = false;
} else if (!doingSearch && !string.IsNullOrEmpty(this.searchInput)) {
doingSearch = true;
}
}
private 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;
}
private 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;
}
private bool GetNodeVisible(AtkResNode* node) {
if (node == null) return false;
while (node != null) {
if ((node->Flags & (short)NodeFlags.Visible) != (short)NodeFlags.Visible) return false;
node = node->ParentNode;
}
return true;
}
private void DrawOutline(AtkResNode* node) {
var position = GetNodePosition(node);
var scale = GetNodeScale(node);
var size = new Vector2(node->Width, node->Height) * scale;
var nodeVisible = GetNodeVisible(node);
ImGui.GetForegroundDrawList().AddRect(position, position + size, nodeVisible ? 0xFF00FF00 : 0xFF0000FF);
}
private static void ClickToCopyText(string text, string textCopy = null) {
textCopy ??= text;
ImGui.Text($"{text}");
if (ImGui.IsItemHovered()) {
ImGui.SetMouseCursor(ImGuiMouseCursor.Hand);
if (textCopy != text) ImGui.SetTooltip(textCopy);
}
if (ImGui.IsItemClicked()) ImGui.SetClipboardText($"{textCopy}");
}
private void PrintOutValue(ulong addr, IEnumerable<string> path, Type type, object value) {
if (type.IsPointer) {
var val = (Pointer) value;
var unboxed = Pointer.Unbox(val);
if (unboxed != null) {
var unboxedAddr = (ulong) unboxed;
ClickToCopyText($"{(ulong)unboxed:X}");
if (beginModule > 0 && unboxedAddr >= this.beginModule && unboxedAddr <= this.endModule) {
ImGui.SameLine();
ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff);
ClickToCopyText($"ffxiv_dx11.exe+{(unboxedAddr-this.beginModule):X}");
ImGui.PopStyleColor();
}
try {
var eType = type.GetElementType();
var ptrObj = Marshal.PtrToStructure(new IntPtr(unboxed), eType);
ImGui.SameLine();
PrintOutObject(ptrObj, (ulong) unboxed, new List<string>(path));
} catch {
// Ignored
}
} else {
ImGui.Text("null");
}
} else {
if (!type.IsPrimitive) {
PrintOutObject(value, addr, new List<string>(path));
} else {
ImGui.Text($"{value}");
}
}
}
private ulong beginModule;
private ulong endModule;
private void PrintOutObject(object obj, ulong addr, List<string> path, bool autoExpand = false) {
if (this.endModule == 0 && this.beginModule == 0) {
try {
var processModule = Process.GetCurrentProcess().MainModule;
if (processModule != null) {
this.beginModule = (ulong) processModule.BaseAddress.ToInt64();
this.endModule = (this.beginModule + (ulong) processModule.ModuleMemorySize);
} else {
this.endModule = 1;
}
} catch {
this.endModule = 1;
}
}
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF00FFFF);
if (autoExpand) {
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
}
if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) {
ImGui.PopStyleColor();
foreach (var f in obj.GetType().GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.Instance)) {
var fixedBuffer = (FixedBufferAttribute) f.GetCustomAttribute(typeof(FixedBufferAttribute));
if (fixedBuffer != null) {
ImGui.Text($"fixed");
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{fixedBuffer.ElementType.Name}[0x{fixedBuffer.Length:X}]");
} else {
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{f.FieldType.Name}");
}
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: ");
ImGui.SameLine();
PrintOutValue(addr, new List<string>(path) { f.Name }, f.FieldType, f.GetValue(obj));
}
foreach (var p in obj.GetType().GetProperties()) {
ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.9f, 1), $"{p.PropertyType.Name}");
ImGui.SameLine();
ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: ");
ImGui.SameLine();
PrintOutValue(addr, new List<string>(path) { p.Name }, p.PropertyType, p.GetValue(obj));
}
ImGui.TreePop();
} else {
ImGui.PopStyleColor();
}
}
}
}