diff --git a/RuntimeUnityEditor.Core/RuntimeUnityEditor.Core.projitems b/RuntimeUnityEditor.Core/RuntimeUnityEditor.Core.projitems
index fe394b5..c5494fe 100644
--- a/RuntimeUnityEditor.Core/RuntimeUnityEditor.Core.projitems
+++ b/RuntimeUnityEditor.Core/RuntimeUnityEditor.Core.projitems
@@ -87,6 +87,8 @@
+
+
@@ -106,4 +108,7 @@
+
+
+
\ No newline at end of file
diff --git a/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/CacheEntryBase.cs b/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/CacheEntryBase.cs
index d6c2e07..1e5d80c 100644
--- a/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/CacheEntryBase.cs
+++ b/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/CacheEntryBase.cs
@@ -110,7 +110,7 @@ public string TypeName()
}
private bool? _canEnter;
- private readonly GUIContent _nameContent;
+ private protected readonly GUIContent _nameContent;
///
public virtual bool CanEnterValue()
diff --git a/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/MethodCacheEntry.cs b/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/MethodCacheEntry.cs
index c6beab6..ff2e5a2 100644
--- a/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/MethodCacheEntry.cs
+++ b/RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/MethodCacheEntry.cs
@@ -25,7 +25,7 @@ public MethodCacheEntry(object instance, MethodInfo methodInfo, Type owner)
ParameterString = GetParameterPreviewString(methodInfo);
- _content = new GUIContent(_name,null, methodInfo.GetFancyDescription());
+ _nameContent = new GUIContent(_name,null, methodInfo.GetFancyDescription());
}
internal static string GetParameterPreviewString(MethodBase methodInfo)
@@ -65,7 +65,7 @@ internal static string GetParameterPreviewString(MethodBase methodInfo)
private readonly string _name;
private readonly string _returnTypeName;
- private readonly GUIContent _content;
+ private protected readonly GUIContent _nameContent;
///
/// Name of the method.
@@ -78,7 +78,7 @@ internal static string GetParameterPreviewString(MethodBase methodInfo)
public string TypeName() => _returnTypeName;
///
- public GUIContent GetNameContent() => _content;
+ public GUIContent GetNameContent() => _nameContent;
///
/// Not supported for methods.
diff --git a/RuntimeUnityEditor.Core/Windows/Inspector/IL2CPP/MemberCollector.IL2CPP.cs b/RuntimeUnityEditor.Core/Windows/Inspector/IL2CPP/MemberCollector.IL2CPP.cs
new file mode 100644
index 0000000..dc9f87b
--- /dev/null
+++ b/RuntimeUnityEditor.Core/Windows/Inspector/IL2CPP/MemberCollector.IL2CPP.cs
@@ -0,0 +1,225 @@
+#if IL2CPP
+using HarmonyLib;
+using RuntimeUnityEditor.Core.Inspector.Entries;
+using RuntimeUnityEditor.Core.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace RuntimeUnityEditor.Core.Inspector.IL2CPP;
+
+public class IL2CPPCacheEntryHelper
+{
+ private static readonly Dictionary> _ptrLookup = new();
+
+ public static Dictionary GetPtrLookupTable(Type type)
+ {
+ // todo some way to clean up old entries?
+ if (_ptrLookup.TryGetValue(type, out var value))
+ return value;
+
+ value = new Dictionary();
+
+ var staticFields = type.GetFields(BindingFlags.Static | BindingFlags.NonPublic).Where(x => x.IsInitOnly).ToList();
+
+ var fieldPtrs = staticFields.Where(x => x.Name.StartsWith("NativeFieldInfoPtr_"))
+ .Select(x => new { trimmed = x.Name.Substring("NativeFieldInfoPtr_".Length), ptrF = x });
+
+ var usedFields = new HashSet();
+
+ foreach (var fieldPtr in fieldPtrs)
+ {
+ var targetFieldName = fieldPtr.trimmed;
+ // Fields are props in il2cpp interop
+ var targetField = type.GetProperty(targetFieldName, AccessTools.all);
+ if (targetField != null)
+ {
+ value[targetField] = fieldPtr.ptrF;
+
+ var getMethod = targetField.GetGetMethod();
+ if (getMethod != null) usedFields.Add(getMethod);
+ var setMethod = targetField.GetSetMethod();
+ if (setMethod != null) usedFields.Add(setMethod);
+ }
+ }
+
+ foreach (var propertyInfo in type.GetAllProperties(true))
+ {
+ // It's a field
+ if (value.ContainsKey(propertyInfo))
+ continue;
+
+ var getMethod = propertyInfo.GetGetMethod(true);
+ if (getMethod != null && getMethod.GetMethodBody() != null)
+ {
+ var ptr = Il2CppInterop.Common.Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(getMethod);
+ if (ptr != null)
+ value[getMethod] = ptr;
+ }
+
+ var setMethod = propertyInfo.GetSetMethod();
+ if (setMethod != null && setMethod.GetMethodBody() != null)
+ {
+ var ptr = Il2CppInterop.Common.Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(setMethod);
+ if (ptr != null)
+ value[setMethod] = ptr;
+ }
+ }
+
+ foreach (var methodInfo in type.GetAllMethods(true))
+ {
+ if (value.ContainsKey(methodInfo) || usedFields.Contains(methodInfo))
+ continue;
+
+ if (methodInfo.GetMethodBody() != null)
+ {
+ var ptr = Il2CppInterop.Common.Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(methodInfo);
+ if (ptr != null)
+ value[methodInfo] = ptr;
+ }
+ }
+
+ _ptrLookup[type] = value;
+ return value;
+ }
+
+ public static bool TryGetIl2CppCacheEntry(object instance, Type type, EventInfo p, Dictionary lookup, out ICacheEntry result)
+ {
+ FieldInfo ptrAdd = null;
+ FieldInfo ptrRaise = null;
+ FieldInfo ptrRemove = null;
+ var addMethod = p.GetAddMethod(true);
+ if (addMethod != null) lookup.TryGetValue(addMethod, out ptrAdd);
+ var raiseMethod = p.GetRaiseMethod(true);
+ if (raiseMethod != null) lookup.TryGetValue(raiseMethod, out ptrRaise);
+ var removeMethod = p.GetRemoveMethod(true);
+ if (removeMethod != null) lookup.TryGetValue(removeMethod, out ptrRemove);
+ if (ptrAdd != null || ptrRaise != null || ptrRemove != null)
+ {
+ result = new IL2CPPEventCacheEntry(instance, p, type, ptrAdd, ptrRaise, ptrRemove);
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+
+ public static bool TryGetIl2CppCacheEntry(object instance, Type type, PropertyInfo p, Dictionary lookup, out ICacheEntry result)
+ {
+ if (lookup.TryGetValue(p, out var ptr))
+ {
+ result = new IL2CPPFieldCacheEntry(instance, p, type, ptr);
+ return true;
+ }
+
+ FieldInfo ptrGet = null;
+ FieldInfo ptrSet = null;
+ var getMethod = p.GetGetMethod(true);
+ if (getMethod != null) lookup.TryGetValue(getMethod, out ptrGet);
+ var setMethod = p.GetSetMethod(true);
+ if (setMethod != null) lookup.TryGetValue(setMethod, out ptrSet);
+ if (ptrGet != null || ptrSet != null)
+ {
+ result = new IL2CPPPropertyCacheEntry(instance, p, type, ptrGet, ptrSet);
+ return true;
+ }
+
+ result = null;
+ return false;
+ }
+
+ public static object SafeGetPtr(Type owner, FieldInfo ptrField)
+ {
+ if (ptrField == null) return "null";
+ if (owner.ContainsGenericParameters)
+ return "???";
+ try
+ {
+ return ptrField.GetValue(null);
+ }
+ catch
+ {
+ return "error";
+ }
+ }
+
+ internal static bool IsIl2CppCacheEntry(ICacheEntry entry)
+ {
+ return entry is IL2CPPFieldCacheEntry || entry is IL2CPPPropertyCacheEntry || entry is IL2CPPMethodCacheEntry || entry is IL2CPPEventCacheEntry;
+ }
+}
+
+///
+public class IL2CPPFieldCacheEntry : PropertyCacheEntry
+{
+ public FieldInfo PtrField { get; }
+
+ ///
+ public IL2CPPFieldCacheEntry(object ins, PropertyInfo p, Type owner, FieldInfo ptrField) : base(ins, p, owner)
+ {
+ PtrField = ptrField;
+ _nameContent.tooltip = $"IL2CPP Field (ptr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrField)})\n\n{_nameContent.tooltip}";
+ }
+
+ ///
+ public IL2CPPFieldCacheEntry(object ins, PropertyInfo p, Type owner, FieldInfo ptrField, ICacheEntry parent) : base(ins, p, owner, parent)
+ {
+ PtrField = ptrField;
+ _nameContent.tooltip = $"IL2CPP Field (ptr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrField)})\n\n{_nameContent.tooltip}";
+ }
+}
+
+///
+public class IL2CPPPropertyCacheEntry : PropertyCacheEntry
+{
+ public FieldInfo PtrFieldGet { get; }
+ public FieldInfo PtrFieldSet { get; }
+
+ ///
+ public IL2CPPPropertyCacheEntry(object ins, PropertyInfo p, Type owner, FieldInfo ptrFieldGet, FieldInfo ptrFieldSet) : base(ins, p, owner)
+ {
+ PtrFieldGet = ptrFieldGet;
+ PtrFieldSet = ptrFieldSet;
+ _nameContent.tooltip = $"IL2CPP Property (getPtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldGet)}, setPtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldSet)})\n\n{_nameContent.tooltip}";
+ }
+ ///
+ public IL2CPPPropertyCacheEntry(object ins, PropertyInfo p, Type owner, FieldInfo ptrFieldGet, FieldInfo ptrFieldSet, ICacheEntry parent) : base(ins, p, owner, parent)
+ {
+ PtrFieldGet = ptrFieldGet;
+ PtrFieldSet = ptrFieldSet;
+ _nameContent.tooltip = $"IL2CPP Property (getPtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldGet)}, setPtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldSet)})\n\n{_nameContent.tooltip}";
+ }
+}
+
+///
+public class IL2CPPMethodCacheEntry : MethodCacheEntry
+{
+ public FieldInfo PtrField { get; }
+
+ ///
+ public IL2CPPMethodCacheEntry(object instance, MethodInfo methodInfo, Type owner, FieldInfo ptrField) : base(instance, methodInfo, owner)
+ {
+ PtrField = ptrField;
+ _nameContent.tooltip = $"IL2CPP Method (ptr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrField)})\n\n{_nameContent.tooltip}";
+ }
+}
+
+///
+/// TODO: This does nothing so far because events are not implemented in il2cpp interop (they show up as separate add/remove/raise methods). Maybe combine them back into events?
+public class IL2CPPEventCacheEntry : EventCacheEntry
+{
+ public FieldInfo PtrFieldAdd { get; }
+ public FieldInfo PtrFieldRaise { get; }
+ public FieldInfo PtrFieldRemove { get; }
+ ///
+ public IL2CPPEventCacheEntry(object ins, EventInfo e, Type owner, FieldInfo ptrFieldAdd, FieldInfo ptrFieldRaise, FieldInfo ptrFieldRemove) : base(ins, e, owner)
+ {
+ PtrFieldAdd = ptrFieldAdd;
+ PtrFieldRaise = ptrFieldRaise;
+ PtrFieldRemove = ptrFieldRemove;
+ _nameContent.tooltip = $"IL2CPP Event (addPtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldAdd)}, raisePtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldRaise)}, removePtr={IL2CPPCacheEntryHelper.SafeGetPtr(owner, ptrFieldRemove)})\n\n{_nameContent.tooltip}";
+ }
+}
+
+#endif
diff --git a/RuntimeUnityEditor.Core/Windows/Inspector/Inspector.InspectorTab.cs b/RuntimeUnityEditor.Core/Windows/Inspector/Inspector.InspectorTab.cs
index 467566d..1c37a4c 100644
--- a/RuntimeUnityEditor.Core/Windows/Inspector/Inspector.InspectorTab.cs
+++ b/RuntimeUnityEditor.Core/Windows/Inspector/Inspector.InspectorTab.cs
@@ -39,7 +39,7 @@ public InspectorStackEntryBase CurrentStackItem
public void Clear()
{
InspectorStack.Clear();
- CacheAllMembers(null);
+ _fieldCache.Clear();
}
public void Pop()
@@ -68,218 +68,17 @@ public void Push(InspectorStackEntryBase stackEntry)
LoadStackEntry(stackEntry);
}
- private static IEnumerable MethodsToCacheEntries(object instance, Type ownerType, IEnumerable methodsToCheck)
- {
- var cacheItems = methodsToCheck
- .Where(x => !x.IsConstructor && !x.IsSpecialName)
- .Where(f => !f.IsDefined(typeof(CompilerGeneratedAttribute), false))
- .Where(x => x.Name != "MemberwiseClone" && x.Name != "obj_address") // Instant game crash
- .Select(m => new MethodCacheEntry(instance, m, ownerType)).Cast();
- return cacheItems;
- }
-
- private void CacheAllMembers(InstanceStackEntry entry)
- {
- _fieldCache.Clear();
-
- var objectToOpen = entry?.Instance;
- if (objectToOpen == null) return;
-
- var type = objectToOpen.GetType();
-
- try
- {
- CallbackCacheEntry GetExportTexEntry(Texture texture)
- {
- return new CallbackCacheEntry("Export Texture to file",
- "Encode the texture to a PNG and save it to a new file",
- texture.SaveTextureToFileWithDialog);
- }
-
- if (objectToOpen is Component cmp)
- {
- if (ObjectTreeViewer.Initialized)
- {
- _fieldCache.Add(new CallbackCacheEntry("Open in Scene Object Browser",
- "Navigate to GameObject this Component is attached to",
- () => ObjectTreeViewer.Instance.SelectAndShowObject(cmp.transform)));
- }
-
- if (objectToOpen is UnityEngine.UI.Image img)
- _fieldCache.Add(GetExportTexEntry(img.mainTexture));
- else if (objectToOpen is Renderer rend && MeshExport.CanExport(rend))
- {
- _fieldCache.Add(new CallbackCacheEntry("Export mesh to .obj", "Save base mesh used by this renderer to file", () => MeshExport.ExportObj(rend, false, false)));
- _fieldCache.Add(new CallbackCacheEntry("Export mesh to .obj (Baked)", "Bakes current pose into the exported mesh", () => MeshExport.ExportObj(rend, true, false)));
- _fieldCache.Add(new CallbackCacheEntry("Export mesh to .obj (World)", "Bakes pose while keeping world position", () => MeshExport.ExportObj(rend, true, true)));
- }
- }
- else if (objectToOpen is GameObject castedObj)
- {
- if (ObjectTreeViewer.Initialized)
- {
- _fieldCache.Add(new CallbackCacheEntry("Open in Scene Object Browser",
- "Navigate to this object in the Scene Object Browser",
- () => ObjectTreeViewer.Instance.SelectAndShowObject(castedObj.transform)));
- }
-#if !IL2CPP
- _fieldCache.Add(new ReadonlyCacheEntry("Child objects", castedObj.transform.Cast().ToArray()));
-#endif
- _fieldCache.Add(new ReadonlyCacheEntry("Components", castedObj.AbstractGetAllComponents()));
- }
- else if (objectToOpen is Texture tex)
- {
- _fieldCache.Add(GetExportTexEntry(tex));
- }
-
- // If we somehow enter a string, this allows user to see what the string actually says
- if (type == typeof(string))
- {
- _fieldCache.Add(new ReadonlyCacheEntry("this", objectToOpen));
- }
- else if (objectToOpen is Transform)
- {
- // Prevent the list overloads from listing subcomponents
- }
- else if (objectToOpen is IList list)
- {
- for (var i = 0; i < list.Count; i++)
- _fieldCache.Add(new ListCacheEntry(list, i));
- }
- else if (objectToOpen is IEnumerable enumerable)
- {
- _fieldCache.AddRange(enumerable.Cast