Compare commits

..

9 Commits
1.4.0 ... 1.4.2

Author SHA1 Message Date
45b5ce0ef8 1.4.2
* Fixed a bug on the Reflection window which would prevent primitive values from being applied
* Improved some parts of the Scene Explorer and the Reflection Window interfaces
* Scene Explorer now has "page view" like other lists
* Various minor cleanups and refactorings
2020-08-24 01:42:19 +10:00
e3d1add090 Update README.md 2020-08-23 03:00:19 +10:00
a59bcc95e4 Update README.md 2020-08-23 03:00:12 +10:00
ac4414ca86 Update README.md 2020-08-23 02:50:59 +10:00
19263092fe Update CacheObject.cs 2020-08-22 19:36:06 +10:00
6bafab785b 1.4.1
* Cleanup some small bugs introduced in 1.4.0
* Added better exception handling for failed Reflection, and the ability to hide failed reflection members in the Reflection window, as well as see the error type.
* Reflection window members now display the full name instead of just the member name (eg. "Camera.main" instead of just "main").
2020-08-22 17:17:11 +10:00
62b1688d53 Update README.md 2020-08-22 01:42:29 +10:00
4d015cbe93 Update README.md 2020-08-22 01:08:09 +10:00
0da8f4faea Update README.md 2020-08-22 01:02:04 +10:00
19 changed files with 771 additions and 630 deletions

View File

