mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-01 19:13:03 +08:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
6bafab785b | |||
62b1688d53 | |||
4d015cbe93 | |||
0da8f4faea | |||
b264151c46 | |||
3d2bc7cd4b | |||
85c26e6af7 | |||
b149efa234 | |||
72c222d59a | |||
153ad2268b | |||
1ba9b2eae1 | |||
be2da96cc0 | |||
a2405d69c5 |
29
README.md
29
README.md
@ -1,9 +1,20 @@
|
||||
# CppExplorer
|
||||
# CppExplorer []()
|
||||
|
||||
[]()
|
||||
<p align="center">
|
||||
<img align="center" src="https://i.imgur.com/1ZoZemW.png">
|
||||
</p>
|
||||
|
||||
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).
|
||||
<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>
|
||||
|
||||
<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
|
||||
@ -27,8 +38,20 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
|
||||
* Press F7 to show or hide the menu.
|
||||
* Simply browse through the scene, search for objects, etc, it's pretty self-explanatory.
|
||||
|
||||
### Help! I can't use the mouse!
|
||||
|
||||
It is fairly common for games to override mouse control with their own mouse behaviour. Unfortunately, it's not feasible for CppExplorer to handle this due to how differently every game will go about it.
|
||||
|
||||
In order to fix this problem, you can:
|
||||
* Use [VRCExplorerMouseControl](https://github.com/sinaioutlander/VRCExplorerMouseControl) (for VRChat)
|
||||
* Use [HPExplorerMouseControl](https://github.com/sinaioutlander/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl) (for Hellpoint)
|
||||
* In general, pressing Escape (to open a menu) will usually give you temporary control over the mouse.
|
||||
* Create your own mini-plugin using one of the two plugins above as an example. Usually only 1 or 2 simple Harmony patches are needed to fix the problem.
|
||||
|
||||
## 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)
|
||||
|
72
src/CachedObjects/CacheEnum.cs
Normal file
72
src/CachedObjects/CacheEnum.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheEnum : CacheObject
|
||||
{
|
||||
private readonly Type m_enumType;
|
||||
private readonly string[] m_names;
|
||||
|
||||
public CacheEnum(object obj)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
m_enumType = obj.GetType();
|
||||
m_names = Enum.GetNames(m_enumType);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (MemberInfo != null)
|
||||
{
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
SetEnum(ref Value, -1);
|
||||
SetValue();
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
SetEnum(ref Value, 1);
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void SetEnum(ref object value, int change)
|
||||
{
|
||||
var names = m_names.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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
src/CachedObjects/CacheGameObject.cs
Normal file
53
src/CachedObjects/CacheGameObject.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheGameObject : CacheObject
|
||||
{
|
||||
private GameObject m_gameObject;
|
||||
|
||||
public CacheGameObject(object obj)
|
||||
{
|
||||
if (obj != null)
|
||||
m_gameObject = GetGameObject(obj);
|
||||
}
|
||||
|
||||
private GameObject GetGameObject(object obj)
|
||||
{
|
||||
if (obj is Il2CppSystem.Object ilObj)
|
||||
{
|
||||
var ilType = ilObj.GetIl2CppType();
|
||||
|
||||
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
|
||||
{
|
||||
return ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
UIHelpers.GameobjButton(m_gameObject, null, false, width);
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
throw new NotImplementedException("TODO");
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
m_gameObject = GetGameObject(Value);
|
||||
}
|
||||
}
|
||||
}
|
170
src/CachedObjects/CacheList.cs
Normal file
170
src/CachedObjects/CacheList.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Mono.CSharp;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public partial class CacheList : CacheObject
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public int ArrayOffset { get; set; }
|
||||
|
||||
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)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
Value = obj;
|
||||
EntryType = obj.GetType().GetGenericArguments()[0];
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable CppListToEnumerable(object list)
|
||||
{
|
||||
if (EntryType == null) return null;
|
||||
|
||||
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)
|
||||
{
|
||||
int count = m_cachedEntries.Length;
|
||||
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
if (count > CppExplorer.ArrayLimit)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(190);
|
||||
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)CppExplorer.ArrayLimit)) - 1;
|
||||
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
// prev/next page buttons
|
||||
if (GUILayout.Button("< Prev", null))
|
||||
{
|
||||
if (ArrayOffset > 0) ArrayOffset--;
|
||||
}
|
||||
if (GUILayout.Button("Next >", null))
|
||||
{
|
||||
if (ArrayOffset < maxOffset) ArrayOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
int offset = ArrayOffset * CppExplorer.ArrayLimit;
|
||||
|
||||
if (offset >= count) offset = 0;
|
||||
|
||||
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
|
||||
{
|
||||
var entry = m_cachedEntries[i];
|
||||
|
||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(190);
|
||||
|
||||
if (entry.Value == null)
|
||||
{
|
||||
GUILayout.Label("<i><color=grey>null</color></i>", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
|
||||
entry.DrawValue(window, window.width - 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
throw new NotImplementedException("TODO");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the user presses the "Update" button, or if AutoUpdate is on.
|
||||
/// </summary>
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value == null) return;
|
||||
|
||||
var enumerator = Enumerable?.GetEnumerator();
|
||||
|
||||
if (enumerator == null) return;
|
||||
|
||||
var list = new List<CacheObject>();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
list.Add(GetCacheObject(enumerator.Current));
|
||||
}
|
||||
|
||||
m_cachedEntries = list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
194
src/CachedObjects/CacheObject.cs
Normal file
194
src/CachedObjects/CacheObject.cs
Normal file
@ -0,0 +1,194 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public abstract class CacheObject
|
||||
{
|
||||
public object Value;
|
||||
public string ValueType;
|
||||
|
||||
// Reflection window only
|
||||
public MemberInfo MemberInfo { 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;
|
||||
|
||||
// methods
|
||||
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))
|
||||
&& (type.FullName.Contains("UnityEngine.GameObject") || type.FullName.Contains("UnityEngine.Transform")))
|
||||
{
|
||||
holder = new CacheGameObject(obj);
|
||||
}
|
||||
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
|
||||
{
|
||||
holder = new CacheOther();
|
||||
}
|
||||
|
||||
if (memberInfo != null)
|
||||
{
|
||||
holder.MemberInfo = memberInfo;
|
||||
holder.DeclaringType = memberInfo.DeclaringType;
|
||||
holder.DeclaringInstance = declaringInstance;
|
||||
|
||||
//if (declaringInstance is Il2CppSystem.Object ilInstance && ilInstance.GetType() != memberInfo.DeclaringType)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// holder.DeclaringInstance = ilInstance.Il2CppCast(holder.DeclaringType);
|
||||
// }
|
||||
// catch (Exception e)
|
||||
// {
|
||||
// holder.ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||
// holder.DeclaringInstance = declaringInstance;
|
||||
// }
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// holder.DeclaringInstance = declaringInstance;
|
||||
//}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
holder.Value = obj;
|
||||
holder.ValueType = type.FullName;
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
public void Draw(Rect window, float labelWidth = 180f)
|
||||
{
|
||||
if (MemberInfo != null)
|
||||
{
|
||||
GUILayout.Label("<color=cyan>" + FullName + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Space(labelWidth);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ReflectionException))
|
||||
{
|
||||
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
|
||||
}
|
||||
else if (Value == null)
|
||||
{
|
||||
GUILayout.Label("<i>null (" + this.ValueType + ")</i>", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawValue(window, window.width - labelWidth - 90);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void UpdateValue()
|
||||
{
|
||||
if (MemberInfo == null || !string.IsNullOrEmpty(ReflectionException))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (MemberInfo.MemberType == MemberTypes.Field)
|
||||
{
|
||||
var fi = MemberInfo as FieldInfo;
|
||||
Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
||||
}
|
||||
else if (MemberInfo.MemberType == MemberTypes.Property)
|
||||
{
|
||||
var pi = MemberInfo as PropertyInfo;
|
||||
bool isStatic = pi.GetAccessors()[0].IsStatic;
|
||||
var target = isStatic ? null : DeclaringInstance;
|
||||
Value = pi.GetValue(target, null);
|
||||
}
|
||||
//ReflectionException = null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue(object value, MemberInfo memberInfo, object declaringInstance)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (memberInfo.MemberType == MemberTypes.Field)
|
||||
{
|
||||
var fi = memberInfo as FieldInfo;
|
||||
if (!(fi.IsLiteral && !fi.IsInitOnly))
|
||||
{
|
||||
fi.SetValue(fi.IsStatic ? null : declaringInstance, value);
|
||||
}
|
||||
}
|
||||
else if (memberInfo.MemberType == MemberTypes.Property)
|
||||
{
|
||||
var pi = memberInfo as PropertyInfo;
|
||||
if (pi.CanWrite)
|
||||
{
|
||||
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : declaringInstance, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
src/CachedObjects/CacheOther.cs
Normal file
78
src/CachedObjects/CacheOther.cs
Normal file
@ -0,0 +1,78 @@
|
||||
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 = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
|
||||
|
||||
if (!label.Contains(ValueType))
|
||||
{
|
||||
label += $" ({ValueType})";
|
||||
}
|
||||
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
//public override void UpdateValue(object obj)
|
||||
//{
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
107
src/CachedObjects/CachePrimitive.cs
Normal file
107
src/CachedObjects/CachePrimitive.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CachePrimitive : CacheObject
|
||||
{
|
||||
public enum PrimitiveType
|
||||
{
|
||||
Bool,
|
||||
Double,
|
||||
Float,
|
||||
Int,
|
||||
String
|
||||
}
|
||||
|
||||
private readonly PrimitiveType m_primitiveType;
|
||||
|
||||
public CachePrimitive(object obj)
|
||||
{
|
||||
if (obj == null) return;
|
||||
|
||||
if (obj is bool)
|
||||
{
|
||||
m_primitiveType = PrimitiveType.Bool;
|
||||
}
|
||||
else if (obj is double)
|
||||
{
|
||||
m_primitiveType = PrimitiveType.Double;
|
||||
}
|
||||
else if (obj is float)
|
||||
{
|
||||
m_primitiveType = PrimitiveType.Float;
|
||||
}
|
||||
else if (obj is int || obj is IntPtr || obj is uint)
|
||||
{
|
||||
m_primitiveType = PrimitiveType.Int;
|
||||
}
|
||||
else if (obj is string)
|
||||
{
|
||||
m_primitiveType = PrimitiveType.String;
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_primitiveType == PrimitiveType.Bool && Value is bool b)
|
||||
{
|
||||
var color = "<color=" + (b ? "lime>" : "red>");
|
||||
Value = GUILayout.Toggle((bool)Value, color + Value.ToString() + "</color>", null);
|
||||
|
||||
if (b != (bool)Value)
|
||||
{
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var toString = Value.ToString();
|
||||
if (toString.Length > 37)
|
||||
{
|
||||
Value = GUILayout.TextArea(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
|
||||
}
|
||||
else
|
||||
{
|
||||
Value = GUILayout.TextField(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
|
||||
}
|
||||
|
||||
if (MemberInfo != null)
|
||||
{
|
||||
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetValue()
|
||||
{
|
||||
if (MemberInfo == null)
|
||||
{
|
||||
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (m_primitiveType)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,9 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using Harmony;
|
||||
using Il2CppSystem.Runtime.InteropServices;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
@ -17,53 +13,23 @@ namespace Explorer
|
||||
// consts
|
||||
|
||||
public const string ID = "com.sinai.cppexplorer";
|
||||
public const string VERSION = "1.3.3";
|
||||
public const string VERSION = "1.4.1";
|
||||
public const string AUTHOR = "Sinai";
|
||||
|
||||
public const string NAME = "CppExplorer"
|
||||
#if Release_Unity2018
|
||||
public const string NAME = "IL2CPP Runtime Explorer (Unity 2018)";
|
||||
#else
|
||||
public const string NAME = "IL2CPP Runtime Explorer";
|
||||
+ " (Unity 2018)"
|
||||
#endif
|
||||
;
|
||||
|
||||
// fields
|
||||
|
||||
public static CppExplorer Instance;
|
||||
private string m_objUnderMouseName = "";
|
||||
private Camera m_main;
|
||||
|
||||
// props
|
||||
|
||||
public static bool ShowMenu { get; set; } = false;
|
||||
public static int ArrayLimit { get; set; } = 20;
|
||||
public bool MouseInspect { get; set; } = false;
|
||||
|
||||
// prop helpers
|
||||
|
||||
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
|
||||
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 string ActiveSceneName
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||
}
|
||||
}
|
||||
|
||||
public Camera MainCamera
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_main == null)
|
||||
{
|
||||
m_main = Camera.main;
|
||||
}
|
||||
return m_main;
|
||||
}
|
||||
}
|
||||
|
||||
// methods
|
||||
|
||||
@ -76,21 +42,15 @@ namespace Explorer
|
||||
new MainMenu();
|
||||
new WindowManager();
|
||||
|
||||
// done init
|
||||
ShowMenu = true;
|
||||
}
|
||||
|
||||
// On scene change
|
||||
public override void OnLevelWasLoaded(int level)
|
||||
{
|
||||
if (ScenePage.Instance != null)
|
||||
{
|
||||
ScenePage.Instance.OnSceneChange();
|
||||
SearchPage.Instance.OnSceneChange();
|
||||
}
|
||||
ScenePage.Instance?.OnSceneChange();
|
||||
SearchPage.Instance?.OnSceneChange();
|
||||
}
|
||||
|
||||
// Update
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F7))
|
||||
@ -109,43 +69,7 @@ namespace Explorer
|
||||
MainMenu.Instance.Update();
|
||||
WindowManager.Instance.Update();
|
||||
|
||||
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
|
||||
{
|
||||
MouseInspect = !MouseInspect;
|
||||
}
|
||||
|
||||
if (MouseInspect)
|
||||
{
|
||||
InspectUnderMouse();
|
||||
}
|
||||
}
|
||||
else if (MouseInspect)
|
||||
{
|
||||
MouseInspect = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void InspectUnderMouse()
|
||||
{
|
||||
Ray ray = MainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||
{
|
||||
var obj = hit.transform.gameObject;
|
||||
|
||||
m_objUnderMouseName = GetGameObjectPath(obj.transform);
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
MouseInspect = false;
|
||||
m_objUnderMouseName = "";
|
||||
|
||||
WindowManager.InspectObject(obj, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_objUnderMouseName = "";
|
||||
InspectUnderMouse.Update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,79 +80,7 @@ namespace Explorer
|
||||
MainMenu.Instance.OnGUI();
|
||||
WindowManager.Instance.OnGUI();
|
||||
|
||||
if (MouseInspect)
|
||||
{
|
||||
if (m_objUnderMouseName != "")
|
||||
{
|
||||
var pos = Input.mousePosition;
|
||||
var rect = new Rect(
|
||||
pos.x - (Screen.width / 2), // x
|
||||
Screen.height - pos.y - 50, // y
|
||||
Screen.width, // w
|
||||
50 // h
|
||||
);
|
||||
|
||||
var origAlign = GUI.skin.label.alignment;
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
//shadow text
|
||||
GUI.Label(rect, $"<color=black>{m_objUnderMouseName}</color>");
|
||||
//white text
|
||||
GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName);
|
||||
|
||||
GUI.skin.label.alignment = origAlign;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ************** public helpers **************
|
||||
|
||||
public static object Il2CppCast(object obj, Type castTo)
|
||||
{
|
||||
var method = typeof(Il2CppObjectBase).GetMethod("TryCast");
|
||||
var generic = method.MakeGenericMethod(castTo);
|
||||
return generic.Invoke(obj, null);
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(Transform _transform)
|
||||
{
|
||||
return GetGameObjectPath(_transform, true);
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(Transform _transform, bool _includeItemName)
|
||||
{
|
||||
string text = _includeItemName ? ("/" + _transform.name) : "";
|
||||
GameObject gameObject = _transform.gameObject;
|
||||
while (gameObject.transform.parent != null)
|
||||
{
|
||||
gameObject = gameObject.transform.parent.gameObject;
|
||||
text = "/" + gameObject.name + text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public static Type GetType(string _type)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (asm.GetType(_type) is Type type)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
InspectUnderMouse.OnGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,24 +136,32 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CachedObjects\CacheEnum.cs" />
|
||||
<Compile Include="CachedObjects\CacheGameObject.cs" />
|
||||
<Compile Include="CachedObjects\CacheList.cs" />
|
||||
<Compile Include="CachedObjects\CachePrimitive.cs" />
|
||||
<Compile Include="CachedObjects\CacheOther.cs" />
|
||||
<Compile Include="CppExplorer.cs" />
|
||||
<Compile Include="Inspectors\Reflection\FieldInfoHolder.cs" />
|
||||
<Compile Include="Inspectors\Reflection\MemberInfoHolder.cs" />
|
||||
<Compile Include="Inspectors\Reflection\PropertyInfoHolder.cs" />
|
||||
<Compile Include="Inspectors\UIWindow.cs" />
|
||||
<Compile Include="Extensions\ReflectionExtensions.cs" />
|
||||
<Compile Include="Extensions\UnityExtensions.cs" />
|
||||
<Compile Include="Helpers\ReflectionHelpers.cs" />
|
||||
<Compile Include="Helpers\UIHelpers.cs" />
|
||||
<Compile Include="Helpers\UnityHelpers.cs" />
|
||||
<Compile Include="MainMenu\InspectUnderMouse.cs" />
|
||||
<Compile Include="CachedObjects\CacheObject.cs" />
|
||||
<Compile Include="Windows\UIWindow.cs" />
|
||||
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
||||
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
||||
<Compile Include="MainMenu\Pages\Console\REPLHelper.cs" />
|
||||
<Compile Include="MainMenu\Pages\WindowPage.cs" />
|
||||
<Compile Include="WindowManager.cs" />
|
||||
<Compile Include="Windows\WindowManager.cs" />
|
||||
<Compile Include="MainMenu\MainMenu.cs" />
|
||||
<Compile Include="Inspectors\GameObjectWindow.cs" />
|
||||
<Compile Include="Inspectors\ReflectionWindow.cs" />
|
||||
<Compile Include="Windows\GameObjectWindow.cs" />
|
||||
<Compile Include="Windows\ReflectionWindow.cs" />
|
||||
<Compile Include="MainMenu\Pages\ScenePage.cs" />
|
||||
<Compile Include="MainMenu\Pages\SearchPage.cs" />
|
||||
<Compile Include="UIStyles.cs" />
|
||||
<Compile Include="Helpers\UIStyles.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="utils\AccessTools.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
16
src/Extensions/ReflectionExtensions.cs
Normal file
16
src/Extensions/ReflectionExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
public static object Il2CppCast(this object obj, Type castTo)
|
||||
{
|
||||
return ReflectionHelpers.Il2CppCast(obj, castTo);
|
||||
}
|
||||
}
|
||||
}
|
29
src/Extensions/UnityExtensions.cs
Normal file
29
src/Extensions/UnityExtensions.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class UnityExtensions
|
||||
{
|
||||
public static string GetGameObjectPath(this Transform _transform)
|
||||
{
|
||||
return GetGameObjectPath(_transform, true);
|
||||
}
|
||||
|
||||
public static string GetGameObjectPath(this Transform _transform, bool _includeThisName)
|
||||
{
|
||||
string path = _includeThisName ? ("/" + _transform.name) : "";
|
||||
GameObject gameObject = _transform.gameObject;
|
||||
while (gameObject.transform.parent != null)
|
||||
{
|
||||
gameObject = gameObject.transform.parent.gameObject;
|
||||
path = "/" + gameObject.name + path;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
151
src/Helpers/ReflectionHelpers.cs
Normal file
151
src/Helpers/ReflectionHelpers.cs
Normal file
@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using ILBF = Il2CppSystem.Reflection.BindingFlags;
|
||||
using MelonLoader;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ReflectionHelpers
|
||||
{
|
||||
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
||||
public static ILBF CommonFlags_IL = ILBF.Public | ILBF.NonPublic | ILBF.Instance | ILBF.Static;
|
||||
|
||||
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
|
||||
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>();
|
||||
|
||||
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
|
||||
&& t.GetGenericTypeDefinition() is Type typeDef
|
||||
&& (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>)));
|
||||
}
|
||||
|
||||
public static Type GetTypeByName(string typeName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (asm.GetType(typeName) is Type type)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type GetActualType(object m_object)
|
||||
{
|
||||
if (m_object == null) return null;
|
||||
|
||||
if (m_object is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var iltype = ilObject.GetIl2CppType();
|
||||
return Type.GetType(iltype.AssemblyQualifiedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_object.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
public static Type[] GetAllBaseTypes(object m_object)
|
||||
{
|
||||
var list = new List<Type>();
|
||||
|
||||
if (m_object is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var ilType = ilObject.GetIl2CppType();
|
||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilTypeToManaged)
|
||||
{
|
||||
list.Add(ilTypeToManaged);
|
||||
|
||||
while (ilType.BaseType != null)
|
||||
{
|
||||
ilType = ilType.BaseType;
|
||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilBaseTypeToManaged)
|
||||
{
|
||||
list.Add(ilBaseTypeToManaged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = m_object.GetType();
|
||||
list.Add(type);
|
||||
while (type.BaseType != null)
|
||||
{
|
||||
type = type.BaseType;
|
||||
list.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
106
src/Helpers/UIHelpers.cs
Normal file
106
src/Helpers/UIHelpers.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class UIHelpers
|
||||
{
|
||||
// helper for "Instantiate" button on UnityEngine.Objects
|
||||
public static void InstantiateButton(Object obj, float width = 100)
|
||||
{
|
||||
if (GUILayout.Button("Instantiate", new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
{
|
||||
var newobj = Object.Instantiate(obj);
|
||||
|
||||
WindowManager.InspectObject(newobj, out _);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
bool children = obj.transform.childCount > 0;
|
||||
|
||||
string label = children ? "[" + obj.transform.childCount + " children] " : "";
|
||||
label += obj.name;
|
||||
|
||||
bool enabled = obj.activeSelf;
|
||||
int childCount = obj.transform.childCount;
|
||||
Color color;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
if (childCount > 0)
|
||||
{
|
||||
color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = UIStyles.LightGreen;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.red;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("<i><color=red>null</color></i>", null);
|
||||
return;
|
||||
}
|
||||
|
||||
// ------ toggle active button ------
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.button.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUI.color = activeColor;
|
||||
|
||||
enabled = GUILayout.Toggle(enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
||||
if (obj.activeSelf != enabled)
|
||||
{
|
||||
obj.SetActive(enabled);
|
||||
}
|
||||
|
||||
// ------- actual button ---------
|
||||
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Height(22), GUILayout.Width(width) }))
|
||||
{
|
||||
if (specialInspectMethod != null)
|
||||
{
|
||||
specialInspectMethod(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
}
|
||||
|
||||
// ------ small "Inspect" button on the right ------
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
GUI.color = Color.white;
|
||||
|
||||
if (showSmallInspectBtn)
|
||||
{
|
||||
if (GUILayout.Button("Inspect", null))
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
100
src/Helpers/UIStyles.cs
Normal file
100
src/Helpers/UIStyles.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class UIStyles
|
||||
{
|
||||
public static Color LightGreen = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f);
|
||||
|
||||
public static GUISkin WindowSkin
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_customSkin == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_customSkin = CreateWindowSkin();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_customSkin = GUI.skin;
|
||||
}
|
||||
}
|
||||
|
||||
return _customSkin;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HorizontalLine(Color color)
|
||||
{
|
||||
var c = GUI.color;
|
||||
GUI.color = color;
|
||||
GUILayout.Box(GUIContent.none, HorizontalBar, null);
|
||||
GUI.color = c;
|
||||
}
|
||||
|
||||
private static GUISkin _customSkin;
|
||||
|
||||
public static Texture2D m_nofocusTex;
|
||||
public static Texture2D m_focusTex;
|
||||
|
||||
private static GUIStyle _horizBarStyle;
|
||||
|
||||
private static GUIStyle HorizontalBar
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_horizBarStyle == null)
|
||||
{
|
||||
_horizBarStyle = new GUIStyle();
|
||||
_horizBarStyle.normal.background = Texture2D.whiteTexture;
|
||||
_horizBarStyle.margin = new RectOffset(0, 0, 4, 4);
|
||||
_horizBarStyle.fixedHeight = 2;
|
||||
}
|
||||
|
||||
return _horizBarStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private static GUISkin CreateWindowSkin()
|
||||
{
|
||||
var newSkin = Object.Instantiate(GUI.skin);
|
||||
Object.DontDestroyOnLoad(newSkin);
|
||||
|
||||
m_nofocusTex = MakeTex(550, 700, new Color(0.1f, 0.1f, 0.1f, 0.7f));
|
||||
m_focusTex = MakeTex(550, 700, new Color(0.3f, 0.3f, 0.3f, 1f));
|
||||
|
||||
newSkin.window.normal.background = m_nofocusTex;
|
||||
newSkin.window.onNormal.background = m_focusTex;
|
||||
|
||||
newSkin.box.normal.textColor = Color.white;
|
||||
newSkin.window.normal.textColor = Color.white;
|
||||
newSkin.button.normal.textColor = Color.white;
|
||||
newSkin.textField.normal.textColor = Color.white;
|
||||
newSkin.label.normal.textColor = Color.white;
|
||||
|
||||
return newSkin;
|
||||
}
|
||||
|
||||
public static Texture2D MakeTex(int width, int height, Color col)
|
||||
{
|
||||
Color[] pix = new Color[width * height];
|
||||
for (int i = 0; i < pix.Length; ++i)
|
||||
{
|
||||
pix[i] = col;
|
||||
}
|
||||
Texture2D result = new Texture2D(width, height);
|
||||
result.SetPixels(pix);
|
||||
result.Apply();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
34
src/Helpers/UnityHelpers.cs
Normal file
34
src/Helpers/UnityHelpers.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class UnityHelpers
|
||||
{
|
||||
private static Camera m_mainCamera;
|
||||
|
||||
public static Camera MainCamera
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_mainCamera == null)
|
||||
{
|
||||
m_mainCamera = Camera.main;
|
||||
}
|
||||
return m_mainCamera;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ActiveSceneName
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class FieldInfoHolder : MemberInfoHolder
|
||||
{
|
||||
public FieldInfo fieldInfo;
|
||||
public object m_value;
|
||||
|
||||
public FieldInfoHolder(Type _type, FieldInfo _fieldInfo)
|
||||
{
|
||||
classType = _type;
|
||||
fieldInfo = _fieldInfo;
|
||||
}
|
||||
|
||||
public override void UpdateValue(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var declaringType = this.fieldInfo.DeclaringType;
|
||||
|
||||
var cast = CppExplorer.Il2CppCast(obj, declaringType);
|
||||
m_value = this.fieldInfo.GetValue(fieldInfo.IsStatic ? null : cast);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_value = this.fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Error updating FieldInfoHolder | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(ReflectionWindow window)
|
||||
{
|
||||
UIStyles.DrawMember(ref m_value, ref this.IsExpanded, ref this.arrayOffset, this.fieldInfo, window.m_rect, window.m_object, SetValue);
|
||||
}
|
||||
|
||||
public override void SetValue(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (fieldInfo.FieldType.IsEnum)
|
||||
{
|
||||
if (Enum.Parse(fieldInfo.FieldType, m_value.ToString()) is object enumValue && enumValue != null)
|
||||
{
|
||||
m_value = enumValue;
|
||||
}
|
||||
}
|
||||
else if (fieldInfo.FieldType.IsPrimitive)
|
||||
{
|
||||
if (fieldInfo.FieldType == typeof(float))
|
||||
{
|
||||
if (float.TryParse(m_value.ToString(), out float f))
|
||||
{
|
||||
m_value = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!");
|
||||
}
|
||||
}
|
||||
else if (fieldInfo.FieldType == typeof(double))
|
||||
{
|
||||
if (double.TryParse(m_value.ToString(), out double d))
|
||||
{
|
||||
m_value = d;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!");
|
||||
}
|
||||
}
|
||||
else if (fieldInfo.FieldType != typeof(bool))
|
||||
{
|
||||
if (int.TryParse(m_value.ToString(), out int i))
|
||||
{
|
||||
m_value = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + fieldInfo.FieldType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log("Unsupported primitive field type: " + fieldInfo.FieldType.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var declaringType = this.fieldInfo.DeclaringType;
|
||||
|
||||
var cast = CppExplorer.Il2CppCast(obj, declaringType);
|
||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : cast, m_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : obj, m_value);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Error setting FieldInfoHolder | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public abstract class MemberInfoHolder
|
||||
{
|
||||
public Type classType;
|
||||
public bool IsExpanded = false;
|
||||
public int arrayOffset = 0;
|
||||
|
||||
public abstract void Draw(ReflectionWindow window);
|
||||
public abstract void UpdateValue(object obj);
|
||||
public abstract void SetValue(object obj);
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class PropertyInfoHolder : MemberInfoHolder
|
||||
{
|
||||
public PropertyInfo propInfo;
|
||||
public object m_value;
|
||||
|
||||
public PropertyInfoHolder(Type _type, PropertyInfo _propInfo)
|
||||
{
|
||||
classType = _type;
|
||||
propInfo = _propInfo;
|
||||
}
|
||||
|
||||
public override void Draw(ReflectionWindow window)
|
||||
{
|
||||
UIStyles.DrawMember(ref m_value, ref this.IsExpanded, ref this.arrayOffset, this.propInfo, window.m_rect, window.m_object, SetValue);
|
||||
}
|
||||
|
||||
public override void UpdateValue(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var declaringType = this.propInfo.DeclaringType;
|
||||
|
||||
if (declaringType == typeof(Il2CppObjectBase))
|
||||
{
|
||||
m_value = ilObject.Pointer;
|
||||
}
|
||||
else
|
||||
{
|
||||
var cast = CppExplorer.Il2CppCast(obj, declaringType);
|
||||
m_value = this.propInfo.GetValue(this.propInfo.GetAccessors()[0].IsStatic ? null : cast, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_value = this.propInfo.GetValue(obj, null);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception on PropertyInfoHolder.UpdateValue, Name: " + this.propInfo.Name);
|
||||
MelonLogger.Log(e.GetType() + ", " + e.Message);
|
||||
|
||||
var inner = e.InnerException;
|
||||
while (inner != null)
|
||||
{
|
||||
MelonLogger.Log("inner: " + inner.GetType() + ", " + inner.Message);
|
||||
inner = inner.InnerException;
|
||||
}
|
||||
|
||||
m_value = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetValue(object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (propInfo.PropertyType.IsEnum)
|
||||
{
|
||||
if (Enum.Parse(propInfo.PropertyType, m_value.ToString()) is object enumValue && enumValue != null)
|
||||
{
|
||||
m_value = enumValue;
|
||||
}
|
||||
}
|
||||
else if (propInfo.PropertyType.IsPrimitive)
|
||||
{
|
||||
if (propInfo.PropertyType == typeof(float))
|
||||
{
|
||||
if (float.TryParse(m_value.ToString(), out float f))
|
||||
{
|
||||
m_value = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a float!");
|
||||
}
|
||||
}
|
||||
else if (propInfo.PropertyType == typeof(double))
|
||||
{
|
||||
if (double.TryParse(m_value.ToString(), out double d))
|
||||
{
|
||||
m_value = d;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to a double!");
|
||||
}
|
||||
}
|
||||
else if (propInfo.PropertyType != typeof(bool))
|
||||
{
|
||||
if (int.TryParse(m_value.ToString(), out int i))
|
||||
{
|
||||
m_value = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning("Cannot parse " + m_value.ToString() + " to an integer! type: " + propInfo.PropertyType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var declaring = propInfo.DeclaringType;
|
||||
var cast = CppExplorer.Il2CppCast(obj, declaring);
|
||||
|
||||
propInfo.SetValue(propInfo.GetAccessors()[0].IsStatic ? null : cast, m_value, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//MelonLogger.Log("Exception trying to set property " + this.propInfo.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
using Mono.CSharp;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ReflectionWindow : UIWindow
|
||||
{
|
||||
public override string Name { get => "Object Reflection"; set => Name = value; }
|
||||
|
||||
public Type m_objectType;
|
||||
public object m_object;
|
||||
|
||||
private List<FieldInfoHolder> m_FieldInfos;
|
||||
private List<PropertyInfoHolder> m_PropertyInfos;
|
||||
|
||||
private bool m_autoUpdate = false;
|
||||
private string m_search = "";
|
||||
public MemberFilter m_filter = MemberFilter.Property;
|
||||
|
||||
public enum MemberFilter
|
||||
{
|
||||
Both,
|
||||
Property,
|
||||
Field
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
m_object = Target;
|
||||
|
||||
m_FieldInfos = new List<FieldInfoHolder>();
|
||||
m_PropertyInfos = new List<PropertyInfoHolder>();
|
||||
|
||||
var type = GetActualType(m_object);
|
||||
if (type == null)
|
||||
{
|
||||
MelonLogger.Log("could not get underlying type for object. ToString(): " + m_object.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
m_objectType = type;
|
||||
GetFields(m_object);
|
||||
GetProperties(m_object);
|
||||
}
|
||||
catch { }
|
||||
|
||||
UpdateValues(true);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_autoUpdate)
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues(bool forceAll = false)
|
||||
{
|
||||
if (forceAll || m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
||||
{
|
||||
foreach (var holder in this.m_FieldInfos)
|
||||
{
|
||||
if (forceAll || m_search == "" || holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower()))
|
||||
{
|
||||
holder.UpdateValue(m_object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forceAll || m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
||||
{
|
||||
foreach (var holder in this.m_PropertyInfos)
|
||||
{
|
||||
if (forceAll || m_search == "" || holder.propInfo.Name.ToLower().Contains(m_search.ToLower()))
|
||||
{
|
||||
holder.UpdateValue(m_object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
try
|
||||
{
|
||||
Header();
|
||||
|
||||
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>" + m_objectType.Name + "</color>", null);
|
||||
|
||||
bool unityObj = m_object is UnityEngine.Object;
|
||||
|
||||
if (unityObj)
|
||||
{
|
||||
GUILayout.Label("Name: " + (m_object as UnityEngine.Object).name, null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (unityObj)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
|
||||
UIStyles.InstantiateButton((UnityEngine.Object)m_object);
|
||||
|
||||
if (m_object is Component comp && comp.gameObject is GameObject obj)
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("GameObject:", null);
|
||||
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 350) }))
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<b>Search:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
m_search = GUILayout.TextField(m_search, null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
FilterToggle(MemberFilter.Both, "Both");
|
||||
FilterToggle(MemberFilter.Property, "Properties");
|
||||
FilterToggle(MemberFilter.Field, "Fields");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<b>Values:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
GUI.color = m_autoUpdate ? Color.green : Color.red;
|
||||
m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
GUI.color = Color.white;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
||||
{
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
GUILayout.Label("<size=18><b><color=gold>Fields</color></b></size>", null);
|
||||
|
||||
foreach (var holder in this.m_FieldInfos)
|
||||
{
|
||||
if (m_search != "" && !holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
||||
holder.Draw(this);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
||||
{
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
GUILayout.Label("<size=18><b><color=gold>Properties</color></b></size>", null);
|
||||
|
||||
foreach (var holder in this.m_PropertyInfos)
|
||||
{
|
||||
if (m_search != "" && !holder.propInfo.Name.ToLower().Contains(m_search.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
||||
holder.Draw(this);
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterToggle(MemberFilter mode, string label)
|
||||
{
|
||||
if (m_filter == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
m_filter = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
// ============ HELPERS ===============
|
||||
|
||||
public Type GetActualType(object m_object)
|
||||
{
|
||||
if (m_object is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var iltype = ilObject.GetIl2CppType();
|
||||
return Type.GetType(iltype.AssemblyQualifiedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_object.GetType();
|
||||
}
|
||||
}
|
||||
|
||||
public Type[] GetAllBaseTypes(object m_object)
|
||||
{
|
||||
var list = new List<Type>();
|
||||
|
||||
if (m_object is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var ilType = ilObject.GetIl2CppType();
|
||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilTypeToManaged)
|
||||
{
|
||||
list.Add(ilTypeToManaged);
|
||||
|
||||
while (ilType.BaseType != null)
|
||||
{
|
||||
ilType = ilType.BaseType;
|
||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilBaseTypeToManaged)
|
||||
{
|
||||
list.Add(ilBaseTypeToManaged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = m_object.GetType();
|
||||
list.Add(type);
|
||||
while (type.BaseType != null)
|
||||
{
|
||||
type = type.BaseType;
|
||||
list.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public static bool IsList(Type t)
|
||||
{
|
||||
return t.IsGenericType
|
||||
&& t.GetGenericTypeDefinition() is Type typeDef
|
||||
&& (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>)));
|
||||
}
|
||||
|
||||
private void GetProperties(object m_object, List<string> names = null)
|
||||
{
|
||||
if (names == null)
|
||||
{
|
||||
names = new List<string>();
|
||||
}
|
||||
|
||||
var types = GetAllBaseTypes(m_object);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
PropertyInfo[] propInfos = new PropertyInfo[0];
|
||||
|
||||
try
|
||||
{
|
||||
propInfos = type.GetProperties(At.flags);
|
||||
}
|
||||
catch (TypeLoadException)
|
||||
{
|
||||
MelonLogger.Log($"Couldn't get Properties for Type '{type.Name}', it may not support Il2Cpp Reflection at the moment.");
|
||||
}
|
||||
|
||||
foreach (var pi in propInfos)
|
||||
{
|
||||
// this member causes a crash when inspected, so just skipping it for now.
|
||||
if (pi.Name == "Il2CppType")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (names.Contains(pi.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
names.Add(pi.Name);
|
||||
|
||||
var piHolder = new PropertyInfoHolder(type, pi);
|
||||
m_PropertyInfos.Add(piHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GetFields(object m_object, List<string> names = null)
|
||||
{
|
||||
if (names == null)
|
||||
{
|
||||
names = new List<string>();
|
||||
}
|
||||
|
||||
var types = GetAllBaseTypes(m_object);
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
foreach (var fi in type.GetFields(At.flags))
|
||||
{
|
||||
if (names.Contains(fi.Name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
names.Add(fi.Name);
|
||||
|
||||
var fiHolder = new FieldInfoHolder(type, fi);
|
||||
m_FieldInfos.Add(fiHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/MainMenu/InspectUnderMouse.cs
Normal file
87
src/MainMenu/InspectUnderMouse.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class InspectUnderMouse
|
||||
{
|
||||
public static bool EnableInspect { get; set; } = false;
|
||||
|
||||
private static string m_objUnderMouseName = "";
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (CppExplorer.ShowMenu)
|
||||
{
|
||||
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
|
||||
{
|
||||
EnableInspect = !EnableInspect;
|
||||
}
|
||||
|
||||
if (EnableInspect)
|
||||
{
|
||||
InspectRaycast();
|
||||
}
|
||||
}
|
||||
else if (EnableInspect)
|
||||
{
|
||||
EnableInspect = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InspectRaycast()
|
||||
{
|
||||
Ray ray = UnityHelpers.MainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||
{
|
||||
var obj = hit.transform.gameObject;
|
||||
|
||||
m_objUnderMouseName = obj.transform.GetGameObjectPath();
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
EnableInspect = false;
|
||||
m_objUnderMouseName = "";
|
||||
|
||||
WindowManager.InspectObject(obj, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_objUnderMouseName = "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnGUI()
|
||||
{
|
||||
if (EnableInspect)
|
||||
{
|
||||
if (m_objUnderMouseName != "")
|
||||
{
|
||||
var pos = Input.mousePosition;
|
||||
var rect = new Rect(
|
||||
pos.x - (Screen.width / 2), // x
|
||||
Screen.height - pos.y - 50, // y
|
||||
Screen.width, // w
|
||||
50 // h
|
||||
);
|
||||
|
||||
var origAlign = GUI.skin.label.alignment;
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
//shadow text
|
||||
GUI.Label(rect, $"<color=black>{m_objUnderMouseName}</color>");
|
||||
//white text
|
||||
GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName);
|
||||
|
||||
GUI.skin.label.alignment = origAlign;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -98,7 +98,7 @@ namespace Explorer
|
||||
{
|
||||
CppExplorer.ArrayLimit = _lim;
|
||||
}
|
||||
CppExplorer.Instance.MouseInspect = GUILayout.Toggle(CppExplorer.Instance.MouseInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
||||
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
@ -36,7 +36,7 @@ namespace Explorer
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_currentScene = CppExplorer.ActiveSceneName;
|
||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||
|
||||
m_currentTransform = null;
|
||||
CancelSearch();
|
||||
@ -155,7 +155,7 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(CppExplorer.GetGameObjectPath(m_currentTransform), null);
|
||||
GUILayout.Label(m_currentTransform.GetGameObjectPath(), null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
@ -169,7 +169,7 @@ namespace Explorer
|
||||
foreach (var obj in m_objectList)
|
||||
{
|
||||
//UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
UIStyles.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -187,7 +187,7 @@ namespace Explorer
|
||||
foreach (var obj in m_searchResults)
|
||||
{
|
||||
//UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
UIStyles.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -22,9 +23,11 @@ namespace Explorer
|
||||
private string m_typeInput = "";
|
||||
private int m_limit = 20;
|
||||
private int m_pageOffset = 0;
|
||||
private List<object> m_searchResults = new List<object>();
|
||||
//private List<object> m_searchResults = new List<object>();
|
||||
private Vector2 resultsScroll = Vector2.zero;
|
||||
|
||||
private List<CacheObject> m_searchResults = new List<CacheObject>();
|
||||
|
||||
public SceneFilter SceneMode = SceneFilter.Any;
|
||||
public TypeFilter TypeMode = TypeFilter.Object;
|
||||
|
||||
@ -59,6 +62,24 @@ namespace Explorer
|
||||
{
|
||||
}
|
||||
|
||||
private void CacheResults(IEnumerable results)
|
||||
{
|
||||
m_searchResults = new List<CacheObject>();
|
||||
|
||||
foreach (var obj in results)
|
||||
{
|
||||
var toCache = obj;
|
||||
|
||||
if (toCache is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
|
||||
}
|
||||
|
||||
var cache = CacheObject.GetCacheObject(toCache);
|
||||
m_searchResults.Add(cache);
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
@ -68,7 +89,8 @@ namespace Explorer
|
||||
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
||||
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
|
||||
{
|
||||
m_searchResults = GetInstanceClassScanner().ToList();
|
||||
//m_searchResults = GetInstanceClassScanner().ToList();
|
||||
CacheResults(GetInstanceClassScanner());
|
||||
m_pageOffset = 0;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
@ -89,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--;
|
||||
@ -111,28 +133,12 @@ namespace Explorer
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = m_pageOffset * this.m_limit;
|
||||
int preiterated = 0;
|
||||
|
||||
if (offset >= count) m_pageOffset = 0;
|
||||
|
||||
for (int i = 0; i < m_searchResults.Count; i++)
|
||||
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
|
||||
{
|
||||
if (offset > 0 && preiterated < offset)
|
||||
{
|
||||
preiterated++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i - offset > this.m_limit - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var obj = m_searchResults[i];
|
||||
|
||||
bool _ = false;
|
||||
int __ = 0;
|
||||
UIStyles.DrawValue(ref obj, ref _, ref __, _temprect);
|
||||
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||
//m_searchResults[i].DrawValue(MainMenu.MainRect);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -141,7 +147,6 @@ namespace Explorer
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch
|
||||
@ -252,7 +257,7 @@ namespace Explorer
|
||||
private void Search()
|
||||
{
|
||||
m_pageOffset = 0;
|
||||
m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput);
|
||||
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||
@ -263,8 +268,9 @@ namespace Explorer
|
||||
{
|
||||
try
|
||||
{
|
||||
var findType = CppExplorer.GetType(_type);
|
||||
var findType = ReflectionHelpers.GetTypeByName(_type);
|
||||
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -273,18 +279,18 @@ namespace Explorer
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
{
|
||||
searchType = CppExplorer.ObjectType;
|
||||
searchType = ReflectionHelpers.ObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.GameObject)
|
||||
{
|
||||
searchType = CppExplorer.GameObjectType;
|
||||
searchType = ReflectionHelpers.GameObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Component)
|
||||
{
|
||||
searchType = CppExplorer.ComponentType;
|
||||
searchType = ReflectionHelpers.ComponentType;
|
||||
}
|
||||
|
||||
if (!CppExplorer.ObjectType.IsAssignableFrom(searchType))
|
||||
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
|
||||
{
|
||||
MelonLogger.LogError("Your Custom Class Type must inherit from UnityEngine.Object!");
|
||||
return new List<object>();
|
||||
@ -294,14 +300,19 @@ 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;
|
||||
}
|
||||
|
||||
if (searchType == CppExplorer.ComponentType && CppExplorer.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
if (searchType == ReflectionHelpers.ComponentType && ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
{
|
||||
// Transforms shouldn't really be counted as Components, skip them.
|
||||
// They're more akin to GameObjects.
|
||||
@ -317,6 +328,8 @@ namespace Explorer
|
||||
{
|
||||
matches.Add(obj);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return matches;
|
||||
@ -346,7 +359,7 @@ namespace Explorer
|
||||
}
|
||||
else if (filter == SceneFilter.This)
|
||||
{
|
||||
return go.scene.name == CppExplorer.ActiveSceneName;
|
||||
return go.scene.name == UnityHelpers.ActiveSceneName;
|
||||
}
|
||||
else if (filter == SceneFilter.DontDestroy)
|
||||
{
|
||||
|
505
src/UIStyles.cs
505
src/UIStyles.cs
@ -1,505 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Il2CppSystem.Collections;
|
||||
//using Il2CppSystem.Reflection;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class UIStyles
|
||||
{
|
||||
public static Color LightGreen = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f);
|
||||
|
||||
public static GUISkin WindowSkin
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_customSkin == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_customSkin = CreateWindowSkin();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_customSkin = GUI.skin;
|
||||
}
|
||||
}
|
||||
|
||||
return _customSkin;
|
||||
}
|
||||
}
|
||||
|
||||
public static void HorizontalLine(Color color)
|
||||
{
|
||||
var c = GUI.color;
|
||||
GUI.color = color;
|
||||
GUILayout.Box(GUIContent.none, HorizontalBar, null);
|
||||
GUI.color = c;
|
||||
}
|
||||
|
||||
private static GUISkin _customSkin;
|
||||
|
||||
public static Texture2D m_nofocusTex;
|
||||
public static Texture2D m_focusTex;
|
||||
|
||||
private static GUIStyle _horizBarStyle;
|
||||
|
||||
private static GUIStyle HorizontalBar
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_horizBarStyle == null)
|
||||
{
|
||||
_horizBarStyle = new GUIStyle();
|
||||
_horizBarStyle.normal.background = Texture2D.whiteTexture;
|
||||
_horizBarStyle.margin = new RectOffset(0, 0, 4, 4);
|
||||
_horizBarStyle.fixedHeight = 2;
|
||||
}
|
||||
|
||||
return _horizBarStyle;
|
||||
}
|
||||
}
|
||||
|
||||
private static GUISkin CreateWindowSkin()
|
||||
{
|
||||
var newSkin = Object.Instantiate(GUI.skin);
|
||||
Object.DontDestroyOnLoad(newSkin);
|
||||
|
||||
m_nofocusTex = MakeTex(550, 700, new Color(0.1f, 0.1f, 0.1f, 0.7f));
|
||||
m_focusTex = MakeTex(550, 700, new Color(0.3f, 0.3f, 0.3f, 1f));
|
||||
|
||||
newSkin.window.normal.background = m_nofocusTex;
|
||||
newSkin.window.onNormal.background = m_focusTex;
|
||||
|
||||
newSkin.box.normal.textColor = Color.white;
|
||||
newSkin.window.normal.textColor = Color.white;
|
||||
newSkin.button.normal.textColor = Color.white;
|
||||
newSkin.textField.normal.textColor = Color.white;
|
||||
newSkin.label.normal.textColor = Color.white;
|
||||
|
||||
return newSkin;
|
||||
}
|
||||
|
||||
public static Texture2D MakeTex(int width, int height, Color col)
|
||||
{
|
||||
Color[] pix = new Color[width * height];
|
||||
for (int i = 0; i < pix.Length; ++i)
|
||||
{
|
||||
pix[i] = col;
|
||||
}
|
||||
Texture2D result = new Texture2D(width, height);
|
||||
result.SetPixels(pix);
|
||||
result.Apply();
|
||||
return result;
|
||||
}
|
||||
|
||||
// *********************************** METHODS FOR DRAWING VALUES IN GUI ************************************
|
||||
|
||||
// helper for "Instantiate" button on UnityEngine.Objects
|
||||
public static void InstantiateButton(Object obj, float width = 100)
|
||||
{
|
||||
if (GUILayout.Button("Instantiate", new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
{
|
||||
var newobj = Object.Instantiate(obj);
|
||||
|
||||
WindowManager.InspectObject(newobj, out _);
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
bool children = obj.transform.childCount > 0;
|
||||
|
||||
string label = children ? "[" + obj.transform.childCount + " children] " : "";
|
||||
label += obj.name;
|
||||
|
||||
bool enabled = obj.activeSelf;
|
||||
int childCount = obj.transform.childCount;
|
||||
Color color;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
if (childCount > 0)
|
||||
{
|
||||
color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = LightGreen;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.red;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("<i><color=red>null</color></i>", null);
|
||||
return;
|
||||
}
|
||||
|
||||
// ------ toggle active button ------
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.button.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUI.color = activeColor;
|
||||
|
||||
enabled = GUILayout.Toggle(enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
||||
if (obj.activeSelf != enabled)
|
||||
{
|
||||
obj.SetActive(enabled);
|
||||
}
|
||||
|
||||
// ------- actual button ---------
|
||||
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Height(22), GUILayout.Width(width) }))
|
||||
{
|
||||
if (specialInspectMethod != null)
|
||||
{
|
||||
specialInspectMethod(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
}
|
||||
|
||||
// ------ small "Inspect" button on the right ------
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
GUI.color = Color.white;
|
||||
|
||||
if (showSmallInspectBtn)
|
||||
{
|
||||
if (GUILayout.Button("Inspect", null))
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public static void DrawMember(ref object value, ref bool isExpanded, ref int arrayOffset, MemberInfo memberInfo, Rect rect, object setTarget = null, Action<object> setAction = null, float labelWidth = 180, bool autoSet = false)
|
||||
{
|
||||
GUILayout.Label("<color=cyan>" + memberInfo.Name + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||
|
||||
string valueType = "";
|
||||
bool canWrite = true;
|
||||
if (memberInfo is FieldInfo fi)
|
||||
{
|
||||
valueType = fi.FieldType.Name;
|
||||
canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
|
||||
}
|
||||
else if (memberInfo is PropertyInfo pi)
|
||||
{
|
||||
valueType = pi.PropertyType.Name;
|
||||
canWrite = pi.CanWrite;
|
||||
}
|
||||
|
||||
DrawValue(ref value, ref isExpanded, ref arrayOffset, rect, valueType, (canWrite ? setTarget : null), (canWrite ? setAction : null), autoSet);
|
||||
}
|
||||
|
||||
public static void DrawValue(ref object value, ref bool isExpanded, ref int arrayOffset, Rect rect, string nullValueType = null, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
GUILayout.Label("<i>null (" + nullValueType + ")</i>", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var valueType = value.GetType();
|
||||
|
||||
Il2CppSystem.Type ilType = null;
|
||||
if (value is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
ilType = ilObject.GetIl2CppType();
|
||||
}
|
||||
|
||||
if (valueType.IsPrimitive || value.GetType() == typeof(string))
|
||||
{
|
||||
DrawPrimitive(ref value, rect, setTarget, setAction);
|
||||
}
|
||||
else if (ilType != null && ilType == CppExplorer.GameObjectType || CppExplorer.TransformType.IsAssignableFrom(ilType))
|
||||
{
|
||||
GameObject go;
|
||||
var ilObj = value as Il2CppSystem.Object;
|
||||
if (ilType == CppExplorer.GameObjectType)
|
||||
{
|
||||
go = ilObj.TryCast<GameObject>();
|
||||
}
|
||||
else
|
||||
{
|
||||
go = ilObj.TryCast<Transform>().gameObject;
|
||||
}
|
||||
|
||||
GameobjButton(go, null, false, rect.width - 250);
|
||||
}
|
||||
else if (valueType.IsEnum)
|
||||
{
|
||||
if (setAction != null)
|
||||
{
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
SetEnum(ref value, -1);
|
||||
setAction.Invoke(setTarget);
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
SetEnum(ref value, 1);
|
||||
setAction.Invoke(setTarget);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label(value.ToString(), null);
|
||||
}
|
||||
else if (value is System.Collections.IEnumerable || ReflectionWindow.IsList(valueType))
|
||||
{
|
||||
System.Collections.IEnumerable enumerable;
|
||||
|
||||
if (value is System.Collections.IEnumerable isEnumerable)
|
||||
{
|
||||
enumerable = isEnumerable;
|
||||
}
|
||||
else
|
||||
{
|
||||
var listValueType = value.GetType().GetGenericArguments()[0];
|
||||
var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType });
|
||||
var method = listType.GetMethod("ToArray");
|
||||
enumerable = (System.Collections.IEnumerable)method.Invoke(value, new object[0]);
|
||||
}
|
||||
|
||||
int count = enumerable.Cast<object>().Count();
|
||||
|
||||
if (!isExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
isExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
isExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = "<color=yellow>[" + count + "] " + valueType + "</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 260) }))
|
||||
{
|
||||
WindowManager.InspectObject(value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
if (isExpanded)
|
||||
{
|
||||
if (count > CppExplorer.ArrayLimit)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(190);
|
||||
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
|
||||
GUILayout.Label($"Page {arrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
// prev/next page buttons
|
||||
if (GUILayout.Button("< Prev", null))
|
||||
{
|
||||
if (arrayOffset > 0) arrayOffset--;
|
||||
}
|
||||
if (GUILayout.Button("Next >", null))
|
||||
{
|
||||
if (arrayOffset < maxOffset) arrayOffset++;
|
||||
}
|
||||
}
|
||||
|
||||
int offset = arrayOffset * CppExplorer.ArrayLimit;
|
||||
|
||||
if (offset >= count) offset = 0;
|
||||
|
||||
var enumerator = enumerable.GetEnumerator();
|
||||
if (enumerator != null)
|
||||
{
|
||||
int i = 0;
|
||||
int preiterated = 0;
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (offset > 0 && preiterated < offset)
|
||||
{
|
||||
preiterated++;
|
||||
continue;
|
||||
}
|
||||
|
||||
var obj = enumerator.Current;
|
||||
|
||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(190);
|
||||
|
||||
if (i > CppExplorer.ArrayLimit - 1)
|
||||
{
|
||||
//GUILayout.Label($"<i><color=red>{count - CppExplorer.ArrayLimit} results omitted, array is too long!</color></i>", null);
|
||||
break;
|
||||
}
|
||||
|
||||
if (obj == null)
|
||||
{
|
||||
GUILayout.Label("<i><color=grey>null</color></i>", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = obj.GetType();
|
||||
|
||||
var lbl = (i + offset) + ": <color=cyan>" + obj.ToString() + "</color>";
|
||||
|
||||
if (type.IsPrimitive || typeof(string).IsAssignableFrom(type))
|
||||
{
|
||||
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;
|
||||
}
|
||||
//var type = obj.GetType();
|
||||
//DrawMember(ref obj, type.ToString(), i.ToString(), rect, setTarget, setAction, 25, true);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var label = value.ToString();
|
||||
|
||||
if (valueType == typeof(Object))
|
||||
{
|
||||
label = (value as Object).name;
|
||||
}
|
||||
else if (value is Vector4 vec4)
|
||||
{
|
||||
label = vec4.ToString();
|
||||
}
|
||||
else if (value is Vector3 vec3)
|
||||
{
|
||||
label = vec3.ToString();
|
||||
}
|
||||
else if (value is Vector2 vec2)
|
||||
{
|
||||
label = vec2.ToString();
|
||||
}
|
||||
else if (value is Rect rec)
|
||||
{
|
||||
label = rec.ToString();
|
||||
}
|
||||
else if (value is Matrix4x4 matrix)
|
||||
{
|
||||
label = matrix.ToString();
|
||||
}
|
||||
else if (value is Color col)
|
||||
{
|
||||
label = col.ToString();
|
||||
}
|
||||
|
||||
string typeLabel;
|
||||
if (ilType != null)
|
||||
{
|
||||
typeLabel = ilType.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
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(rect.width - 230) }))
|
||||
{
|
||||
WindowManager.InspectObject(value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for drawing primitive values (with Apply button)
|
||||
|
||||
public static void DrawPrimitive(ref object value, Rect m_rect, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
||||
{
|
||||
bool allowSet = setAction != null;
|
||||
|
||||
if (value.GetType() == typeof(bool))
|
||||
{
|
||||
bool b = (bool)value;
|
||||
var color = "<color=" + (b ? "lime>" : "red>");
|
||||
|
||||
if (allowSet)
|
||||
{
|
||||
value = GUILayout.Toggle((bool)value, color + value.ToString() + "</color>", null);
|
||||
|
||||
if (b != (bool)value)
|
||||
{
|
||||
setAction.Invoke(setTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value.ToString().Length > 37)
|
||||
{
|
||||
value = GUILayout.TextArea(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) });
|
||||
}
|
||||
else
|
||||
{
|
||||
value = GUILayout.TextField(value.ToString(), new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 260) });
|
||||
}
|
||||
|
||||
if (autoSet || (allowSet && GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) })))
|
||||
{
|
||||
setAction.Invoke(setTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper for setting an enum
|
||||
|
||||
public static void SetEnum(ref object value, int change)
|
||||
{
|
||||
var type = value.GetType();
|
||||
var names = Enum.GetNames(type).ToList();
|
||||
|
||||
int newindex = names.IndexOf(value.ToString()) + change;
|
||||
|
||||
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
|
||||
{
|
||||
value = Enum.Parse(type, names[newindex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -133,7 +133,7 @@ namespace Explorer
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
|
||||
if (m_scene == CppExplorer.ActiveSceneName)
|
||||
if (m_scene == UnityHelpers.ActiveSceneName)
|
||||
{
|
||||
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
|
||||
{
|
||||
@ -145,7 +145,7 @@ namespace Explorer
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
string pathlabel = CppExplorer.GetGameObjectPath(m_object.transform);
|
||||
string pathlabel = m_object.transform.GetGameObjectPath();
|
||||
if (m_object.transform.parent != null)
|
||||
{
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
@ -198,7 +198,7 @@ namespace Explorer
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
}
|
||||
foreach (var obj in m_children.Where(x => x.childCount == 0))
|
||||
{
|
||||
@ -207,7 +207,7 @@ namespace Explorer
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -329,7 +329,7 @@ namespace Explorer
|
||||
new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); }
|
||||
|
||||
UIStyles.InstantiateButton(m_object, 100);
|
||||
UIHelpers.InstantiateButton(m_object, 100);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
341
src/Windows/ReflectionWindow.cs
Normal file
341
src/Windows/ReflectionWindow.cs
Normal file
@ -0,0 +1,341 @@
|
||||
using System;
|
||||
using System.CodeDom;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
using Mono.CSharp;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ReflectionWindow : UIWindow
|
||||
{
|
||||
public override string Name { get => "Object Reflection"; set => Name = value; }
|
||||
|
||||
public Type ObjectType;
|
||||
|
||||
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 = true;
|
||||
|
||||
public enum MemberInfoType
|
||||
{
|
||||
Field,
|
||||
Property,
|
||||
Method,
|
||||
All
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
var type = ReflectionHelpers.GetActualType(Target);
|
||||
if (type == null)
|
||||
{
|
||||
MelonLogger.Log("Could not get underlying type for object. ToString(): " + Target.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectType = type;
|
||||
|
||||
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
||||
CacheMembers(types);
|
||||
|
||||
m_filter = MemberInfoType.All;
|
||||
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||
UpdateValues();
|
||||
m_filter = MemberInfoType.Property;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||
|
||||
if (m_autoUpdate)
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
UpdateMembers();
|
||||
}
|
||||
|
||||
private void UpdateMembers()
|
||||
{
|
||||
foreach (var member in m_cachedMemberFiltered)
|
||||
{
|
||||
member.UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldProcessMember(CacheObject holder)
|
||||
{
|
||||
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.FullName
|
||||
.ToLower()
|
||||
.Contains(m_search.ToLower());
|
||||
}
|
||||
|
||||
private void CacheMembers(Type[] types)
|
||||
{
|
||||
var list = new List<CacheObject>();
|
||||
|
||||
var names = new List<string>();
|
||||
|
||||
foreach (var declaringType in types)
|
||||
{
|
||||
if (declaringType == typeof(Il2CppObjectBase)) continue;
|
||||
|
||||
MemberInfo[] infos;
|
||||
|
||||
string exception = null;
|
||||
|
||||
try
|
||||
{
|
||||
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
|
||||
}
|
||||
catch
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
|
||||
{
|
||||
if (member.Name == "Il2CppType") continue;
|
||||
|
||||
var name = member.DeclaringType.Name + "." + member.Name;
|
||||
if (names.Contains(name)) continue;
|
||||
names.Add(name);
|
||||
|
||||
var cached = CacheObject.GetCacheObject(null, member, target);
|
||||
list.Add(cached);
|
||||
cached.ReflectionException = exception;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_cachedMembers = list.ToArray();
|
||||
}
|
||||
|
||||
// =========== GUI DRAW =========== //
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
try
|
||||
{
|
||||
Header();
|
||||
|
||||
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);
|
||||
|
||||
bool unityObj = Target is UnityEngine.Object;
|
||||
|
||||
if (unityObj)
|
||||
{
|
||||
GUILayout.Label("Name: " + (Target as UnityEngine.Object).name, null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (unityObj)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
|
||||
UIHelpers.InstantiateButton((UnityEngine.Object)Target);
|
||||
|
||||
if (Target is Component comp && comp.gameObject is GameObject obj)
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("GameObject:", null);
|
||||
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 350) }))
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
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);
|
||||
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
FilterToggle(MemberInfoType.All, "All");
|
||||
FilterToggle(MemberInfoType.Property, "Properties");
|
||||
FilterToggle(MemberInfoType.Field, "Fields");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<b>Values:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||
if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
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();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
int count = m_cachedMemberFiltered.Length;
|
||||
|
||||
if (count > m_limitPerPage)
|
||||
{
|
||||
// prev/next page buttons
|
||||
GUILayout.BeginHorizontal(null);
|
||||
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limitPerPage)) - 1;
|
||||
if (GUILayout.Button("< Prev", null))
|
||||
{
|
||||
if (m_pageOffset > 0) m_pageOffset--;
|
||||
}
|
||||
|
||||
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
|
||||
if (GUILayout.Button("Next >", null))
|
||||
{
|
||||
if (m_pageOffset < maxOffset) m_pageOffset++;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
DrawMembers(this.m_cachedMemberFiltered);
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMembers(CacheObject[] members)
|
||||
{
|
||||
// todo pre-cache list based on current search, otherwise this doesnt work.
|
||||
|
||||
int i = 0;
|
||||
DrawMembersInternal("Properties", MemberInfoType.Property, members, ref i);
|
||||
DrawMembersInternal("Fields", MemberInfoType.Field, members, ref i);
|
||||
}
|
||||
|
||||
private void DrawMembersInternal(string title, MemberInfoType filter, CacheObject[] members, ref int index)
|
||||
{
|
||||
if (m_filter != filter && m_filter != MemberInfoType.All)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
||||
GUILayout.Label($"<size=18><b><color=gold>{title}</color></b></size>", null);
|
||||
|
||||
int offset = (m_pageOffset * m_limitPerPage) + index;
|
||||
|
||||
if (offset >= m_cachedMemberFiltered.Length)
|
||||
{
|
||||
m_pageOffset = 0;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
for (int j = offset; j < offset + m_limitPerPage && j < members.Length; j++)
|
||||
{
|
||||
var holder = members[j];
|
||||
|
||||
if (holder.MemberInfoType != filter || !ShouldProcessMember(holder)) continue;
|
||||
|
||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
||||
try
|
||||
{
|
||||
holder.Draw(this.m_rect, 180f);
|
||||
}
|
||||
catch // (Exception e)
|
||||
{
|
||||
//MelonLogger.Log("Exception drawing member " + holder.MemberInfo.Name);
|
||||
//MelonLogger.Log(e.GetType() + ", " + e.Message);
|
||||
//MelonLogger.Log(e.StackTrace);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
index++;
|
||||
if (index >= m_limitPerPage) break;
|
||||
}
|
||||
}
|
||||
|
||||
private void FilterToggle(MemberInfoType mode, string label)
|
||||
{
|
||||
if (m_filter == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
m_filter = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,9 +23,12 @@ namespace Explorer
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
public abstract void Init();
|
||||
public abstract void WindowFunction(int windowID);
|
||||
public abstract void Update();
|
||||
|
||||
public static UIWindow CreateWindow<T>(object target) where T : UIWindow
|
||||
{
|
||||
//var component = (UIWindow)AddToGameObject<T>(Instance.gameObject);
|
||||
var window = Activator.CreateInstance<T>();
|
||||
|
||||
window.Target = target;
|
||||
@ -50,13 +53,8 @@ namespace Explorer
|
||||
MelonLogger.Log("Exception removing Window from WindowManager.Windows list!");
|
||||
MelonLogger.Log($"{e.GetType()} : {e.Message}\r\n{e.StackTrace}");
|
||||
}
|
||||
//Destroy(this);
|
||||
}
|
||||
|
||||
public abstract void Init();
|
||||
public abstract void WindowFunction(int windowID);
|
||||
public abstract void Update();
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (CppExplorer.ShowMenu)
|
@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
/// <summary>
|
||||
/// AccessTools
|
||||
/// Some helpers for Reflection (GetValue, SetValue, Call, InheritBaseValues)
|
||||
/// </summary>
|
||||
public static class At
|
||||
{
|
||||
public static Il2CppSystem.Reflection.BindingFlags ilFlags = Il2CppSystem.Reflection.BindingFlags.Public
|
||||
| Il2CppSystem.Reflection.BindingFlags.NonPublic
|
||||
| Il2CppSystem.Reflection.BindingFlags.Instance
|
||||
| Il2CppSystem.Reflection.BindingFlags.Static;
|
||||
|
||||
public static BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
//reflection call
|
||||
public static object Call(object obj, string method, params object[] args)
|
||||
{
|
||||
var methodInfo = obj.GetType().GetMethod(method, flags);
|
||||
if (methodInfo != null)
|
||||
{
|
||||
return methodInfo.Invoke(obj, args);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// set value
|
||||
public static void SetValue<T>(T value, Type type, object obj, string field)
|
||||
{
|
||||
FieldInfo fieldInfo = type.GetField(field, flags);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(obj, value);
|
||||
}
|
||||
}
|
||||
|
||||
// get value
|
||||
public static object GetValue(Type type, object obj, string value)
|
||||
{
|
||||
FieldInfo fieldInfo = type.GetField(value, flags);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
return fieldInfo.GetValue(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// inherit base values
|
||||
public static void InheritBaseValues(object _derived, object _base)
|
||||
{
|
||||
foreach (FieldInfo fi in _base.GetType().GetFields(flags))
|
||||
{
|
||||
try { _derived.GetType().GetField(fi.Name).SetValue(_derived, fi.GetValue(_base)); } catch { }
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user