From 822e26ef93f09226602225fdf80c67b31c55cf97 Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 15 Oct 2023 19:18:33 +0900 Subject: [PATCH] Support Span preview through ShowStruct (#1485) --- Dalamud/Utility/Util.cs | 213 ++++++++++++++++++++++++++++++++++------ 1 file changed, 182 insertions(+), 31 deletions(-) diff --git a/Dalamud/Utility/Util.cs b/Dalamud/Utility/Util.cs index fb6c854a1..49bbbd0b6 100644 --- a/Dalamud/Utility/Util.cs +++ b/Dalamud/Utility/Util.cs @@ -6,9 +6,11 @@ using System.IO.Compression; using System.Linq; using System.Numerics; using System.Reflection; +using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; + using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; @@ -242,10 +244,12 @@ public static class Util /// The address to the structure. /// Whether or not this structure should start out expanded. /// The already followed path. - public static void ShowStruct(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null) + /// Do not print addresses. Use when displaying a copied value. + public static void ShowStruct(object obj, ulong addr, bool autoExpand = false, IEnumerable? path = null, bool hideAddress = false) { ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(3, 2)); path ??= new List(); + var pathList = path is List ? (List)path : path.ToList(); if (moduleEndAddr == 0 && moduleStartAddr == 0) { @@ -274,7 +278,7 @@ public static class Util ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); } - if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", path)}")) + if (ImGui.TreeNode($"{obj}##print-obj-{addr:X}-{string.Join("-", pathList)}")) { ImGui.PopStyleColor(); foreach (var f in obj.GetType() @@ -297,10 +301,24 @@ public static class Util ImGui.TextColored(new Vector4(0.2f, 0.9f, 0.4f, 1), $"{f.Name}: "); ImGui.SameLine(); - if (f.FieldType.IsGenericType && f.FieldType.GetGenericTypeDefinition() == GenericSpanType) - ImGui.Text("Span preview is currently not supported."); - else - ShowValue(addr, new List(path) {f.Name}, f.FieldType, f.GetValue(obj)); + pathList.Add(f.Name); + try + { + if (f.FieldType.IsGenericType && (f.FieldType.IsByRef || f.FieldType.IsByRefLike)) + ImGui.Text("Cannot preview ref typed fields."); // object never contains ref struct + else + ShowValue(addr, pathList, f.FieldType, f.GetValue(obj), hideAddress); + } + catch (Exception ex) + { + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + ImGui.PopStyleColor(); + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } foreach (var p in obj.GetType().GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) @@ -310,10 +328,26 @@ public static class Util ImGui.TextColored(new Vector4(0.2f, 0.6f, 0.4f, 1), $"{p.Name}: "); ImGui.SameLine(); - if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) - ImGui.Text("Span preview is currently not supported."); - else - ShowValue(addr, new List(path) {p.Name}, p.PropertyType, p.GetValue(obj)); + pathList.Add(p.Name); + try + { + if (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == GenericSpanType) + ShowSpanProperty(addr, pathList, p, obj); + else if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) + ImGui.Text("Cannot preview ref typed properties."); + else + ShowValue(addr, pathList, p.PropertyType, p.GetValue(obj), hideAddress); + } + catch (Exception ex) + { + ImGui.PushStyleColor(ImGuiCol.Text, new Vector4(1f, 0.4f, 0.4f, 1f)); + ImGui.TextUnformatted($"Error: {ex.GetType().Name}: {ex.Message}"); + ImGui.PopStyleColor(); + } + finally + { + pathList.RemoveAt(pathList.Count - 1); + } } ImGui.TreePop(); @@ -374,21 +408,21 @@ public static class Util ImGui.Indent(); - foreach (var propertyInfo in type.GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) + foreach (var p in type.GetProperties().Where(p => p.GetGetMethod()?.GetParameters().Length == 0)) { - if (propertyInfo.PropertyType.IsGenericType && - propertyInfo.PropertyType.GetGenericTypeDefinition() == GenericSpanType) + if (p.PropertyType.IsGenericType && (p.PropertyType.IsByRef || p.PropertyType.IsByRefLike)) { - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: Span preview is currently not supported."); - continue; + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {p.Name}: (ref typed property)"); } - - var value = propertyInfo.GetValue(obj); - var valueType = value?.GetType(); - if (valueType == typeof(IntPtr)) - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: 0x{value:X}"); else - ImGui.TextColored(ImGuiColors.DalamudOrange, $" {propertyInfo.Name}: {value}"); + { + var value = p.GetValue(obj); + var valueType = value?.GetType(); + if (valueType == typeof(IntPtr)) + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {p.Name}: 0x{value:X}"); + else + ImGui.TextColored(ImGuiColors.DalamudOrange, $" {p.Name}: {value}"); + } } ImGui.Unindent(); @@ -797,7 +831,120 @@ public static class Util } } - private static unsafe void ShowValue(ulong addr, IEnumerable path, Type type, object value) + private static void ShowSpanProperty(ulong addr, IList path, PropertyInfo p, object obj) + { + var objType = obj.GetType(); + var propType = p.PropertyType; + if (p.GetGetMethod() is not { } getMethod) + { + ImGui.Text("(No getter available)"); + return; + } + + var dm = new DynamicMethod( + "-", + MethodAttributes.Public | MethodAttributes.Static, + CallingConventions.Standard, + null, + new[] { typeof(object), typeof(IList), typeof(ulong) }, + obj.GetType(), + true); + + var ilg = dm.GetILGenerator(); + var objLocalIndex = unchecked((byte)ilg.DeclareLocal(objType, true).LocalIndex); + var propLocalIndex = unchecked((byte)ilg.DeclareLocal(propType, true).LocalIndex); + ilg.Emit(OpCodes.Ldarg_0); + if (objType.IsValueType) + { + ilg.Emit(OpCodes.Unbox_Any, objType); + ilg.Emit(OpCodes.Stloc_S, objLocalIndex); + ilg.Emit(OpCodes.Ldloca_S, objLocalIndex); + } + + ilg.Emit(OpCodes.Call, getMethod); + var mm = typeof(Util).GetMethod(nameof(ShowSpanPrivate), BindingFlags.Static | BindingFlags.NonPublic)! + .MakeGenericMethod(p.PropertyType.GetGenericArguments()); + ilg.Emit(OpCodes.Stloc_S, propLocalIndex); + ilg.Emit(OpCodes.Ldarg_2); // addr = arg2 + ilg.Emit(OpCodes.Ldarg_1); // path = arg1 + ilg.Emit(OpCodes.Ldc_I4_0); // offset = 0 + ilg.Emit(OpCodes.Ldc_I4_1); // isTop = true + ilg.Emit(OpCodes.Ldloca_S, propLocalIndex); // spanobj + ilg.Emit(OpCodes.Call, mm); + ilg.Emit(OpCodes.Ret); + + dm.Invoke(null, new[] { obj, path, addr }); + } + + private static unsafe void ShowSpanPrivate(ulong addr, IList path, int offset, bool isTop, in Span spanobj) + { +#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + if (isTop) + { + fixed (void* p = spanobj) + { + if (!ImGui.TreeNode( + $"Span<{typeof(T).Name}> of length {spanobj.Length:n0} (0x{spanobj.Length:X})" + + $"##print-obj-{addr:X}-{string.Join("-", path)}-head")) + { + return; + } + } + } + + try + { + const int batchSize = 20; + if (spanobj.Length > batchSize) + { + var skip = batchSize; + while ((spanobj.Length + skip - 1) / skip > batchSize) + skip *= batchSize; + for (var i = 0; i < spanobj.Length; i += skip) + { + var next = Math.Min(i + skip, spanobj.Length); + path.Add($"{offset + i:X}_{skip}"); + if (ImGui.TreeNode( + $"{offset + i:n0} ~ {offset + next - 1:n0} (0x{offset + i:X} ~ 0x{offset + next - 1:X})" + + $"##print-obj-{addr:X}-{string.Join("-", path)}")) + { + try + { + ShowSpanPrivate(addr, path, offset + i, false, spanobj[i..next]); + } + finally + { + ImGui.TreePop(); + } + } + + path.RemoveAt(path.Count - 1); + } + } + else + { + fixed (T* p = spanobj) + { + var pointerType = typeof(T*); + for (var i = 0; i < spanobj.Length; i++) + { + ImGui.TextUnformatted($"[{offset + i:n0} (0x{offset + i:X})] "); + ImGui.SameLine(); + path.Add($"{offset + i}"); + ShowValue(addr, path, pointerType, Pointer.Box(p + i, pointerType), true); + } + } + } + } + finally + { + if (isTop) + ImGui.TreePop(); + } +#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type + } + + private static unsafe void ShowValue(ulong addr, IList path, Type type, object value, bool hideAddress) { if (type.IsPointer) { @@ -805,28 +952,32 @@ public static class Util var unboxed = Pointer.Unbox(val); if (unboxed != null) { - var unboxedAddr = (ulong)unboxed; - ImGuiHelpers.ClickToCopyText($"{(ulong)unboxed:X}"); - if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) + if (!hideAddress) { + var unboxedAddr = (ulong)unboxed; + ImGuiHelpers.ClickToCopyText($"{(ulong)unboxed:X}"); + if (moduleStartAddr > 0 && unboxedAddr >= moduleStartAddr && unboxedAddr <= moduleEndAddr) + { + ImGui.SameLine(); + ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); + ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); + ImGui.PopStyleColor(); + } + ImGui.SameLine(); - ImGui.PushStyleColor(ImGuiCol.Text, 0xffcbc0ff); - ImGuiHelpers.ClickToCopyText($"ffxiv_dx11.exe+{unboxedAddr - moduleStartAddr:X}"); - ImGui.PopStyleColor(); } try { var eType = type.GetElementType(); var ptrObj = SafeMemory.PtrToStructure(new IntPtr(unboxed), eType); - ImGui.SameLine(); if (ptrObj == null) { ImGui.Text("null or invalid"); } else { - ShowStruct(ptrObj, (ulong)unboxed, path: new List(path)); + ShowStruct(ptrObj, addr, path: path, hideAddress: hideAddress); } } catch @@ -843,7 +994,7 @@ public static class Util { if (!type.IsPrimitive) { - ShowStruct(value, addr, path: new List(path)); + ShowStruct(value, addr, path: path, hideAddress: hideAddress); } else {