@ -1,11 +1,20 @@
# CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.6-green.svg)]()
<p align="center">
<img align="center" src="https://i.imgur.com/1ZoZemW.png">
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
</p>
[![Version](https://img.shields.io/badge/MelonLoader-0.2.6-green.svg)]()
<p align="center">
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> Unity games, using <a href="https://github.com/HerpDerpinstine/MelonLoader">MelonLoader</a>.<br><br>
An in-game explorer and a suite of debugging tools for [IL2CPP](https://docs.unity3d.com/Manual/IL2CPP.html) Unity games, using [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader).
<a href="../../releases/latest">
<img src="https://img.shields.io/github/release/sinai-dev/CppExplorer.svg" />
</a>
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
</p>
### Note
Most games running on Unity 2017 to 2019 should be supported. If you find that the GUI does not display properly and you get errors in the MelonLoader console about it, then this is likely due to a bug with Il2CppAssemblyUnhollower's unstripping. This bug is known by the developer of the tool and they will fix it as soon as they are able to.
## Features
@ -41,6 +50,8 @@ In order to fix this problem, you can:
## Images
<i>Note: images may be slightly outdated, taken from version 1.2</i>.
Scene explorer, and inspection of a MonoBehaviour object:
[![](https://i.imgur.com/Yxizwcz.png)](https://i.imgur.com/Yxizwcz.png)

View File

@ -10,18 +10,18 @@ namespace Explorer
{
public class CacheEnum : CacheObject
{
private readonly Type m_enumType;
private readonly string[] m_names;
public Type EnumType;
public string[] EnumNames;
public CacheEnum(object obj)
public override void Init()
{
m_enumType = obj.GetType();
m_names = Enum.GetNames(obj.GetType());
EnumType = Value.GetType();
EnumNames = Enum.GetNames(EnumType);
}
public override void DrawValue(Rect window, float width)
{
if (MemberInfo != null)
if (CanWrite)
{
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
{
@ -35,34 +35,18 @@ namespace Explorer
}
}
GUILayout.Label(Value.ToString(), null);
}
public override void SetValue()
{
if (MemberInfo == null)
{
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
return;
}
if (Enum.Parse(m_enumType, Value.ToString()) is object enumValue && enumValue != null)
{
Value = enumValue;
}
SetValue(Value, MemberInfo, DeclaringInstance);
GUILayout.Label(Value.ToString(), null);// + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
}
public void SetEnum(ref object value, int change)
{
var names = m_names.ToList();
var names = EnumNames.ToList();
int newindex = names.IndexOf(value.ToString()) + change;
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
{
value = Enum.Parse(m_enumType, names[newindex]);
value = Enum.Parse(EnumType, names[newindex]);
}
}
}

View File

@ -10,44 +10,37 @@ namespace Explorer
{
public class CacheGameObject : CacheObject
{
private GameObject m_gameObject;
public CacheGameObject(object obj)
private GameObject GameObj
{
if (obj != null)
m_gameObject = GetGameObject(obj);
}
private GameObject GetGameObject(object obj)
{
if (obj is Il2CppSystem.Object ilObj)
get
{
var ilType = ilObj.GetIl2CppType();
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
if (m_gameObject == null)
{
return ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
}
}
if (Value is Il2CppSystem.Object ilObj)
{
var ilType = ilObj.GetIl2CppType();
return null;
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
{
m_gameObject = ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
}
}
}
return m_gameObject;
}
}
private GameObject m_gameObject;
public override void DrawValue(Rect window, float width)
{
UIHelpers.GameobjButton(m_gameObject, null, false, width);
UIHelpers.GameobjButton(GameObj, null, false, width);
}
public override void SetValue()
public override void UpdateValue()
{
throw new NotImplementedException("TODO");
}
public override void UpdateValue(object obj)
{
base.UpdateValue(obj);
m_gameObject = GetGameObject(Value);
base.UpdateValue();
}
}
}

View File

@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheIl2CppObject : CacheObject
{
public override void DrawValue(Rect window, float width)
{
var label = ValueType ?? Value.ToString();
if (!label.Contains(ValueType))
{
label += $" ({ValueType})";
}
if (Value is UnityEngine.Object unityObj)
{
label = unityObj.name + " | " + label;
}
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(width) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
}
public override void SetValue()
{
throw new NotImplementedException("TODO");
}
}
}

View File

@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Mono.CSharp;
using UnityEngine;
namespace Explorer
@ -13,30 +14,48 @@ namespace Explorer
{
public bool IsExpanded { get; set; }
public int ArrayOffset { get; set; }
public Type EntryType { get; set; }
public int ArrayLimit { get; set; } = 20;
public Type EntryType
{
get
{
if (m_entryType == null)
{
m_entryType = Value?.GetType().GetGenericArguments()[0];
}
return m_entryType;
}
set
{
m_entryType = value;
}
}
private Type m_entryType;
public IEnumerable Enumerable
{
get
{
if (m_enumerable == null && Value != null)
{
m_enumerable = Value as IEnumerable ?? CppListToEnumerable(Value);
}
return m_enumerable;
}
}
private IEnumerable m_enumerable;
private CacheObject[] m_cachedEntries;
public CacheList(object obj)
private IEnumerable CppListToEnumerable(object list)
{
GetEnumerable(obj);
EntryType = m_enumerable.GetType().GetGenericArguments()[0];
}
if (EntryType == null) return null;
private void GetEnumerable(object obj)
{
if (obj is IEnumerable isEnumerable)
{
m_enumerable = isEnumerable;
}
else
{
var listValueType = obj.GetType().GetGenericArguments()[0];
var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType });
var method = listType.GetMethod("ToArray");
m_enumerable = (IEnumerable)method.Invoke(obj, new object[0]);
}
return (IEnumerable)typeof(Il2CppSystem.Collections.Generic.List<>)
.MakeGenericType(new Type[] { EntryType })
.GetMethod("ToArray")
.Invoke(list, new object[0]);
}
public override void DrawValue(Rect window, float width)
@ -68,12 +87,12 @@ namespace Explorer
if (IsExpanded)
{
if (count > CppExplorer.ArrayLimit)
if (count > ArrayLimit)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(190);
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
// prev/next page buttons
if (GUILayout.Button("< Prev", null))
@ -84,13 +103,20 @@ namespace Explorer
{
if (ArrayOffset < maxOffset) ArrayOffset++;
}
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.ArrayLimit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != ArrayLimit.ToString() && int.TryParse(limit, out int i))
{
ArrayLimit = i;
}
}
int offset = ArrayOffset * CppExplorer.ArrayLimit;
int offset = ArrayOffset * ArrayLimit;
if (offset >= count) offset = 0;
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
for (int i = offset; i < offset + ArrayLimit && i < count; i++)
{
var entry = m_cachedEntries[i];
@ -99,7 +125,7 @@ namespace Explorer
GUILayout.BeginHorizontal(null);
GUILayout.Space(190);
if (entry == null)
if (entry.Value == null)
{
GUILayout.Label("<i><color=grey>null</color></i>", null);
}
@ -108,43 +134,28 @@ namespace Explorer
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(30) });
entry.DrawValue(window, window.width - 250);
//var lbl = i + ": <color=cyan>" + obj.Value.ToString() + "</color>";
//if (EntryType.IsPrimitive || typeof(string).IsAssignableFrom(EntryType))
//{
// GUILayout.Label(lbl, null);
//}
//else
//{
// GUI.skin.button.alignment = TextAnchor.MiddleLeft;
// if (GUILayout.Button(lbl, null))
// {
// WindowManager.InspectObject(obj, out _);
// }
// GUI.skin.button.alignment = TextAnchor.MiddleCenter;
//}
}
}
}
}
public override void SetValue()
/// <summary>
/// Called when the user presses the "Update" button, or if AutoUpdate is on.
/// </summary>
public override void UpdateValue()
{
throw new NotImplementedException("TODO");
}
base.UpdateValue();
public override void UpdateValue(object obj)
{
GetEnumerable(Value);
if (Value == null) return;
var enumerator = Enumerable?.GetEnumerator();
if (enumerator == null) return;
var list = new List<CacheObject>();
var enumerator = m_enumerable.GetEnumerator();
while (enumerator.MoveNext())
{
list.Add(GetCacheObject(enumerator.Current));
list.Add(GetCacheObject(enumerator.Current, null, null, this.EntryType));
}
m_cachedEntries = list.ToArray();

View File

@ -17,120 +17,139 @@ namespace Explorer
// Reflection window only
public MemberInfo MemberInfo { get; set; }
public ReflectionWindow.MemberInfoType MemberInfoType { get; set; }
// public ReflectionWindow.MemberInfoType MemberInfoType { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public string FullName => $"{MemberInfo.DeclaringType.Name}.{MemberInfo.Name}";
public string ReflectionException;
public bool CanWrite
{
get
{
if (MemberInfo is FieldInfo fi)
{
return !(fi.IsLiteral && !fi.IsInitOnly);
}
else if (MemberInfo is PropertyInfo pi)
{
return pi.CanWrite;
}
else
{
return false;
}
}
}
public ReflectionWindow.MemberInfoType MemberInfoType
{
get
{
if (MemberInfo is FieldInfo) return ReflectionWindow.MemberInfoType.Field;
if (MemberInfo is PropertyInfo) return ReflectionWindow.MemberInfoType.Property;
if (MemberInfo is MethodInfo) return ReflectionWindow.MemberInfoType.Method;
return ReflectionWindow.MemberInfoType.All;
}
}
// methods
public virtual void Init() { }
public abstract void DrawValue(Rect window, float width);
public abstract void SetValue();
public static CacheObject GetCacheObject(object obj)
{
return GetCacheObject(obj, null, null);
}
/// <summary>
/// Gets the CacheObject subclass for an object or MemberInfo
/// </summary>
/// <param name="obj">The current value (can be null if memberInfo is not null)</param>
/// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param>
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param>
/// <returns></returns>
public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
{
CacheObject holder;
var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
if (obj is Il2CppSystem.Object || typeof(Il2CppSystem.Object).IsAssignableFrom(type))
{
var name = type.FullName;
if (name == "UnityEngine.GameObject" || name == "UnityEngine.Transform")
{
holder = new CacheGameObject(obj);
}
else
{
holder = new CacheIl2CppObject();
}
}
else
{
if (type.IsPrimitive || type == typeof(string))
{
holder = new CachePrimitive(obj);
}
else if (type.IsEnum)
{
holder = new CacheEnum(obj);
}
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) || ReflectionHelpers.IsList(type))
{
holder = new CacheList(obj);
}
else if (type.IsValueType)
{
holder = new CacheStruct(obj);
}
else
{
holder = new CacheOther();
}
}
if (holder == null)
if (type == null)
{
MelonLogger.Log("Could not get type for object or memberinfo!");
return null;
}
if (memberInfo != null)
{
holder.MemberInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType;
if (declaringInstance is Il2CppSystem.Object ilInstance && ilInstance.GetType() != memberInfo.DeclaringType)
{
try
{
holder.DeclaringInstance = ilInstance.Il2CppCast(holder.DeclaringType);
}
catch
{
holder.DeclaringInstance = declaringInstance;
}
}
else
{
holder.DeclaringInstance = declaringInstance;
}
return GetCacheObject(obj, memberInfo, declaringInstance, type);
}
if (memberInfo.MemberType == MemberTypes.Field)
{
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Field;
}
else if (memberInfo.MemberType == MemberTypes.Property)
{
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Property;
}
else if (memberInfo.MemberType == MemberTypes.Method)
{
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Method;
}
/// <summary>
/// Gets the CacheObject subclass for an object or MemberInfo
/// </summary>
/// <param name="obj">The current value (can be null if memberInfo is not null)</param>
/// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param>
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param>
/// <param name="type">The type of the object or MemberInfo value.</param>
/// <returns></returns>
public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance, Type type)
{
CacheObject holder;
if ((obj is Il2CppSystem.Object || typeof(Il2CppSystem.Object).IsAssignableFrom(type))
&& (type.FullName.Contains("UnityEngine.GameObject") || type.FullName.Contains("UnityEngine.Transform")))
{
holder = new CacheGameObject();
}
else if (type.IsPrimitive || type == typeof(string))
{
holder = new CachePrimitive();
}
else if (type.IsEnum)
{
holder = new CacheEnum();
}
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) || ReflectionHelpers.IsList(type))
{
holder = new CacheList();
}
else
{
holder = new CacheOther();
}
holder.Value = obj;
holder.ValueType = type.FullName;
if (memberInfo != null)
{
holder.MemberInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType;
holder.DeclaringInstance = declaringInstance;
}
holder.UpdateValue();
holder.Init();
return holder;
}
public void Draw(Rect window, float labelWidth = 180f)
public void Draw(Rect window, float labelWidth = 215f)
{
if (MemberInfo != null)
{
GUILayout.Label("<color=cyan>" + MemberInfo.Name + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
GUILayout.Label("<color=cyan>" + FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
}
else
{
GUILayout.Space(labelWidth);
}
if (Value == null)
if (!string.IsNullOrEmpty(ReflectionException))
{
GUILayout.Label("<i>null (" + this.ValueType + ")</i>", null);
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
}
else if (Value == null)
{
GUILayout.Label("<i>null (" + ValueType + ")</i>", null);
}
else
{
@ -138,9 +157,9 @@ namespace Explorer
}
}
public virtual void UpdateValue(object obj)
public virtual void UpdateValue()
{
if (MemberInfo == null)
if (MemberInfo == null || !string.IsNullOrEmpty(ReflectionException))
{
return;
}
@ -155,34 +174,31 @@ namespace Explorer
else if (MemberInfo.MemberType == MemberTypes.Property)
{
var pi = MemberInfo as PropertyInfo;
Value = pi.GetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, null);
bool isStatic = pi.GetAccessors()[0].IsStatic;
var target = isStatic ? null : DeclaringInstance;
Value = pi.GetValue(target, null);
}
//ReflectionException = null;
}
catch //(Exception e)
catch (Exception e)
{
//MelonLogger.Log($"Error updating MemberInfo value | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
}
public void SetValue(object value, MemberInfo memberInfo, object declaringInstance)
public void SetValue()
{
try
{
if (memberInfo.MemberType == MemberTypes.Field)
if (MemberInfo.MemberType == MemberTypes.Field)
{
var fi = memberInfo as FieldInfo;
if (!(fi.IsLiteral && !fi.IsInitOnly))
{
fi.SetValue(fi.IsStatic ? null : declaringInstance, value);
}
var fi = MemberInfo as FieldInfo;
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, Value);
}
else if (memberInfo.MemberType == MemberTypes.Property)
else if (MemberInfo.MemberType == MemberTypes.Property)
{
var pi = memberInfo as PropertyInfo;
if (pi.CanWrite)
{
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : declaringInstance, value);
}
var pi = MemberInfo as PropertyInfo;
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
}
}
catch (Exception e)

