mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-23 08:53:05 +08:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
6bafab785b | |||
62b1688d53 | |||
4d015cbe93 | |||
0da8f4faea | |||
b264151c46 | |||
3d2bc7cd4b | |||
85c26e6af7 | |||
b149efa234 | |||
72c222d59a | |||
153ad2268b | |||
1ba9b2eae1 | |||
be2da96cc0 | |||
a2405d69c5 | |||
b2a90c832f | |||
9a784fd467 | |||
d399b6acd1 | |||
0c3067973e | |||
411593590d |
35
README.md
35
README.md
@ -1,17 +1,28 @@
|
||||
# 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>
|
||||
|
||||
This was designed to be an IL2CPP-compatible equivalent to [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor).
|
||||
<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
|
||||
* Scene hierarchy explorer
|
||||
* Search loaded assets with filters
|
||||
* Traverse and manipulate GameObjects
|
||||
* Generic Reflection inspector
|
||||
* REPL Console
|
||||
* C# REPL Console
|
||||
* Inspect-under-mouse
|
||||
|
||||
## How to install
|
||||
@ -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)
|
||||
@ -38,7 +61,7 @@ Search feature:
|
||||
[](https://i.imgur.com/F9ZfMvz.png)
|
||||
|
||||
|
||||
REPL console:
|
||||
C# REPL console:
|
||||
|
||||
[](https://i.imgur.com/14Dbtf8.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,11 +2,9 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using Harmony;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
@ -15,41 +13,23 @@ namespace Explorer
|
||||
// consts
|
||||
|
||||
public const string ID = "com.sinai.cppexplorer";
|
||||
public const string NAME = "IL2CPP Runtime Explorer";
|
||||
public const string VERSION = "1.3.1";
|
||||
public const string VERSION = "1.4.1";
|
||||
public const string AUTHOR = "Sinai";
|
||||
|
||||
public const string NAME = "CppExplorer"
|
||||
#if Release_Unity2018
|
||||
+ " (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;
|
||||
|
||||
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
|
||||
|
||||
@ -62,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))
|
||||
@ -95,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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,31 +8,33 @@
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Explorer</RootNamespace>
|
||||
<AssemblyName>CppExplorer</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<AssemblyName>CppExplorer</AssemblyName>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\Release\</OutputPath>
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_Unity2018|AnyCPU' ">
|
||||
<AssemblyName>CppExplorer_Unity2018</AssemblyName>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\Release\</OutputPath>
|
||||
<DefineConstants>Release_Unity2018</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@ -66,70 +68,100 @@
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Unity.TextMeshPro">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Unity.TextMeshPro.dll</HintPath>
|
||||
<!-- Unity 2019 build (InputLegacyModule.dll) -->
|
||||
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputLegacyModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ParticleSystemModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.ParticleSystemModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule">
|
||||
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextRenderingModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UIElementsModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
||||
<Reference Include="UnityEngine.UIElementsModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UIModule">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
|
||||
<!-- Unity 2018 build (InputModule.dll) -->
|
||||
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UIElementsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</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>
|
@ -8,13 +8,13 @@ EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release_Unity2018|Any CPU = Release_Unity2018|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.ActiveCfg = Release_Unity2018|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.Build.0 = Release_Unity2018|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
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,82 +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)
|
||||
{
|
||||
m_value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (fieldInfo.FieldType.IsEnum)
|
||||
{
|
||||
if (System.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : obj, m_value);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,126 +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(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 (System.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,355 +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 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 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();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_autoUpdate)
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
||||
{
|
||||
foreach (var holder in this.m_FieldInfos)
|
||||
{
|
||||
if (m_search == "" || holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower()))
|
||||
{
|
||||
holder.UpdateValue(m_object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
||||
{
|
||||
foreach (var holder in this.m_PropertyInfos)
|
||||
{
|
||||
if (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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -56,7 +56,7 @@ namespace Explorer
|
||||
var origSkin = GUI.skin;
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
|
||||
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, "IL2CPP Runtime Explorer");
|
||||
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, CppExplorer.NAME);
|
||||
|
||||
GUI.skin = origSkin;
|
||||
}
|
||||
@ -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);
|
||||
|
@ -146,11 +146,11 @@ MelonLogger.Log(""hello world"");";
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(110) });
|
||||
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
if (GUILayout.Button("Add", new GUILayoutOption[] { GUILayout.Width(50) }))
|
||||
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
AddUsing(UsingInput);
|
||||
}
|
||||
if (GUILayout.Button("<color=red>Reset</color>", null))
|
||||
if (GUILayout.Button("<b><color=red>Clear All</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
ResetConsole();
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ namespace Explorer
|
||||
// gameobject list
|
||||
private Transform m_currentTransform;
|
||||
private List<GameObjectCache> m_objectList = new List<GameObjectCache>();
|
||||
private float m_timeOfLastUpdate = -1f;
|
||||
|
||||
// search bar
|
||||
private bool m_searching = false;
|
||||
@ -37,7 +36,7 @@ namespace Explorer
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_currentScene = CppExplorer.ActiveSceneName;
|
||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||
|
||||
m_currentTransform = null;
|
||||
CancelSearch();
|
||||
@ -45,14 +44,6 @@ namespace Explorer
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (Time.time - m_timeOfLastUpdate < 0.2f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
var start = Time.realtimeSinceStartup;
|
||||
|
||||
if (!m_searching)
|
||||
{
|
||||
m_objectList = new List<GameObjectCache>();
|
||||
@ -101,33 +92,39 @@ namespace Explorer
|
||||
GUILayout.BeginHorizontal(null);
|
||||
// Current Scene label
|
||||
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
if (SceneManager.sceneCount > 1)
|
||||
try
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
|
||||
var scenes = SceneManager.GetAllScenes().ToList();
|
||||
|
||||
if (scenes.Count > 1)
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
var scenes = SceneManager.GetAllScenes();
|
||||
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||
index += changeWanted;
|
||||
if (index >= scenes.Count - 1)
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
index = 0;
|
||||
changeWanted = -1;
|
||||
}
|
||||
else if (index > 0)
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||
index += changeWanted;
|
||||
if (index > scenes.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
@ -158,7 +155,7 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(CppExplorer.GetGameObjectPath(m_currentTransform), null);
|
||||
GUILayout.Label(m_currentTransform.GetGameObjectPath(), null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
@ -169,17 +166,11 @@ namespace Explorer
|
||||
|
||||
if (m_objectList.Count > 0)
|
||||
{
|
||||
var start = Time.realtimeSinceStartup;
|
||||
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);
|
||||
}
|
||||
var diff = Time.realtimeSinceStartup - start;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if m_currentTransform != null ...
|
||||
}
|
||||
}
|
||||
else // ------ Scene Search results ------
|
||||
@ -196,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;
|
||||
@ -20,8 +21,12 @@ namespace Explorer
|
||||
|
||||
private string m_searchInput = "";
|
||||
private string m_typeInput = "";
|
||||
private int m_limit = 100;
|
||||
private int m_limit = 20;
|
||||
private int m_pageOffset = 0;
|
||||
//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;
|
||||
@ -42,9 +47,6 @@ namespace Explorer
|
||||
Custom
|
||||
}
|
||||
|
||||
private List<object> m_searchResults = new List<object>();
|
||||
private Vector2 resultsScroll = Vector2.zero;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
@ -60,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
|
||||
@ -69,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();
|
||||
@ -86,11 +107,11 @@ namespace Explorer
|
||||
|
||||
int count = m_searchResults.Count;
|
||||
|
||||
if (count > CppExplorer.ArrayLimit)
|
||||
if (count > this.m_limit)
|
||||
{
|
||||
// prev/next page buttons
|
||||
GUILayout.BeginHorizontal(null);
|
||||
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
|
||||
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1;
|
||||
if (GUILayout.Button("< Prev", null))
|
||||
{
|
||||
if (m_pageOffset > 0) m_pageOffset--;
|
||||
@ -111,29 +132,13 @@ namespace Explorer
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = m_pageOffset * CppExplorer.ArrayLimit;
|
||||
int preiterated = 0;
|
||||
int offset = m_pageOffset * this.m_limit;
|
||||
if (offset >= count) m_pageOffset = 0;
|
||||
|
||||
if (offset >= count) offset = 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 > CppExplorer.ArrayLimit - 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
|
||||
@ -142,7 +147,6 @@ namespace Explorer
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch
|
||||
@ -246,7 +250,125 @@ namespace Explorer
|
||||
}
|
||||
|
||||
|
||||
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
|
||||
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
|
||||
|
||||
// ======= search functions =======
|
||||
|
||||
private void Search()
|
||||
{
|
||||
m_pageOffset = 0;
|
||||
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||
{
|
||||
Il2CppSystem.Type searchType = null;
|
||||
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
try
|
||||
{
|
||||
var findType = ReflectionHelpers.GetTypeByName(_type);
|
||||
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
{
|
||||
searchType = ReflectionHelpers.ObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.GameObject)
|
||||
{
|
||||
searchType = ReflectionHelpers.GameObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Component)
|
||||
{
|
||||
searchType = ReflectionHelpers.ComponentType;
|
||||
}
|
||||
|
||||
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
|
||||
{
|
||||
MelonLogger.LogError("Your Custom Class Type must inherit from UnityEngine.Object!");
|
||||
return new List<object>();
|
||||
}
|
||||
|
||||
var matches = new List<object>();
|
||||
|
||||
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 == ReflectionHelpers.ComponentType && ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
{
|
||||
// Transforms shouldn't really be counted as Components, skip them.
|
||||
// They're more akin to GameObjects.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SceneMode != SceneFilter.Any && !FilterScene(obj, this.SceneMode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!matches.Contains(obj))
|
||||
{
|
||||
matches.Add(obj);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public static bool FilterScene(object obj, SceneFilter filter)
|
||||
{
|
||||
GameObject go;
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
go = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Component>().gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
go = (obj as GameObject) ?? (obj as Component).gameObject;
|
||||
}
|
||||
|
||||
if (!go)
|
||||
{
|
||||
// object is not on a GameObject, cannot perform scene filter operation.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter == SceneFilter.None)
|
||||
{
|
||||
return string.IsNullOrEmpty(go.scene.name);
|
||||
}
|
||||
else if (filter == SceneFilter.This)
|
||||
{
|
||||
return go.scene.name == UnityHelpers.ActiveSceneName;
|
||||
}
|
||||
else if (filter == SceneFilter.DontDestroy)
|
||||
{
|
||||
return go.scene.name == "DontDestroyOnLoad";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====== other ========
|
||||
|
||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
||||
public static IEnumerable<object> GetInstanceClassScanner()
|
||||
@ -286,172 +408,5 @@ namespace Explorer
|
||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
||||
catch { return Enumerable.Empty<Type>(); }
|
||||
}
|
||||
|
||||
// ======= search functions =======
|
||||
|
||||
private void Search()
|
||||
{
|
||||
m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput);
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||
{
|
||||
Il2CppSystem.Type type = null;
|
||||
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
try
|
||||
{
|
||||
var findType = CppExplorer.GetType(_type);
|
||||
type = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
{
|
||||
type = Il2CppType.Of<Object>();
|
||||
}
|
||||
else if (TypeMode == TypeFilter.GameObject)
|
||||
{
|
||||
type = Il2CppType.Of<GameObject>();
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Component)
|
||||
{
|
||||
type = Il2CppType.Of<Component>();
|
||||
}
|
||||
|
||||
if (!Il2CppType.Of<Object>().IsAssignableFrom(type))
|
||||
{
|
||||
MelonLogger.LogError("Your Class Type must inherit from UnityEngine.Object! Leave blank to default to UnityEngine.Object");
|
||||
return new List<object>();
|
||||
}
|
||||
|
||||
var matches = new List<object>();
|
||||
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll(type))
|
||||
{
|
||||
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SceneMode != SceneFilter.Any)
|
||||
{
|
||||
if (SceneMode == SceneFilter.None)
|
||||
{
|
||||
if (!NoSceneFilter(obj, obj.GetType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject go;
|
||||
|
||||
var objtype = obj.GetType();
|
||||
if (objtype == typeof(GameObject))
|
||||
{
|
||||
go = obj as GameObject;
|
||||
}
|
||||
else if (typeof(Component).IsAssignableFrom(objtype))
|
||||
{
|
||||
go = (obj as Component).gameObject;
|
||||
}
|
||||
else { continue; }
|
||||
|
||||
if (!go) { continue; }
|
||||
|
||||
if (SceneMode == SceneFilter.This)
|
||||
{
|
||||
if (go.scene.name != CppExplorer.ActiveSceneName || go.scene.name == "DontDestroyOnLoad")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (SceneMode == SceneFilter.DontDestroy)
|
||||
{
|
||||
if (go.scene.name != "DontDestroyOnLoad")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matches.Contains(obj))
|
||||
{
|
||||
matches.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public static bool ThisSceneFilter(object obj, Type type)
|
||||
{
|
||||
if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
var go = obj as GameObject ?? (obj as Component).gameObject;
|
||||
|
||||
if (go != null && go.scene.name == CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool DontDestroyFilter(object obj, Type type)
|
||||
{
|
||||
if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
var go = obj as GameObject ?? (obj as Component).gameObject;
|
||||
|
||||
if (go != null && go.scene.name == "DontDestroyOnLoad")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool NoSceneFilter(object obj, Type type)
|
||||
{
|
||||
if (type == typeof(GameObject))
|
||||
{
|
||||
var go = obj as GameObject;
|
||||
|
||||
if (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
var go = (obj as Component).gameObject;
|
||||
|
||||
if (go == null || (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
502
src/UIStyles.cs
502
src/UIStyles.cs
@ -1,502 +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();
|
||||
if (valueType.IsPrimitive || value.GetType() == typeof(string))
|
||||
{
|
||||
DrawPrimitive(ref value, rect, setTarget, setAction);
|
||||
}
|
||||
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
|
||||
{
|
||||
GameObject go;
|
||||
if (value.GetType() == typeof(Transform))
|
||||
{
|
||||
go = (value as Transform).gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
go = (value as 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//public static void DrawMember(ref object value, string valueType, string memberName, Rect rect, object setTarget = null, Action<object> setAction = null, float labelWidth = 180, bool autoSet = false)
|
||||
//{
|
||||
// GUILayout.Label("<color=cyan>" + memberName + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||
|
||||
// DrawValue(ref value, rect, valueType, memberName, setTarget, setAction, autoSet);
|
||||
//}
|
||||
|
||||
//public static void DrawValue(ref object value, Rect rect, string nullValueType = null, string memberName = null, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
||||
//{
|
||||
// if (value == null)
|
||||
// {
|
||||
// GUILayout.Label("<i>null (" + nullValueType + ")</i>", null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using Harmony;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CppExplorer : MelonMod
|
||||
{
|
||||
// consts
|
||||
|
||||
public const string ID = "com.sinai.cppexplorer";
|
||||
public const string NAME = "IL2CPP Runtime Explorer (Unity 2018)";
|
||||
public const string VERSION = "1.3.1";
|
||||
public const string AUTHOR = "Sinai";
|
||||
|
||||
// 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;
|
||||
|
||||
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
|
||||
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
base.OnApplicationStart();
|
||||
|
||||
Instance = this;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// Update
|
||||
public override void OnUpdate()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.F7))
|
||||
{
|
||||
ShowMenu = !ShowMenu;
|
||||
}
|
||||
|
||||
if (ShowMenu)
|
||||
{
|
||||
if (!Cursor.visible)
|
||||
{
|
||||
Cursor.visible = true;
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
}
|
||||
|
||||
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 = "";
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnGUI()
|
||||
{
|
||||
base.OnGUI();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Explorer</RootNamespace>
|
||||
<AssemblyName>CppExplorer_Unity2018</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\Release\</OutputPath>
|
||||
<DefineConstants>
|
||||
</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Il2Cppmscorlib">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Il2CppSystem">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2CppSystem.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Il2CppSystem.Core">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="mcs">
|
||||
<HintPath>..\lib\mcs.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader.ModHandler">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="UnhollowerBaseLib">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnhollowerRuntimeLib">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextRenderingModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UIElementsModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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="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="MainMenu\MainMenu.cs" />
|
||||
<Compile Include="Inspectors\GameObjectWindow.cs" />
|
||||
<Compile Include="Inspectors\ReflectionWindow.cs" />
|
||||
<Compile Include="MainMenu\Pages\ScenePage.cs" />
|
||||
<Compile Include="MainMenu\Pages\SearchPage.cs" />
|
||||
<Compile Include="UIStyles.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="utils\AccessTools.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30128.74
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppExplorer", "CppExplorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DD5C0A5D-03F1-4CC3-8B4D-E10834908C5A}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -1,448 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MelonLoader;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class GameObjectWindow : UIWindow
|
||||
{
|
||||
public override string Name { get => "GameObject Inspector"; set => Name = value; }
|
||||
|
||||
public GameObject m_object;
|
||||
|
||||
// gui element holders
|
||||
private string m_name;
|
||||
private string m_scene;
|
||||
|
||||
private Vector2 m_transformScroll = Vector2.zero;
|
||||
private Transform[] m_children;
|
||||
|
||||
private Vector2 m_compScroll = Vector2.zero;
|
||||
//private Component[] m_components;
|
||||
|
||||
private float m_translateAmount = 0.3f;
|
||||
private float m_rotateAmount = 50f;
|
||||
private float m_scaleAmount = 0.1f;
|
||||
|
||||
List<Component> m_cachedDestroyList = new List<Component>();
|
||||
//private string m_addComponentInput = "";
|
||||
|
||||
private string m_setParentInput = "";
|
||||
|
||||
public bool GetObjectAsGameObject()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
MelonLogger.Log("Target is null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetType = Target.GetType();
|
||||
|
||||
if (targetType == typeof(GameObject))
|
||||
{
|
||||
m_object = Target as GameObject;
|
||||
return true;
|
||||
}
|
||||
else if (targetType == typeof(Transform))
|
||||
{
|
||||
m_object = (Target as Transform).gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
MelonLogger.Log("Error: Target is null or not a GameObject/Transform!");
|
||||
DestroyWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (!GetObjectAsGameObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_name = m_object.name;
|
||||
m_scene = m_object.scene == null ? "null" : m_object.scene.name;
|
||||
|
||||
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
//m_object.GetComponents(listComps);
|
||||
//m_components = listComps.ToArray();
|
||||
|
||||
var list = new List<Transform>();
|
||||
for (int i = 0; i < m_object.transform.childCount; i++)
|
||||
{
|
||||
list.Add(m_object.transform.GetChild(i));
|
||||
}
|
||||
m_children = list.ToArray();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!m_object && !GetObjectAsGameObject())
|
||||
{
|
||||
MelonLogger.Log("Object is null! Destroying window...");
|
||||
DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void InspectGameObject(GameObject obj)
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height);
|
||||
DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReflectObject(Il2CppSystem.Object obj)
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100))
|
||||
{
|
||||
window.m_rect = new Rect(
|
||||
this.m_rect.x + this.m_rect.width + 20,
|
||||
this.m_rect.y,
|
||||
550,
|
||||
700);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
Header();
|
||||
|
||||
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
||||
|
||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
|
||||
if (m_scene == CppExplorer.ActiveSceneName)
|
||||
{
|
||||
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
|
||||
{
|
||||
ScenePage.Instance.SetTransformTarget(m_object);
|
||||
MainMenu.SetCurrentPage(0);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
string pathlabel = CppExplorer.GetGameObjectPath(m_object.transform);
|
||||
if (m_object.transform.parent != null)
|
||||
{
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
InspectGameObject(m_object.transform.parent.gameObject);
|
||||
}
|
||||
}
|
||||
GUILayout.TextArea(pathlabel, null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
GUILayout.TextArea(m_name, null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// --- Horizontal Columns section ---
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
|
||||
TransformList();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
|
||||
ComponentList();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.EndHorizontal(); // end horiz columns
|
||||
|
||||
GameObjectControls();
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void TransformList()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
||||
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView);
|
||||
|
||||
GUILayout.Label("<b>Children:</b>", null);
|
||||
if (m_children != null && m_children.Length > 0)
|
||||
{
|
||||
foreach (var obj in m_children.Where(x => x.childCount > 0))
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
}
|
||||
foreach (var obj in m_children.Where(x => x.childCount == 0))
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<i>None</i>", null);
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
private void ComponentList()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
||||
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView);
|
||||
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
m_cachedDestroyList.Clear();
|
||||
}
|
||||
|
||||
var m_components = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
m_object.GetComponentsInternal(Il2CppType.Of<Component>(), false, false, true, false, m_components);
|
||||
|
||||
var ilTypeOfTransform = Il2CppType.Of<Transform>();
|
||||
var ilTypeOfBehaviour = Il2CppType.Of<Behaviour>();
|
||||
foreach (var component in m_components)
|
||||
{
|
||||
var ilType = component.GetIl2CppType();
|
||||
if (ilType == ilTypeOfTransform)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (ilTypeOfBehaviour.IsAssignableFrom(ilType))
|
||||
{
|
||||
BehaviourEnabledBtn(component.TryCast<Behaviour>());
|
||||
}
|
||||
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
|
||||
{
|
||||
ReflectObject(component);
|
||||
}
|
||||
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
m_cachedDestroyList.Add(component);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var comp = m_cachedDestroyList[i];
|
||||
GameObject.Destroy(comp);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
//GUILayout.BeginHorizontal(null);
|
||||
//m_addComponentInput = GUILayout.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 150) });
|
||||
//if (GUILayout.Button("Add Component", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
//{
|
||||
// if (HPExplorer.GetType(m_addComponentInput) is Type type && typeof(Component).IsAssignableFrom(type))
|
||||
// {
|
||||
// var comp = m_object.AddComponent(type);
|
||||
// var list = m_components.ToList();
|
||||
// list.Add(comp);
|
||||
// m_components = list.ToArray();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// MelonLogger.LogWarning($"Could not get type '{m_addComponentInput}'. If it's not a typo, try the fully qualified name.");
|
||||
// }
|
||||
//}
|
||||
//GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void BehaviourEnabledBtn(Behaviour obj)
|
||||
{
|
||||
var _col = GUI.color;
|
||||
bool _enabled = obj.enabled;
|
||||
if (_enabled)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
}
|
||||
|
||||
// ------ toggle active button ------
|
||||
|
||||
_enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
||||
if (obj.enabled != _enabled)
|
||||
{
|
||||
obj.enabled = _enabled;
|
||||
}
|
||||
GUI.color = _col;
|
||||
}
|
||||
|
||||
private void GameObjectControls()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(530) });
|
||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
bool m_active = m_object.activeSelf;
|
||||
m_active = GUILayout.Toggle(m_active, (m_active ? "<color=lime>Enabled " : "<color=red>Disabled") + "</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); }
|
||||
|
||||
UIStyles.InstantiateButton(m_object, 100);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
if (GUILayout.Button("Remove from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
|
||||
{
|
||||
m_object.transform.parent = null;
|
||||
}
|
||||
m_setParentInput = GUILayout.TextField(m_setParentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width - 280) });
|
||||
if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
if (GameObject.Find(m_setParentInput) is GameObject newparent)
|
||||
{
|
||||
m_object.transform.parent = newparent.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'");
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
|
||||
var t = m_object.transform;
|
||||
TranslateControl(t, TranslateType.Position, ref m_translateAmount, false);
|
||||
TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true);
|
||||
TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false);
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
if (GUILayout.Button("<color=red><b>Destroy</b></color>", null))
|
||||
{
|
||||
GameObject.Destroy(m_object);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
public enum TranslateType
|
||||
{
|
||||
Position,
|
||||
Rotation,
|
||||
Scale
|
||||
}
|
||||
|
||||
private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<color=cyan><b>" + mode + "</b></color>:", new GUILayoutOption[] { GUILayout.Width(65) });
|
||||
|
||||
Vector3 vector = Vector3.zero;
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position: vector = transform.localPosition; break;
|
||||
case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break;
|
||||
case TranslateType.Scale: vector = transform.localScale; break;
|
||||
}
|
||||
GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
|
||||
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.x, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.y, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.z, amount, multByTime);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position: transform.localPosition = vector; break;
|
||||
case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break;
|
||||
case TranslateType.Scale: transform.localScale = vector; break;
|
||||
}
|
||||
|
||||
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
var input = amount.ToString("F3");
|
||||
input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
if (float.TryParse(input, out float f))
|
||||
{
|
||||
amount = f;
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
|
||||
{
|
||||
string s = f.ToString("F3");
|
||||
s = GUILayout.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(s, out float f2))
|
||||
{
|
||||
f = f2;
|
||||
}
|
||||
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f -= multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f += multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,82 +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)
|
||||
{
|
||||
m_value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (fieldInfo.FieldType.IsEnum)
|
||||
{
|
||||
if (System.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldInfo.SetValue(fieldInfo.IsStatic ? null : obj, m_value);
|
||||
}
|
||||
}
|
||||
}
|
@ -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,126 +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(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 (System.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,355 +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 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 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();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_autoUpdate)
|
||||
{
|
||||
UpdateValues();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateValues()
|
||||
{
|
||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Field)
|
||||
{
|
||||
foreach (var holder in this.m_FieldInfos)
|
||||
{
|
||||
if (m_search == "" || holder.fieldInfo.Name.ToLower().Contains(m_search.ToLower()))
|
||||
{
|
||||
holder.UpdateValue(m_object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_filter == MemberFilter.Both || m_filter == MemberFilter.Property)
|
||||
{
|
||||
foreach (var holder in this.m_PropertyInfos)
|
||||
{
|
||||
if (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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Harmony;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public abstract class UIWindow
|
||||
{
|
||||
public abstract string Name { get; set; }
|
||||
|
||||
public object Target;
|
||||
|
||||
public int windowID;
|
||||
public Rect m_rect = new Rect(0, 0, 550, 700);
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
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;
|
||||
window.windowID = WindowManager.NextWindowID();
|
||||
window.m_rect = WindowManager.GetNewWindowRect();
|
||||
|
||||
WindowManager.Windows.Add(window);
|
||||
|
||||
window.Init();
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
public void DestroyWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
WindowManager.Windows.Remove(this);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var origSkin = GUI.skin;
|
||||
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Name);
|
||||
|
||||
GUI.skin = origSkin;
|
||||
}
|
||||
}
|
||||
|
||||
public void Header()
|
||||
{
|
||||
GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20));
|
||||
|
||||
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red><b>X</b></color>"))
|
||||
{
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityEngine.EventSystems;
|
||||
using MelonLoader;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class MainMenu
|
||||
{
|
||||
public static MainMenu Instance;
|
||||
|
||||
public MainMenu()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
Pages.Add(new ScenePage());
|
||||
Pages.Add(new SearchPage());
|
||||
Pages.Add(new ConsolePage());
|
||||
|
||||
foreach (var page in Pages)
|
||||
{
|
||||
page.Init();
|
||||
}
|
||||
}
|
||||
|
||||
public const int MainWindowID = 10;
|
||||
public static Rect MainRect = new Rect(5, 5, 550, 700);
|
||||
private static readonly List<WindowPage> Pages = new List<WindowPage>();
|
||||
private static int m_currentPage = 0;
|
||||
|
||||
public static void SetCurrentPage(int index)
|
||||
{
|
||||
if (index < 0 || Pages.Count <= index)
|
||||
{
|
||||
MelonLogger.Log("cannot set page " + index);
|
||||
return;
|
||||
}
|
||||
m_currentPage = index;
|
||||
GUI.BringWindowToFront(MainWindowID);
|
||||
GUI.FocusWindow(MainWindowID);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Pages[m_currentPage].Update();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (CppExplorer.ShowMenu)
|
||||
{
|
||||
var origSkin = GUI.skin;
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
|
||||
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, "IL2CPP Runtime Explorer");
|
||||
|
||||
GUI.skin = origSkin;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow(int id)
|
||||
{
|
||||
GUI.DragWindow(new Rect(0, 0, MainRect.width - 90, 20));
|
||||
|
||||
if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), "Hide (F7)"))
|
||||
{
|
||||
CppExplorer.ShowMenu = false;
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.BeginArea(new Rect(5, 25, MainRect.width - 10, MainRect.height - 35), GUI.skin.box);
|
||||
|
||||
MainHeader();
|
||||
|
||||
var page = Pages[m_currentPage];
|
||||
page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView);
|
||||
page.DrawWindow();
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
MainRect = WindowManager.ResizeWindow(MainRect, MainWindowID);
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void MainHeader()
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<b>Options:</b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("Array Limit:", new GUILayoutOption[] { GUILayout.Width(70) });
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
var _input = GUILayout.TextField(CppExplorer.ArrayLimit.ToString(), new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (int.TryParse(_input, out int _lim))
|
||||
{
|
||||
CppExplorer.ArrayLimit = _lim;
|
||||
}
|
||||
CppExplorer.Instance.MouseInspect = GUILayout.Toggle(CppExplorer.Instance.MouseInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
for (int i = 0; i < Pages.Count; i++)
|
||||
{
|
||||
if (m_currentPage == i)
|
||||
GUI.color = Color.green;
|
||||
else
|
||||
GUI.color = Color.white;
|
||||
|
||||
if (GUILayout.Button(Pages[i].Name, null))
|
||||
{
|
||||
m_currentPage = i;
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,131 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Mono.CSharp;
|
||||
using UnityEngine;
|
||||
using Attribute = System.Attribute;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class REPL : InteractiveBase
|
||||
{
|
||||
static REPL()
|
||||
{
|
||||
var go = new GameObject("UnityREPL");
|
||||
GameObject.DontDestroyOnLoad(go);
|
||||
//go.transform.parent = HPExplorer.Instance.transform;
|
||||
MB = go.AddComponent<ReplHelper>();
|
||||
}
|
||||
|
||||
[Documentation("MB - A dummy MonoBehaviour for accessing Unity.")]
|
||||
public static ReplHelper MB { get; }
|
||||
|
||||
[Documentation("find<T>() - find a UnityEngine.Object of type T.")]
|
||||
public static T find<T>() where T : Object
|
||||
{
|
||||
return MB.Find<T>();
|
||||
}
|
||||
|
||||
[Documentation("findAll<T>() - find all UnityEngine.Object of type T.")]
|
||||
public static T[] findAll<T>() where T : Object
|
||||
{
|
||||
return MB.FindAll<T>();
|
||||
}
|
||||
|
||||
//[Documentation("runCoroutine(enumerator) - runs an IEnumerator as a Unity coroutine.")]
|
||||
//public static object runCoroutine(IEnumerator i)
|
||||
//{
|
||||
// return MB.RunCoroutine(i);
|
||||
//}
|
||||
|
||||
//[Documentation("endCoroutine(co) - ends a Unity coroutine.")]
|
||||
//public static void endCoroutine(Coroutine c)
|
||||
//{
|
||||
// MB.EndCoroutine(c);
|
||||
//}
|
||||
|
||||
////[Documentation("type<T>() - obtain type info about a type T. Provides some Reflection helpers.")]
|
||||
////public static TypeHelper type<T>()
|
||||
////{
|
||||
//// return new TypeHelper(typeof(T));
|
||||
////}
|
||||
|
||||
////[Documentation("type(obj) - obtain type info about object obj. Provides some Reflection helpers.")]
|
||||
////public static TypeHelper type(object instance)
|
||||
////{
|
||||
//// return new TypeHelper(instance);
|
||||
////}
|
||||
|
||||
//[Documentation("dir(obj) - lists all available methods and fiels of a given obj.")]
|
||||
//public static string dir(object instance)
|
||||
//{
|
||||
// return type(instance).info();
|
||||
//}
|
||||
|
||||
//[Documentation("dir<T>() - lists all available methods and fields of type T.")]
|
||||
//public static string dir<T>()
|
||||
//{
|
||||
// return type<T>().info();
|
||||
//}
|
||||
|
||||
//[Documentation("findrefs(obj) - find references to the object in currently loaded components.")]
|
||||
//public static Component[] findrefs(object obj)
|
||||
//{
|
||||
// if (obj == null) throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
// var results = new List<Component>();
|
||||
// foreach (var component in Object.FindObjectsOfType<Component>())
|
||||
// {
|
||||
// var type = component.GetType();
|
||||
|
||||
// var nameBlacklist = new[] { "parent", "parentInternal", "root", "transform", "gameObject" };
|
||||
// var typeBlacklist = new[] { typeof(bool) };
|
||||
|
||||
// foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
// .Where(x => x.CanRead && !nameBlacklist.Contains(x.Name) && !typeBlacklist.Contains(x.PropertyType)))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (Equals(prop.GetValue(component, null), obj))
|
||||
// {
|
||||
// results.Add(component);
|
||||
// goto finish;
|
||||
// }
|
||||
// }
|
||||
// catch { }
|
||||
// }
|
||||
// foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
// .Where(x => !nameBlacklist.Contains(x.Name) && !typeBlacklist.Contains(x.FieldType)))
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// if (Equals(field.GetValue(component), obj))
|
||||
// {
|
||||
// results.Add(component);
|
||||
// goto finish;
|
||||
// }
|
||||
// }
|
||||
// catch { }
|
||||
// }
|
||||
// finish:;
|
||||
// }
|
||||
|
||||
// return results.ToArray();
|
||||
//}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property)]
|
||||
private class DocumentationAttribute : Attribute
|
||||
{
|
||||
public DocumentationAttribute(string doc)
|
||||
{
|
||||
Docs = doc;
|
||||
}
|
||||
|
||||
public string Docs { get; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
using System.Collections;
|
||||
//using Il2CppSystem;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ReplHelper : MonoBehaviour
|
||||
{
|
||||
public ReplHelper(IntPtr intPtr) : base(intPtr) { }
|
||||
|
||||
public T Find<T>() where T : Object
|
||||
{
|
||||
return FindObjectOfType<T>();
|
||||
}
|
||||
|
||||
public T[] FindAll<T>() where T : Object
|
||||
{
|
||||
return FindObjectsOfType<T>();
|
||||
}
|
||||
|
||||
//public object RunCoroutine(IEnumerator enumerator)
|
||||
//{
|
||||
// return MelonCoroutines.Start(enumerator);
|
||||
//}
|
||||
|
||||
//public void EndCoroutine(Coroutine c)
|
||||
//{
|
||||
// StopCoroutine(c);
|
||||
//}
|
||||
}
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using Mono.CSharp;
|
||||
using System.IO;
|
||||
using MelonLoader;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ConsolePage : WindowPage
|
||||
{
|
||||
public override string Name { get => "C# Console"; set => base.Name = value; }
|
||||
|
||||
private ScriptEvaluator _evaluator;
|
||||
private readonly StringBuilder _sb = new StringBuilder();
|
||||
|
||||
private string MethodInput = "";
|
||||
private string UsingInput = "";
|
||||
|
||||
public static List<string> UsingDirectives;
|
||||
|
||||
private static readonly string[] m_defaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"UnityEngine",
|
||||
"System.Linq",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"System.Reflection",
|
||||
"MelonLoader"
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp<ReplHelper>();
|
||||
|
||||
try
|
||||
{
|
||||
MethodInput = @"// This is a basic C# REPL console.
|
||||
// Some common using directives are added by default, you can add more below.
|
||||
// If you want to return some output, MelonLogger.Log() it.
|
||||
|
||||
MelonLogger.Log(""hello world"");";
|
||||
|
||||
ResetConsole();
|
||||
|
||||
foreach (var use in m_defaultUsing)
|
||||
{
|
||||
AddUsing(use);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Error setting up console!\r\nMessage: {e.Message}\r\nStack: {e.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetConsole()
|
||||
{
|
||||
if (_evaluator != null)
|
||||
{
|
||||
_evaluator.Dispose();
|
||||
}
|
||||
|
||||
_evaluator = new ScriptEvaluator(new StringWriter(_sb)) { InteractiveBaseClass = typeof(REPL) };
|
||||
|
||||
UsingDirectives = new List<string>();
|
||||
}
|
||||
|
||||
public string AsmToUsing(string asm, bool richtext = false)
|
||||
{
|
||||
if (richtext)
|
||||
{
|
||||
return $"<color=#569cd6>using</color> {asm};";
|
||||
}
|
||||
return $"using {asm};";
|
||||
}
|
||||
|
||||
public void AddUsing(string asm)
|
||||
{
|
||||
if (!UsingDirectives.Contains(asm))
|
||||
{
|
||||
UsingDirectives.Add(asm);
|
||||
Evaluate(AsmToUsing(asm));
|
||||
}
|
||||
}
|
||||
|
||||
public object Evaluate(string str)
|
||||
{
|
||||
object ret = VoidType.Value;
|
||||
|
||||
_evaluator.Compile(str, out var compiled);
|
||||
|
||||
try
|
||||
{
|
||||
compiled?.Invoke(ref ret);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.LogWarning(e.ToString());
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", null);
|
||||
|
||||
GUILayout.Label("Method:", null);
|
||||
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(300) });
|
||||
|
||||
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
|
||||
{
|
||||
try
|
||||
{
|
||||
MethodInput = MethodInput.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(MethodInput))
|
||||
{
|
||||
var result = Evaluate(MethodInput);
|
||||
|
||||
if (result != null && !Equals(result, VoidType.Value))
|
||||
{
|
||||
MelonLogger.Log("[Console Output]\r\n" + result.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label("<b>Using directives:</b>", null);
|
||||
foreach (var asm in UsingDirectives)
|
||||
{
|
||||
GUILayout.Label(AsmToUsing(asm, true), null);
|
||||
}
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(110) });
|
||||
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
if (GUILayout.Button("Add", new GUILayoutOption[] { GUILayout.Width(50) }))
|
||||
{
|
||||
AddUsing(UsingInput);
|
||||
}
|
||||
if (GUILayout.Button("<color=red>Reset</color>", null))
|
||||
{
|
||||
ResetConsole();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
private class VoidType
|
||||
{
|
||||
public static readonly VoidType Value = new VoidType();
|
||||
private VoidType() { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class ScriptEvaluator : Evaluator, IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> StdLib =
|
||||
new HashSet<string>(StringComparer.InvariantCultureIgnoreCase) { "mscorlib", "System.Core", "System", "System.Xml" };
|
||||
|
||||
private readonly TextWriter _logger;
|
||||
|
||||
public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger))
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
ImportAppdomainAssemblies(ReferenceAssembly);
|
||||
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
||||
_logger.Dispose();
|
||||
}
|
||||
|
||||
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
||||
{
|
||||
string name = args.LoadedAssembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
return;
|
||||
ReferenceAssembly(args.LoadedAssembly);
|
||||
}
|
||||
|
||||
private static CompilerContext BuildContext(TextWriter tw)
|
||||
{
|
||||
var reporter = new StreamReportPrinter(tw);
|
||||
|
||||
var settings = new CompilerSettings
|
||||
{
|
||||
Version = LanguageVersion.Experimental,
|
||||
GenerateDebugInfo = false,
|
||||
StdLib = true,
|
||||
Target = Target.Library,
|
||||
WarningLevel = 0,
|
||||
EnhancedWarnings = false
|
||||
};
|
||||
|
||||
return new CompilerContext(settings, reporter);
|
||||
}
|
||||
|
||||
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
string name = assembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
continue;
|
||||
import(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,297 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ScenePage : WindowPage
|
||||
{
|
||||
public static ScenePage Instance;
|
||||
|
||||
public override string Name { get => "Scene Explorer"; set => base.Name = value; }
|
||||
|
||||
// ----- Holders for GUI elements ----- //
|
||||
|
||||
private string m_currentScene = "";
|
||||
|
||||
// gameobject list
|
||||
private Transform m_currentTransform;
|
||||
private List<GameObjectCache> m_objectList = new List<GameObjectCache>();
|
||||
private float m_timeOfLastUpdate = -1f;
|
||||
|
||||
// search bar
|
||||
private bool m_searching = false;
|
||||
private string m_searchInput = "";
|
||||
private List<GameObjectCache> m_searchResults = new List<GameObjectCache>();
|
||||
|
||||
// ------------ Init and Update ------------ //
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_currentScene = CppExplorer.ActiveSceneName;
|
||||
|
||||
m_currentTransform = null;
|
||||
CancelSearch();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (Time.time - m_timeOfLastUpdate < 0.2f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
var start = Time.realtimeSinceStartup;
|
||||
|
||||
if (!m_searching)
|
||||
{
|
||||
m_objectList = new List<GameObjectCache>();
|
||||
if (m_currentTransform)
|
||||
{
|
||||
var endAppend = new List<GameObjectCache>();
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
var child = m_currentTransform.GetChild(i);
|
||||
|
||||
if (child)
|
||||
{
|
||||
if (child.childCount > 0)
|
||||
m_objectList.Add(new GameObjectCache(child.gameObject));
|
||||
else
|
||||
endAppend.Add(new GameObjectCache(child.gameObject));
|
||||
}
|
||||
}
|
||||
m_objectList.AddRange(endAppend);
|
||||
endAppend = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var scene = SceneManager.GetSceneByName(m_currentScene);
|
||||
var rootObjects = scene.GetRootGameObjects();
|
||||
|
||||
// add objects with children first
|
||||
foreach (var obj in rootObjects.Where(x => x.transform.childCount > 0))
|
||||
{
|
||||
m_objectList.Add(new GameObjectCache(obj));
|
||||
}
|
||||
foreach (var obj in rootObjects.Where(x => x.transform.childCount == 0))
|
||||
{
|
||||
m_objectList.Add(new GameObjectCache(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------- GUI Draw Functions --------- //
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
// Current Scene label
|
||||
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
if (SceneManager.sceneCount > 1)
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
var scenes = SceneManager.GetAllScenes();
|
||||
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||
index += changeWanted;
|
||||
if (index >= scenes.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index > 0)
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
}
|
||||
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// ----- GameObject Search -----
|
||||
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
||||
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
m_searchInput = GUILayout.TextField(m_searchInput, null);
|
||||
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(15);
|
||||
|
||||
// ************** GameObject list ***************
|
||||
|
||||
// ----- main explorer ------
|
||||
if (!m_searching)
|
||||
{
|
||||
if (m_currentTransform != null)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
TraverseUp();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(CppExplorer.GetGameObjectPath(m_currentTransform), null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Scene Root GameObjects:", null);
|
||||
}
|
||||
|
||||
if (m_objectList.Count > 0)
|
||||
{
|
||||
var start = Time.realtimeSinceStartup;
|
||||
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);
|
||||
}
|
||||
var diff = Time.realtimeSinceStartup - start;
|
||||
}
|
||||
else
|
||||
{
|
||||
// if m_currentTransform != null ...
|
||||
}
|
||||
}
|
||||
else // ------ Scene Search results ------
|
||||
{
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
CancelSearch();
|
||||
}
|
||||
|
||||
GUILayout.Label("Search Results:", null);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
foreach (var obj in m_searchResults)
|
||||
{
|
||||
//UIStyles.GameobjButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
UIStyles.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_currentTransform = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// -------- Actual Methods (not drawing GUI) ---------- //
|
||||
|
||||
public void SetTransformTarget(GameObject obj)
|
||||
{
|
||||
m_currentTransform = obj.transform;
|
||||
CancelSearch();
|
||||
}
|
||||
|
||||
public void TraverseUp()
|
||||
{
|
||||
if (m_currentTransform.parent != null)
|
||||
{
|
||||
m_currentTransform = m_currentTransform.parent;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentTransform = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Search()
|
||||
{
|
||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
||||
m_searching = true;
|
||||
}
|
||||
|
||||
public void CancelSearch()
|
||||
{
|
||||
m_searching = false;
|
||||
}
|
||||
|
||||
public List<GameObjectCache> SearchSceneObjects(string _search)
|
||||
{
|
||||
var matches = new List<GameObjectCache>();
|
||||
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll<GameObject>())
|
||||
{
|
||||
if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == m_currentScene)
|
||||
{
|
||||
matches.Add(new GameObjectCache(obj));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public class GameObjectCache
|
||||
{
|
||||
public GameObject RefGameObject;
|
||||
public string Label;
|
||||
public Color EnabledColor;
|
||||
public int ChildCount;
|
||||
|
||||
public GameObjectCache(GameObject obj)
|
||||
{
|
||||
RefGameObject = obj;
|
||||
ChildCount = obj.transform.childCount;
|
||||
|
||||
Label = (ChildCount > 0) ? "[" + obj.transform.childCount + " children] " : "";
|
||||
Label += obj.name;
|
||||
|
||||
bool enabled = obj.activeSelf;
|
||||
int childCount = obj.transform.childCount;
|
||||
if (enabled)
|
||||
{
|
||||
if (childCount > 0)
|
||||
{
|
||||
EnabledColor = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnabledColor = UIStyles.LightGreen;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EnabledColor = Color.red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,457 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
using UnhollowerRuntimeLib;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class SearchPage : WindowPage
|
||||
{
|
||||
public static SearchPage Instance;
|
||||
|
||||
public override string Name { get => "Object Search"; set => base.Name = value; }
|
||||
|
||||
private string m_searchInput = "";
|
||||
private string m_typeInput = "";
|
||||
private int m_limit = 100;
|
||||
private int m_pageOffset = 0;
|
||||
|
||||
public SceneFilter SceneMode = SceneFilter.Any;
|
||||
public TypeFilter TypeMode = TypeFilter.Object;
|
||||
|
||||
public enum SceneFilter
|
||||
{
|
||||
Any,
|
||||
This,
|
||||
DontDestroy,
|
||||
None
|
||||
}
|
||||
|
||||
public enum TypeFilter
|
||||
{
|
||||
Object,
|
||||
GameObject,
|
||||
Component,
|
||||
Custom
|
||||
}
|
||||
|
||||
private List<object> m_searchResults = new List<object>();
|
||||
private Vector2 resultsScroll = Vector2.zero;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_searchResults.Clear();
|
||||
m_pageOffset = 0;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
}
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// helpers
|
||||
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
||||
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_pageOffset = 0;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// search box
|
||||
SearchBox();
|
||||
|
||||
// results
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<b><color=orange>Results </color></b>" + " (" + m_searchResults.Count + ")", null);
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
int count = m_searchResults.Count;
|
||||
|
||||
if (count > CppExplorer.ArrayLimit)
|
||||
{
|
||||
// prev/next page buttons
|
||||
GUILayout.BeginHorizontal(null);
|
||||
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
|
||||
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();
|
||||
}
|
||||
|
||||
resultsScroll = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView);
|
||||
|
||||
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = m_pageOffset * CppExplorer.ArrayLimit;
|
||||
int preiterated = 0;
|
||||
|
||||
if (offset >= count) offset = 0;
|
||||
|
||||
for (int i = 0; i < m_searchResults.Count; i++)
|
||||
{
|
||||
if (offset > 0 && preiterated < offset)
|
||||
{
|
||||
preiterated++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i - offset > CppExplorer.ArrayLimit - 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var obj = m_searchResults[i];
|
||||
|
||||
bool _ = false;
|
||||
int __ = 0;
|
||||
UIStyles.DrawValue(ref obj, ref _, ref __, _temprect);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_searchResults.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchBox()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
|
||||
// ----- GameObject Search -----
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<b><color=orange>Search</color></b>", null);
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
var resultinput = m_limit.ToString();
|
||||
resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
|
||||
if (int.TryParse(resultinput, out int _i) && _i > 0)
|
||||
{
|
||||
m_limit = _i;
|
||||
}
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Label("Class Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
ClassFilterToggle(TypeFilter.Object, "Object");
|
||||
ClassFilterToggle(TypeFilter.GameObject, "GameObject");
|
||||
ClassFilterToggle(TypeFilter.Component, "Component");
|
||||
ClassFilterToggle(TypeFilter.Custom, "Custom");
|
||||
GUILayout.EndHorizontal();
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("Custom Class:", new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
m_typeInput = GUILayout.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Scene Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
SceneFilterToggle(SceneFilter.Any, "Any", 60);
|
||||
SceneFilterToggle(SceneFilter.This, "This Scene", 100);
|
||||
SceneFilterToggle(SceneFilter.DontDestroy, "DontDestroyOnLoad", 140);
|
||||
SceneFilterToggle(SceneFilter.None, "No Scene", 80);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("<b><color=cyan>Search</color></b>", null))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ClassFilterToggle(TypeFilter mode, string label)
|
||||
{
|
||||
if (TypeMode == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
TypeMode = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
private void SceneFilterToggle(SceneFilter mode, string label, float width)
|
||||
{
|
||||
if (SceneMode == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
{
|
||||
SceneMode = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
|
||||
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
|
||||
|
||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
||||
public static IEnumerable<object> GetInstanceClassScanner()
|
||||
{
|
||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(x => !x.FullName.StartsWith("Mono"))
|
||||
.SelectMany(GetTypesSafe)
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
||||
|
||||
foreach (var type in query)
|
||||
{
|
||||
object obj = null;
|
||||
try
|
||||
{
|
||||
obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
if (obj != null && !obj.ToString().StartsWith("Mono"))
|
||||
{
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
|
||||
{
|
||||
try { return asm.GetTypes(); }
|
||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
||||
catch { return Enumerable.Empty<Type>(); }
|
||||
}
|
||||
|
||||
// ======= search functions =======
|
||||
|
||||
private void Search()
|
||||
{
|
||||
m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput);
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||
{
|
||||
Il2CppSystem.Type type = null;
|
||||
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
try
|
||||
{
|
||||
var findType = CppExplorer.GetType(_type);
|
||||
type = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
{
|
||||
type = Il2CppType.Of<Object>();
|
||||
}
|
||||
else if (TypeMode == TypeFilter.GameObject)
|
||||
{
|
||||
type = Il2CppType.Of<GameObject>();
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Component)
|
||||
{
|
||||
type = Il2CppType.Of<Component>();
|
||||
}
|
||||
|
||||
if (!Il2CppType.Of<Object>().IsAssignableFrom(type))
|
||||
{
|
||||
MelonLogger.LogError("Your Class Type must inherit from UnityEngine.Object! Leave blank to default to UnityEngine.Object");
|
||||
return new List<object>();
|
||||
}
|
||||
|
||||
var matches = new List<object>();
|
||||
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll(type))
|
||||
{
|
||||
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SceneMode != SceneFilter.Any)
|
||||
{
|
||||
if (SceneMode == SceneFilter.None)
|
||||
{
|
||||
if (!NoSceneFilter(obj, obj.GetType()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GameObject go;
|
||||
|
||||
var objtype = obj.GetType();
|
||||
if (objtype == typeof(GameObject))
|
||||
{
|
||||
go = obj as GameObject;
|
||||
}
|
||||
else if (typeof(Component).IsAssignableFrom(objtype))
|
||||
{
|
||||
go = (obj as Component).gameObject;
|
||||
}
|
||||
else { continue; }
|
||||
|
||||
if (!go) { continue; }
|
||||
|
||||
if (SceneMode == SceneFilter.This)
|
||||
{
|
||||
if (go.scene.name != CppExplorer.ActiveSceneName || go.scene.name == "DontDestroyOnLoad")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (SceneMode == SceneFilter.DontDestroy)
|
||||
{
|
||||
if (go.scene.name != "DontDestroyOnLoad")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matches.Contains(obj))
|
||||
{
|
||||
matches.Add(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public static bool ThisSceneFilter(object obj, Type type)
|
||||
{
|
||||
if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
var go = obj as GameObject ?? (obj as Component).gameObject;
|
||||
|
||||
if (go != null && go.scene.name == CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool DontDestroyFilter(object obj, Type type)
|
||||
{
|
||||
if (type == typeof(GameObject) || typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
var go = obj as GameObject ?? (obj as Component).gameObject;
|
||||
|
||||
if (go != null && go.scene.name == "DontDestroyOnLoad")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool NoSceneFilter(object obj, Type type)
|
||||
{
|
||||
if (type == typeof(GameObject))
|
||||
{
|
||||
var go = obj as GameObject;
|
||||
|
||||
if (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (typeof(Component).IsAssignableFrom(type))
|
||||
{
|
||||
var go = (obj as Component).gameObject;
|
||||
|
||||
if (go == null || (go.scene.name != CppExplorer.ActiveSceneName && go.scene.name != "DontDestroyOnLoad"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public abstract class WindowPage
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
public abstract void Init();
|
||||
|
||||
public abstract void DrawWindow();
|
||||
|
||||
public abstract void Update();
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Explorer;
|
||||
using MelonLoader;
|
||||
|
||||
[assembly: MelonInfo(typeof(CppExplorer), CppExplorer.NAME, CppExplorer.VERSION, CppExplorer.AUTHOR)]
|
||||
[assembly: MelonGame(null, null)]
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle(CppExplorer.NAME)]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany(CppExplorer.AUTHOR)]
|
||||
[assembly: AssemblyProduct(CppExplorer.NAME)]
|
||||
[assembly: AssemblyCopyright("")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("b21dbde3-5d6f-4726-93ab-cc3cc68bae7d")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
@ -1,502 +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();
|
||||
if (valueType.IsPrimitive || value.GetType() == typeof(string))
|
||||
{
|
||||
DrawPrimitive(ref value, rect, setTarget, setAction);
|
||||
}
|
||||
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
|
||||
{
|
||||
GameObject go;
|
||||
if (value.GetType() == typeof(Transform))
|
||||
{
|
||||
go = (value as Transform).gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
go = (value as 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//public static void DrawMember(ref object value, string valueType, string memberName, Rect rect, object setTarget = null, Action<object> setAction = null, float labelWidth = 180, bool autoSet = false)
|
||||
//{
|
||||
// GUILayout.Label("<color=cyan>" + memberName + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||
|
||||
// DrawValue(ref value, rect, valueType, memberName, setTarget, setAction, autoSet);
|
||||
//}
|
||||
|
||||
//public static void DrawValue(ref object value, Rect rect, string nullValueType = null, string memberName = null, object setTarget = null, Action<object> setAction = null, bool autoSet = false)
|
||||
//{
|
||||
// if (value == null)
|
||||
// {
|
||||
// GUILayout.Label("<i>null (" + nullValueType + ")</i>", null);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
|
||||
// }
|
||||
//}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,189 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Harmony;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class WindowManager
|
||||
{
|
||||
public static WindowManager Instance;
|
||||
|
||||
public static List<UIWindow> Windows = new List<UIWindow>();
|
||||
public static int CurrentWindowID { get; set; } = 500000;
|
||||
private static Rect m_lastWindowRect;
|
||||
|
||||
public WindowManager()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var window in Windows)
|
||||
{
|
||||
window.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
foreach (var window in Windows)
|
||||
{
|
||||
window.OnGUI();
|
||||
}
|
||||
}
|
||||
|
||||
// ========= Public Helpers =========
|
||||
|
||||
public static bool IsMouseInWindow
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!CppExplorer.ShowMenu)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var window in Windows)
|
||||
{
|
||||
if (RectContainsMouse(window.m_rect))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return RectContainsMouse(MainMenu.MainRect);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RectContainsMouse(Rect rect)
|
||||
{
|
||||
return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
|
||||
}
|
||||
|
||||
public static int NextWindowID()
|
||||
{
|
||||
return CurrentWindowID++;
|
||||
}
|
||||
|
||||
public static Rect GetNewWindowRect()
|
||||
{
|
||||
return GetNewWindowRect(ref m_lastWindowRect);
|
||||
}
|
||||
|
||||
public static Rect GetNewWindowRect(ref Rect lastRect)
|
||||
{
|
||||
Rect rect = new Rect(0, 0, 550, 700);
|
||||
|
||||
var mainrect = MainMenu.MainRect;
|
||||
if (mainrect.x <= (Screen.width - mainrect.width - 100))
|
||||
{
|
||||
rect = new Rect(mainrect.x + mainrect.width + 20, mainrect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
if (lastRect.x == rect.x)
|
||||
{
|
||||
rect = new Rect(rect.x + 25, rect.y + 25, rect.width, rect.height);
|
||||
}
|
||||
|
||||
lastRect = rect;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static UIWindow InspectObject(object obj, out bool createdNew)
|
||||
{
|
||||
createdNew = false;
|
||||
|
||||
foreach (var window in Windows)
|
||||
{
|
||||
if (obj == window.Target)
|
||||
{
|
||||
GUI.BringWindowToFront(window.windowID);
|
||||
GUI.FocusWindow(window.windowID);
|
||||
return window;
|
||||
}
|
||||
}
|
||||
|
||||
createdNew = true;
|
||||
if (obj is GameObject || obj is Transform)
|
||||
{
|
||||
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
return InspectReflection(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private static UIWindow InspectGameObject(GameObject obj)
|
||||
{
|
||||
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
|
||||
GUI.FocusWindow(new_window.windowID);
|
||||
|
||||
return new_window;
|
||||
}
|
||||
|
||||
public static UIWindow InspectReflection(object obj)
|
||||
{
|
||||
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
|
||||
GUI.FocusWindow(new_window.windowID);
|
||||
|
||||
return new_window;
|
||||
}
|
||||
|
||||
// ============= Resize Window Helper ============
|
||||
|
||||
// static readonly GUIContent gcDrag = new GUIContent("<->", "drag to resize");
|
||||
private static readonly GUIContent gcDrag = new GUIContent("<->");
|
||||
private static bool isResizing = false;
|
||||
private static Rect m_currentResize;
|
||||
private static int m_currentWindow;
|
||||
|
||||
public static Rect ResizeWindow(Rect _rect, int ID)
|
||||
{
|
||||
try
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(_rect.width - 35);
|
||||
|
||||
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Width(25), GUILayout.Height(25) });
|
||||
|
||||
var r = GUILayoutUtility.GetLastRect();
|
||||
|
||||
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
|
||||
|
||||
if (r.Contains(mouse) && Input.GetMouseButtonDown(0))
|
||||
{
|
||||
isResizing = true;
|
||||
m_currentWindow = ID;
|
||||
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
|
||||
}
|
||||
else if (!Input.GetMouseButton(0))
|
||||
{
|
||||
isResizing = false;
|
||||
}
|
||||
|
||||
if (isResizing && ID == m_currentWindow)
|
||||
{
|
||||
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
|
||||
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
|
||||
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
|
||||
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
catch { }
|
||||
|
||||
return _rect;
|
||||
}
|
||||
}
|
||||
}
|
@ -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