View File

@ -1,49 +1,68 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public class CacheOther : CacheObject
{
private MethodInfo m_toStringMethod;
private bool m_triedToGetMethod;
public MethodInfo ToStringMethod
{
get
{
if (m_toStringMethod == null && !m_triedToGetMethod)
{
if (Value == null) return null;
m_triedToGetMethod = true;
try
{
var methods = ReflectionHelpers.GetActualType(Value)
.GetMethods(ReflectionHelpers.CommonFlags)
.Where(x => x.Name == "ToString")
.GetEnumerator();
while (methods.MoveNext())
{
// just get the first (top-most level) method, then break.
m_toStringMethod = methods.Current;
break;
}
}
catch { }
}
return m_toStringMethod;
}
}
public override void DrawValue(Rect window, float width)
{
string label;
if (Value is UnityEngine.Object uObj)
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
if (!label.Contains(ValueType))
{
label = uObj.name;
label += $" ({ValueType})";
}
else
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
{
label = Value.ToString();
}
string typeLabel = Value.GetType().FullName;
if (!label.Contains(typeLabel))
{
label += $" ({typeLabel})";
label = unityObj.name + " | " + label;
}
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 230) }))
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(width + 40) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
}
public override void SetValue()
{
}
public override void UpdateValue(object obj)
{
}
}
}

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using MelonLoader;
using UnityEngine;
@ -10,7 +7,7 @@ namespace Explorer
{
public class CachePrimitive : CacheObject
{
public enum PrimitiveType
public enum PrimitiveTypes
{
Bool,
Double,
@ -19,67 +16,122 @@ namespace Explorer
String
}
private readonly PrimitiveType m_primitiveType;
private string m_valueToString;
public CachePrimitive(object obj)
public PrimitiveTypes PrimitiveType;
public MethodInfo ParseMethod
{
if (obj is bool)
get
{
m_primitiveType = PrimitiveType.Bool;
if (m_parseMethod == null)
{
Type t = null;
switch (PrimitiveType)
{
case PrimitiveTypes.Bool:
t = typeof(bool); break;
case PrimitiveTypes.Double:
t = typeof(double); break;
case PrimitiveTypes.Float:
t = typeof(float); break;
case PrimitiveTypes.Int:
t = typeof(int); break;
case PrimitiveTypes.String:
t = typeof(string); break;
}
m_parseMethod = t.GetMethod("Parse", new Type[] { typeof(string) });
}
return m_parseMethod;
}
else if (obj is double)
}
private MethodInfo m_parseMethod;
public override void Init()
{
if (Value == null)
{
m_primitiveType = PrimitiveType.Double;
// this must mean it is a string? no other primitive type should be nullable
PrimitiveType = PrimitiveTypes.String;
return;
}
else if (obj is float)
m_valueToString = Value.ToString();
var type = Value.GetType();
if (type == typeof(bool))
{
m_primitiveType = PrimitiveType.Float;
PrimitiveType = PrimitiveTypes.Bool;
}
else if (obj is int)
else if (type == typeof(double))
{
m_primitiveType = PrimitiveType.Int;
PrimitiveType = PrimitiveTypes.Double;
}
else if (obj is string)
else if (type == typeof(float))
{
m_primitiveType = PrimitiveType.String;
PrimitiveType = PrimitiveTypes.Float;
}
else if (type == typeof(int) || type == typeof(long) || type == typeof(uint) || type == typeof(ulong) || type == typeof(IntPtr))
{
PrimitiveType = PrimitiveTypes.Int;
}
else
{
if (type != typeof(string))
{
MelonLogger.Log("Unsupported primitive: " + type);
}
PrimitiveType = PrimitiveTypes.String;
}
}
public override void UpdateValue()
{
base.UpdateValue();
m_valueToString = Value?.ToString();
}
public override void DrawValue(Rect window, float width)
{
if (m_primitiveType == PrimitiveType.Bool && Value is bool b)
if (PrimitiveType == PrimitiveTypes.Bool)
{
var b = (bool)Value;
var color = "<color=" + (b ? "lime>" : "red>");
Value = GUILayout.Toggle((bool)Value, color + Value.ToString() + "</color>", null);
b = GUILayout.Toggle(b, color + b.ToString() + "</color>", null);
if (b != (bool)Value)
{
SetValue();
SetValue(m_valueToString);
}
}
else
{
var toString = Value.ToString();
if (toString.Length > 37)
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
var _width = window.width - 200;
if (m_valueToString.Length > 37)
{
Value = GUILayout.TextArea(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(_width) });
}
else
{
Value = GUILayout.TextField(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(_width) });
}
if (MemberInfo != null)
if (CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
SetValue();
SetValue(m_valueToString);
}
}
}
}
public override void SetValue()
public void SetValue(string value)
{
if (MemberInfo == null)
{
@ -87,19 +139,24 @@ namespace Explorer
return;
}
switch (m_primitiveType)
if (PrimitiveType == PrimitiveTypes.String)
{
case PrimitiveType.Bool:
SetValue(bool.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
case PrimitiveType.Double:
SetValue(double.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
case PrimitiveType.Float:
SetValue(float.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
case PrimitiveType.Int:
SetValue(int.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
case PrimitiveType.String:
SetValue(Value.ToString(), MemberInfo, DeclaringInstance); return;
Value = value;
}
else
{
try
{
var val = ParseMethod.Invoke(null, new object[] { value });
Value = val;
}
catch (Exception e)
{
MelonLogger.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
}
}
SetValue();
}
}
}

View File

@ -1,71 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public class CacheStruct : CacheObject
{
public MethodInfo ToStringMethod { get; private set; }
private static readonly MethodInfo m_defaultToString = typeof(object).GetMethod("ToString");
public CacheStruct(object obj)
{
try
{
var methods = obj.GetType().GetMethods(ReflectionHelpers.CommonFlags).Where(x => x.Name == "ToString");
var enumerator = methods.GetEnumerator();
while (enumerator.MoveNext())
{
ToStringMethod = enumerator.Current;
break;
}
}
catch
{
ToStringMethod = m_defaultToString;
}
}
public override void DrawValue(Rect window, float width)
{
string label;
try
{
label = (string)ToStringMethod.Invoke(Value, null);
}
catch
{
label = Value.ToString();
}
string typeLabel = Value.GetType().FullName;
if (!label.Contains(typeLabel))
{
label += $" ({typeLabel})";
}
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 230) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
}
public override void SetValue()
{
throw new NotImplementedException("TODO");
}
//public override void UpdateValue(object obj)
//{
//}
}
}

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using MelonLoader;
using UnhollowerBaseLib;
namespace Explorer
{
@ -12,7 +13,7 @@ namespace Explorer
// consts
public const string ID = "com.sinai.cppexplorer";
public const string VERSION = "1.4.0";
public const string VERSION = "1.4.2";
public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer"
@ -28,7 +29,6 @@ namespace Explorer
// props
public static bool ShowMenu { get; set; } = false;
public static int ArrayLimit { get; set; } = 20;
// methods

View File

@ -140,8 +140,6 @@
<Compile Include="CachedObjects\CacheGameObject.cs" />
<Compile Include="CachedObjects\CacheList.cs" />
<Compile Include="CachedObjects\CachePrimitive.cs" />
<Compile Include="CachedObjects\CacheIl2CppObject.cs" />
<Compile Include="CachedObjects\CacheStruct.cs" />
<Compile Include="CachedObjects\CacheOther.cs" />
<Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" />

View File

@ -9,6 +9,7 @@ using UnhollowerRuntimeLib;
using UnityEngine;
using BF = System.Reflection.BindingFlags;
using ILBF = Il2CppSystem.Reflection.BindingFlags;
using MelonLoader;
namespace Explorer
{
@ -21,15 +22,59 @@ namespace Explorer
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>();
public static Il2CppSystem.Type ObjectType => Il2CppType.Of<UnityEngine.Object>();
public static Il2CppSystem.Type ComponentType => Il2CppType.Of<Component>();
public static Il2CppSystem.Type BehaviourType => Il2CppType.Of<Behaviour>();
private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
public static object Il2CppCast(object obj, Type castTo)
{
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) return obj;
var generic = m_tryCastMethodInfo.MakeGenericMethod(castTo);
return generic.Invoke(obj, null);
}
public static string ExceptionToString(Exception e)
{
if (IsFailedGeneric(e))
{
return "Unable to initialize this type.";
}
else if (IsObjectCollected(e))
{
return "Garbage collected in Il2Cpp.";
}
return e.GetType() + ", " + e.Message;
}
public static bool IsFailedGeneric(Exception e)
{
return IsExceptionOfType(e, typeof(TargetInvocationException)) && IsExceptionOfType(e, typeof(TypeLoadException));
}
public static bool IsObjectCollected(Exception e)
{
return IsExceptionOfType(e, typeof(ObjectCollectedException));
}
public static bool IsExceptionOfType(Exception e, Type t, bool strict = true, bool checkInner = true)
{
bool isType;
if (strict)
isType = e.GetType() == t;
else
isType = t.IsAssignableFrom(e.GetType());
if (isType) return true;
if (e.InnerException != null && checkInner)
return IsExceptionOfType(e.InnerException, t, strict);
else
return false;
}
public static bool IsList(Type t)
{
return t.IsGenericType

View File

@ -22,7 +22,7 @@ namespace Explorer
}
// helper for drawing a styled button for a GameObject or Transform
public static void GameobjButton(GameObject obj, Action<GameObject> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
public static void GameobjButton(GameObject obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{
bool children = obj.transform.childCount > 0;
@ -52,7 +52,7 @@ namespace Explorer
FastGameobjButton(obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
}
public static void FastGameobjButton(GameObject obj, Color activeColor, string label, bool enabled, Action<GameObject> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
public static void FastGameobjButton(GameObject obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{
if (!obj)
{
@ -79,7 +79,7 @@ namespace Explorer
{
if (specialInspectMethod != null)
{
specialInspectMethod(obj);
specialInspectMethod(obj.transform);
}
else
{
@ -94,13 +94,18 @@ namespace Explorer
if (showSmallInspectBtn)
{
if (GUILayout.Button("Inspect", null))
{
WindowManager.InspectObject(obj, out bool _);
}
SmallInspectButton(obj);
}
GUILayout.EndHorizontal();
}
public static void SmallInspectButton(object obj)
{
if (GUILayout.Button("Inspect", null))
{
WindowManager.InspectObject(obj, out bool _);
}
}
}
}

View File

@ -88,19 +88,6 @@ namespace Explorer
private void MainHeader()
{
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Options:</b>", new GUILayoutOption[] { GUILayout.Width(70) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("Array Limit:", new GUILayoutOption[] { GUILayout.Width(70) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
var _input = GUILayout.TextField(CppExplorer.ArrayLimit.ToString(), new GUILayoutOption[] { GUILayout.Width(60) });
if (int.TryParse(_input, out int _lim))
{
CppExplorer.ArrayLimit = _lim;
}
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
for (int i = 0; i < Pages.Count; i++)
{
@ -116,6 +103,9 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUI.color = Color.white;
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
GUILayout.Space(10);
GUI.color = Color.white;
}

View File

@ -14,9 +14,15 @@ namespace Explorer
public override string Name { get => "Scene Explorer"; set => base.Name = value; }
// ----- Holders for GUI elements ----- //
private int m_pageOffset = 0;
private int m_limit = 20;
private int m_currentTotalCount = 0;
private string m_currentScene = "";
private float m_timeOfLastUpdate = -1f;
// ----- Holders for GUI elements ----- //
private string m_currentScene = "";
// gameobject list
private Transform m_currentTransform;
@ -37,190 +43,84 @@ namespace Explorer
public void OnSceneChange()
{
m_currentScene = UnityHelpers.ActiveSceneName;
SetTransformTarget(null);
}
m_currentTransform = null;
CancelSearch();
public void CheckOffset(ref int offset, int childCount)
{
if (offset >= childCount)
{
offset = 0;
m_pageOffset = 0;
}
}
public override void Update()
{
if (!m_searching)
if (m_searching) return;
if (Time.time - m_timeOfLastUpdate < 1f) return;
m_timeOfLastUpdate = Time.time;
m_objectList = new List<GameObjectCache>();
int offset = m_pageOffset * m_limit;
var allTransforms = new List<Transform>();
// get current list of all transforms (either scene root or our current transform children)
if (m_currentTransform)
{
m_objectList = new List<GameObjectCache>();
if (m_currentTransform)
for (int i = 0; i < m_currentTransform.childCount; i++)
{
var endAppend = new List<GameObjectCache>();
for (int i = 0; i < m_currentTransform.childCount; i++)
{
var child = m_currentTransform.GetChild(i);
if (child)
{
if (child.childCount > 0)
m_objectList.Add(new GameObjectCache(child.gameObject));
else
endAppend.Add(new GameObjectCache(child.gameObject));
}
}
m_objectList.AddRange(endAppend);
endAppend = null;
allTransforms.Add(m_currentTransform.GetChild(i));
}
else
}
else
{
var scene = SceneManager.GetSceneByName(m_currentScene);
var rootObjects = scene.GetRootGameObjects();
foreach (var obj in rootObjects)
{
var scene = SceneManager.GetSceneByName(m_currentScene);
var rootObjects = scene.GetRootGameObjects();
// add objects with children first
foreach (var obj in rootObjects.Where(x => x.transform.childCount > 0))
{
m_objectList.Add(new GameObjectCache(obj));
}
foreach (var obj in rootObjects.Where(x => x.transform.childCount == 0))
{
m_objectList.Add(new GameObjectCache(obj));
}
allTransforms.Add(obj.transform);
}
}
m_currentTotalCount = allTransforms.Count;
// make sure offset doesn't exceed count
CheckOffset(ref offset, m_currentTotalCount);
// sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
for (int i = offset; i < offset + m_limit && i < m_currentTotalCount; i++)
{
var child = allTransforms[i];
m_objectList.Add(new GameObjectCache(child.gameObject));
}
}
// --------- GUI Draw Functions --------- //
public override void DrawWindow()
public void SetTransformTarget(Transform t)
{
try
{
GUILayout.BeginHorizontal(null);
// Current Scene label
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
try
{
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
var scenes = SceneManager.GetAllScenes().ToList();
m_currentTransform = t;
if (scenes.Count > 1)
{
int changeWanted = 0;
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = -1;
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = 1;
}
if (changeWanted != 0)
{
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
index += changeWanted;
if (index > scenes.Count - 1)
{
index = 0;
}
else if (index < 0)
{
index = scenes.Count - 1;
}
m_currentScene = scenes[index].name;
}
}
}
catch { }
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
if (m_searching)
CancelSearch();
GUILayout.EndHorizontal();
// ----- GameObject Search -----
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
m_searchInput = GUILayout.TextField(m_searchInput, null);
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Search();
}
GUILayout.EndHorizontal();
GUILayout.Space(15);
// ************** GameObject list ***************
// ----- main explorer ------
if (!m_searching)
{
if (m_currentTransform != null)
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
TraverseUp();
}
else
{
GUILayout.Label(m_currentTransform.GetGameObjectPath(), null);
}
GUILayout.EndHorizontal();
}
else
{
GUILayout.Label("Scene Root GameObjects:", null);
}
if (m_objectList.Count > 0)
{
foreach (var obj in m_objectList)
{
//UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
}
}
}
else // ------ Scene Search results ------
{
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
CancelSearch();
}
GUILayout.Label("Search Results:", null);
if (m_searchResults.Count > 0)
{
foreach (var obj in m_searchResults)
{
//UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
}
}
else
{
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
}
}
}
catch
{
m_currentTransform = null;
}
}
// -------- Actual Methods (not drawing GUI) ---------- //
public void SetTransformTarget(GameObject obj)
{
m_currentTransform = obj.transform;
CancelSearch();
m_timeOfLastUpdate = -1f;
Update();
}
public void TraverseUp()
{
if (m_currentTransform.parent != null)
{
m_currentTransform = m_currentTransform.parent;
SetTransformTarget(m_currentTransform.parent);
}
else
{
m_currentTransform = null;
SetTransformTarget(null);
}
}
@ -228,6 +128,7 @@ namespace Explorer
{
m_searchResults = SearchSceneObjects(m_searchInput);
m_searching = true;
m_currentTotalCount = m_searchResults.Count;
}
public void CancelSearch()
@ -249,6 +150,220 @@ namespace Explorer
return matches;
}
// --------- GUI Draw Function --------- //
public override void DrawWindow()
{
try
{
DrawHeaderArea();
GUILayout.BeginVertical(GUI.skin.box, null);
DrawPageButtons();
if (!m_searching)
{
DrawGameObjectList();
}
else
{
DrawSearchResultsList();
}
GUILayout.EndVertical();
}
catch (Exception e)
{
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
m_currentTransform = null;
}
}
private void DrawHeaderArea()
{
GUILayout.BeginHorizontal(null);
// Current Scene label
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
try
{
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
var scenes = SceneManager.GetAllScenes().ToList();
if (scenes.Count > 1)
{
int changeWanted = 0;
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = -1;
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = 1;
}
if (changeWanted != 0)
{
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
index += changeWanted;
if (index > scenes.Count - 1)
{
index = 0;
}
else if (index < 0)
{
index = scenes.Count - 1;
}
m_currentScene = scenes[index].name;
}
}
}
catch { }
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
GUILayout.EndHorizontal();
// ----- GameObject Search -----
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
m_searchInput = GUILayout.TextField(m_searchInput, null);
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Search();
}
GUILayout.EndHorizontal();
GUILayout.Space(5);
}
private void DrawPageButtons()
{
GUILayout.BeginHorizontal(null);
GUILayout.Label("Limit per page: ", new GUILayoutOption[] { GUILayout.Width(100) });
var limit = m_limit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(30) });
if (int.TryParse(limit, out int lim))
{
m_limit = lim;
}
// prev/next page buttons
if (m_currentTotalCount > m_limit)
{
int count = m_currentTotalCount;
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1;
if (GUILayout.Button("< Prev", null))
{
if (m_pageOffset > 0) m_pageOffset--;
m_timeOfLastUpdate = -1f;
Update();
}
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
if (GUILayout.Button("Next >", null))
{
if (m_pageOffset < maxOffset) m_pageOffset++;
m_timeOfLastUpdate = -1f;
Update();
}
}
GUILayout.EndHorizontal();
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
private void DrawGameObjectList()
{
if (m_currentTransform != null)
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
TraverseUp();
}
else
{
GUILayout.Label("<color=cyan>" + m_currentTransform.GetGameObjectPath() + "</color>",
new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 187f) });
}
UIHelpers.SmallInspectButton(m_currentTransform);
GUILayout.EndHorizontal();
}
else
{
GUILayout.Label("Scene Root GameObjects:", null);
}
if (m_objectList.Count > 0)
{
foreach (var obj in m_objectList)
{
if (!obj.RefGameObject)
{
string label = "<color=red><i>null";
if (obj.RefGameObject != null)
{
label += " (Destroyed)";
}
label += "</i></color>";
GUILayout.Label(label, null);
}
else
{
UIHelpers.FastGameobjButton(obj.RefGameObject,
obj.EnabledColor,
obj.Label,
obj.RefGameObject.activeSelf,
SetTransformTarget,
true,
MainMenu.MainRect.width - 170);
}
}
}
}
private void DrawSearchResultsList()
{
if (GUILayout.Button("<- Cancel Search", new GUILayoutOption[] { GUILayout.Width(150) }))
{
CancelSearch();
}
GUILayout.Label("Search Results:", null);
if (m_searchResults.Count > 0)
{
int offset = m_pageOffset * m_limit;
if (offset >= m_searchResults.Count)
{
offset = 0;
m_pageOffset = 0;
}
for (int i = offset; i < offset + m_limit && offset < m_searchResults.Count; i++)
{
var obj = m_searchResults[i];
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
}
}
else
{
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
}
}
// -------- Mini GameObjectCache class ---------- //
public class GameObjectCache
{

View File

@ -111,7 +111,7 @@ namespace Explorer
{
// prev/next page buttons
GUILayout.BeginHorizontal(null);
int maxOffset = (int)Mathf.Ceil(count / this.m_limit);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1;
if (GUILayout.Button("< Prev", null))
{
if (m_pageOffset > 0) m_pageOffset--;
@ -135,7 +135,7 @@ namespace Explorer
int offset = m_pageOffset * this.m_limit;
if (offset >= count) m_pageOffset = 0;
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
for (int i = offset; i < offset + m_limit && i < count; i++)
{
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
//m_searchResults[i].DrawValue(MainMenu.MainRect);
@ -270,6 +270,7 @@ namespace Explorer
{
var findType = ReflectionHelpers.GetTypeByName(_type);
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName);
}
catch (Exception e)
{
@ -299,8 +300,13 @@ namespace Explorer
var allObjectsOfType = Resources.FindObjectsOfTypeAll(searchType);
//MelonLogger.Log("Found count: " + allObjectsOfType.Length);
int i = 0;
foreach (var obj in allObjectsOfType)
{
if (i >= 2000) break;
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
{
continue;
@ -322,6 +328,8 @@ namespace Explorer
{
matches.Add(obj);
}
i++;
}
return matches;

View File

@ -29,7 +29,7 @@ namespace Explorer
private float m_rotateAmount = 50f;
private float m_scaleAmount = 0.1f;
List<Component> m_cachedDestroyList = new List<Component>();
private List<Component> m_cachedDestroyList = new List<Component>();
//private string m_addComponentInput = "";
private string m_setParentInput = "";
@ -91,7 +91,7 @@ namespace Explorer
}
}
private void InspectGameObject(GameObject obj)
private void InspectGameObject(Transform obj)
{
var window = WindowManager.InspectObject(obj, out bool created);
@ -137,7 +137,7 @@ namespace Explorer
{
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
{
ScenePage.Instance.SetTransformTarget(m_object);
ScenePage.Instance.SetTransformTarget(m_object.transform);
MainMenu.SetCurrentPage(0);
}
}
@ -150,7 +150,7 @@ namespace Explorer
{
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
InspectGameObject(m_object.transform.parent.gameObject);
InspectGameObject(m_object.transform.parent);
}
}
GUILayout.TextArea(pathlabel, null);
@ -235,18 +235,16 @@ namespace Explorer
var m_components = new Il2CppSystem.Collections.Generic.List<Component>();
m_object.GetComponentsInternal(Il2CppType.Of<Component>(), false, false, true, false, m_components);
var ilTypeOfTransform = Il2CppType.Of<Transform>();
var ilTypeOfBehaviour = Il2CppType.Of<Behaviour>();
foreach (var component in m_components)
{
var ilType = component.GetIl2CppType();
if (ilType == ilTypeOfTransform)
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
GUILayout.BeginHorizontal(null);
if (ilTypeOfBehaviour.IsAssignableFrom(ilType))
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
{
BehaviourEnabledBtn(component.TryCast<Behaviour>());
}
@ -274,24 +272,6 @@ namespace Explorer
GUILayout.EndScrollView();
//GUILayout.BeginHorizontal(null);
//m_addComponentInput = GUILayout.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 150) });
//if (GUILayout.Button("Add Component", new GUILayoutOption[] { GUILayout.Width(120) }))
//{
// if (HPExplorer.GetType(m_addComponentInput) is Type type && typeof(Component).IsAssignableFrom(type))
// {
// var comp = m_object.AddComponent(type);
// var list = m_components.ToList();
// list.Add(comp);
// m_components = list.ToArray();
// }
// else
// {
// MelonLogger.LogWarning($"Could not get type '{m_addComponentInput}'. If it's not a typo, try the fully qualified name.");
// }
//}
//GUILayout.EndHorizontal();
GUILayout.EndVertical();
}

View File

@ -20,10 +20,12 @@ namespace Explorer
private CacheObject[] m_cachedMembers;
private CacheObject[] m_cachedMemberFiltered;
private int m_pageOffset;
private int m_limitPerPage = 20;
private bool m_autoUpdate = false;
private string m_search = "";
public MemberInfoType m_filter = MemberInfoType.Property;
private bool m_hideFailedReflection = false;
public enum MemberInfoType
{
@ -72,7 +74,7 @@ namespace Explorer
{
foreach (var member in m_cachedMemberFiltered)
{
member.UpdateValue(Target);
member.UpdateValue();
}
}
@ -80,74 +82,78 @@ namespace Explorer
{
if (m_filter != MemberInfoType.All && m_filter != holder.MemberInfoType) return false;
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
if (m_search == "" || holder.MemberInfo == null) return true;
return holder.MemberInfo.Name
return holder.FullName
.ToLower()
.Contains(m_search.ToLower());
}
private void CacheMembers(Type[] types, List<string> names = null)
private void CacheMembers(Type[] types)
{
if (names == null)
{
names = new List<string>();
}
var list = new List<CacheObject>();
foreach (var type in types)
var names = new List<string>();
foreach (var declaringType in types)
{
if (declaringType == typeof(Il2CppObjectBase)) continue;
MemberInfo[] infos;
string exception = null;
try
{
infos = type.GetMembers(ReflectionHelpers.CommonFlags);
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
}
catch
{
MelonLogger.Log("Exception getting members for type: " + type.Name);
MelonLogger.Log("Exception getting members for type: " + declaringType.Name);
continue;
}
//object value = null;
object target = Target;
if (target is Il2CppSystem.Object ilObject)
{
try
{
target = ilObject.Il2CppCast(declaringType);
}
catch (Exception e)
{
exception = ReflectionHelpers.ExceptionToString(e);
}
}
foreach (var member in infos)
{
try
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
{
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
if (member.Name == "Il2CppType") continue;
try
{
if (member.Name == "Il2CppType") continue;
var name = member.DeclaringType.Name + "." + member.Name;
if (names.Contains(name)) continue;
names.Add(name);
if (names.Contains(member.Name)) continue;
names.Add(member.Name);
object value = null;
object target = Target;
if (target is Il2CppSystem.Object ilObject)
var cached = CacheObject.GetCacheObject(null, member, target);
if (cached != null)
{
if (member.DeclaringType == typeof(Il2CppObjectBase)) continue;
target = ilObject.Il2CppCast(member.DeclaringType);
list.Add(cached);
cached.ReflectionException = exception;
}
if (member is FieldInfo)
{
value = (member as FieldInfo).GetValue(target);
}
else if (member is PropertyInfo)
{
value = (member as PropertyInfo).GetValue(target);
}
list.Add(CacheObject.GetCacheObject(value, member, Target));
}
}
catch (Exception e)
{
MelonLogger.Log("Exception caching member " + member.Name + "!");
MelonLogger.Log(e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
catch (Exception e)
{
MelonLogger.Log("Exception caching member!");
MelonLogger.Log(e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
}
}
}
}
@ -166,7 +172,7 @@ namespace Explorer
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.Name + "</color>", null);
GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.FullName + "</color>", null);
bool unityObj = Target is UnityEngine.Object;
@ -203,6 +209,13 @@ namespace Explorer
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Search:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
m_search = GUILayout.TextField(m_search, null);
GUILayout.Label("<b>Limit per page:</b>", new GUILayoutOption[] { GUILayout.Width(125) });
var limitString = m_limitPerPage.ToString();
limitString = GUILayout.TextField(limitString, new GUILayoutOption[] { GUILayout.Width(60) });
if (int.TryParse(limitString, out int i))
{
m_limitPerPage = i;
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
@ -220,6 +233,8 @@ namespace Explorer
}
GUI.color = m_autoUpdate ? Color.green : Color.red;
m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) });
GUI.color = m_hideFailedReflection ? Color.green : Color.red;
m_hideFailedReflection = GUILayout.Toggle(m_hideFailedReflection, "Hide failed Reflection?", new GUILayoutOption[] { GUILayout.Width(150) });
GUI.color = Color.white;
GUILayout.EndHorizontal();
@ -227,14 +242,15 @@ namespace Explorer
int count = m_cachedMemberFiltered.Length;
if (count > 20)
if (count > m_limitPerPage)
{
// prev/next page buttons
GUILayout.BeginHorizontal(null);
int maxOffset = (int)Mathf.Ceil(count / 20);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limitPerPage)) - 1;
if (GUILayout.Button("< Prev", null))
{
if (m_pageOffset > 0) m_pageOffset--;
scroll = Vector2.zero;
}
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
@ -242,6 +258,7 @@ namespace Explorer
if (GUILayout.Button("Next >", null))
{
if (m_pageOffset < maxOffset) m_pageOffset++;
scroll = Vector2.zero;
}
GUILayout.EndHorizontal();
}
@ -286,7 +303,7 @@ namespace Explorer
GUILayout.Label($"<size=18><b><color=gold>{title}</color></b></size>", null);
int offset = (m_pageOffset * 20) + index;
int offset = (m_pageOffset * m_limitPerPage) + index;
if (offset >= m_cachedMemberFiltered.Length)
{
@ -294,7 +311,7 @@ namespace Explorer
offset = 0;
}
for (int j = offset; j < offset + 20 && j < members.Length; j++)
for (int j = offset; j < offset + m_limitPerPage && j < members.Length; j++)
{
var holder = members[j];
@ -314,7 +331,7 @@ namespace Explorer
GUILayout.EndHorizontal();
index++;
if (index >= 20) break;
if (index >= m_limitPerPage) break;
}
}

View File

@ -104,7 +104,7 @@ namespace Explorer
foreach (var window in Windows)
{
if (obj == window.Target)
if (ReferenceEquals(obj, window.Target))
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);