Compare commits

...

32 Commits
1.2 ... 1.4.2

Author SHA1 Message Date
45b5ce0ef8 1.4.2
* Fixed a bug on the Reflection window which would prevent primitive values from being applied
* Improved some parts of the Scene Explorer and the Reflection Window interfaces
* Scene Explorer now has "page view" like other lists
* Various minor cleanups and refactorings
2020-08-24 01:42:19 +10:00
e3d1add090 Update README.md 2020-08-23 03:00:19 +10:00
a59bcc95e4 Update README.md 2020-08-23 03:00:12 +10:00
ac4414ca86 Update README.md 2020-08-23 02:50:59 +10:00
19263092fe Update CacheObject.cs 2020-08-22 19:36:06 +10:00
6bafab785b 1.4.1
* Cleanup some small bugs introduced in 1.4.0
* Added better exception handling for failed Reflection, and the ability to hide failed reflection members in the Reflection window, as well as see the error type.
* Reflection window members now display the full name instead of just the member name (eg. "Camera.main" instead of just "main").
2020-08-22 17:17:11 +10:00
62b1688d53 Update README.md 2020-08-22 01:42:29 +10:00
4d015cbe93 Update README.md 2020-08-22 01:08:09 +10:00
0da8f4faea Update README.md 2020-08-22 01:02:04 +10:00
b264151c46 1.4.0
- Wrote the CacheObject class to replace MemberInfoHolder, resulting code is better perfomance and much easier to read.
- Added pages to Object Reflection window, now limited to 20 members per page to improve performance further.
2020-08-22 00:16:05 +10:00
3d2bc7cd4b Update README.md 2020-08-20 18:59:59 +10:00
85c26e6af7 Update README.md 2020-08-20 18:56:07 +10:00
b149efa234 Update README.md 2020-08-20 18:53:56 +10:00
72c222d59a 1.3.5
- Project structure cleanup
- Code refactoring
- Adding some extension methods for cleaner looking code
- Performance improvements on Object Reflection window
2020-08-18 17:38:09 +10:00
153ad2268b Cleanup project structure
Restructured the project somewhat and cleaned up classes so that things are where they belong. Created "Helpers" folder and put appropriate helper classes in there.

Important things:
- The "GameObject path" methods are now extension methods on UnityEngine.Transform
- Removed AccessTools (Reflection helpers) as there was no use of it. Replaced with ReflectionHelpers class.
- Some improvements to the "Object Reflection" window, should be a bit faster now. Code cleaned up significantly.
2020-08-18 17:11:58 +10:00
1ba9b2eae1 Update README.md 2020-08-15 16:08:56 +10:00
be2da96cc0 Update README.md 2020-08-15 16:07:20 +10:00
a2405d69c5 Update README.md 2020-08-14 17:51:09 +10:00
b2a90c832f 1.3.3
Fix scene change buttons not actually working properly.
2020-08-14 16:19:39 +10:00
9a784fd467 Update README.md 2020-08-13 23:42:31 +10:00
d399b6acd1 Merging the two projects into one with build configurations
Removed the "src_2018" project, since it's now 1:1 with the main project and the only difference is references. Now using build configurations and preprocessor directives to accomplish the same thing.
2020-08-13 23:34:21 +10:00
0c3067973e 1.3.2 cleanup
- cleanup
- fixed a mistake with FieldInfos on reflection window, causing all values to be null.
- improved displaying of generic objects (now shows object Type after the name)
2020-08-13 18:43:34 +10:00
411593590d 1.3.2
- Fixed the Scene Filters on the search page, it was not functionally correctly at all.
- Fixed GameObjects and Components being displayed as basic objects on search view (they now open to GameObject inspector like they should).
2020-08-13 17:00:53 +10:00
a2677e2321 1.3.1
- Added ability to cycle through "pages" with lists/arrays and with search results. Also reduced default array display limit to 20 elements, since we can now just cycle through pages.
- Some backend restructuring / cleanups
2020-08-13 00:06:39 +10:00
13c2d6b92d 1.3.0
- various performance improvements
- by default, lists and arrays are now collapsed, use the "v" button to expand them.
- added error handling for a TypeLoadException which can happen with some generic types.
2020-08-12 18:26:57 +10:00
2f3bb80eeb Revert "Revert "1.31""
This reverts commit e58cf45e07.
2020-08-12 18:26:13 +10:00
e58cf45e07 Revert "1.31"
This reverts commit 7144b6a44c.
2020-08-12 18:25:52 +10:00
7144b6a44c 1.31
- various performance improvements
- by default, lists and arrays are now collapsed, use the "v" button to expand them.
- added error handling for a TypeLoadException which can happen with some generic types.
2020-08-12 18:25:33 +10:00
e8b17d3583 1.3
added buttons to change which of the active scenes you are inspecting
2020-08-12 05:34:34 +10:00
10ee2a837f release 2020-08-12 03:55:27 +10:00
5acc5a78d8 Merge branch 'master' of https://github.com/sinaioutlander/CppExplorer 2020-08-12 03:51:50 +10:00
2ba6f27a27 2018 support test 2020-08-12 03:51:47 +10:00
32 changed files with 2392 additions and 1790 deletions

View File

@ -1,17 +1,28 @@
# CppExplorer
# CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.6-green.svg)]()
[![Version](https://img.shields.io/badge/MelonLoader-0.2.6-green.svg)]()
<p align="center">
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.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)](https://i.imgur.com/Yxizwcz.png)
@ -38,7 +61,7 @@ Search feature:
[![](https://i.imgur.com/F9ZfMvz.png)](https://i.imgur.com/F9ZfMvz.png)
REPL console:
C# REPL console:
[![](https://i.imgur.com/14Dbtf8.png)](https://i.imgur.com/14Dbtf8.png)

View 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 CacheEnum : CacheObject
{
public Type EnumType;
public string[] EnumNames;
public override void Init()
{
EnumType = Value.GetType();
EnumNames = Enum.GetNames(EnumType);
}
public override void DrawValue(Rect window, float width)
{
if (CanWrite)
{
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);// + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
}
public void SetEnum(ref object value, int change)
{
var names = EnumNames.ToList();
int newindex = names.IndexOf(value.ToString()) + change;
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
{
value = Enum.Parse(EnumType, names[newindex]);
}
}
}
}

View File

@ -0,0 +1,46 @@
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 GameObj
{
get
{
if (m_gameObject == null)
{
if (Value is Il2CppSystem.Object ilObj)
{
var ilType = ilObj.GetIl2CppType();
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
{
m_gameObject = ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
}
}
}
return m_gameObject;
}
}
private GameObject m_gameObject;
public override void DrawValue(Rect window, float width)
{
UIHelpers.GameobjButton(GameObj, null, false, width);
}
public override void UpdateValue()
{
base.UpdateValue();
}
}
}

View File

@ -0,0 +1,164 @@
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 int ArrayLimit { get; set; } = 20;
public Type EntryType
{
get
{
if (m_entryType == null)
{
m_entryType = Value?.GetType().GetGenericArguments()[0];
}
return m_entryType;
}
set
{
m_entryType = value;
}
}
private Type m_entryType;
public IEnumerable Enumerable
{
get
{
if (m_enumerable == null && Value != null)
{
m_enumerable = Value as IEnumerable ?? CppListToEnumerable(Value);
}
return m_enumerable;
}
}
private IEnumerable m_enumerable;
private CacheObject[] m_cachedEntries;
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 > ArrayLimit)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(190);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
// prev/next page buttons
if (GUILayout.Button("< Prev", null))
{
if (ArrayOffset > 0) ArrayOffset--;
}
if (GUILayout.Button("Next >", null))
{
if (ArrayOffset < maxOffset) ArrayOffset++;
}
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.ArrayLimit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != ArrayLimit.ToString() && int.TryParse(limit, out int i))
{
ArrayLimit = i;
}
}
int offset = ArrayOffset * ArrayLimit;
if (offset >= count) offset = 0;
for (int i = offset; i < offset + 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);
}
}
}
}
/// <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, null, null, this.EntryType));
}
m_cachedEntries = list.ToArray();
}
}
}

View File

@ -0,0 +1,210 @@
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;
public bool CanWrite
{
get
{
if (MemberInfo is FieldInfo fi)
{
return !(fi.IsLiteral && !fi.IsInitOnly);
}
else if (MemberInfo is PropertyInfo pi)
{
return pi.CanWrite;
}
else
{
return false;
}
}
}
public ReflectionWindow.MemberInfoType MemberInfoType
{
get
{
if (MemberInfo is FieldInfo) return ReflectionWindow.MemberInfoType.Field;
if (MemberInfo is PropertyInfo) return ReflectionWindow.MemberInfoType.Property;
if (MemberInfo is MethodInfo) return ReflectionWindow.MemberInfoType.Method;
return ReflectionWindow.MemberInfoType.All;
}
}
// methods
public virtual void Init() { }
public abstract void DrawValue(Rect window, float width);
public 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)
{
var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
if (type == null)
{
MelonLogger.Log("Could not get type for object or memberinfo!");
return null;
}
return GetCacheObject(obj, memberInfo, declaringInstance, type);
}
/// <summary>
/// Gets the CacheObject subclass for an object or MemberInfo
/// </summary>
/// <param name="obj">The current value (can be null if memberInfo is not null)</param>
/// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param>
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param>
/// <param name="type">The type of the object or MemberInfo value.</param>
/// <returns></returns>
public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance, Type type)
{
CacheObject holder;
if ((obj is Il2CppSystem.Object || typeof(Il2CppSystem.Object).IsAssignableFrom(type))
&& (type.FullName.Contains("UnityEngine.GameObject") || type.FullName.Contains("UnityEngine.Transform")))
{
holder = new CacheGameObject();
}
else if (type.IsPrimitive || type == typeof(string))
{
holder = new CachePrimitive();
}
else if (type.IsEnum)
{
holder = new CacheEnum();
}
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) || ReflectionHelpers.IsList(type))
{
holder = new CacheList();
}
else
{
holder = new CacheOther();
}
holder.Value = obj;
holder.ValueType = type.FullName;
if (memberInfo != null)
{
holder.MemberInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType;
holder.DeclaringInstance = declaringInstance;
}
holder.UpdateValue();
holder.Init();
return holder;
}
public void Draw(Rect window, float labelWidth = 215f)
{
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 (" + 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()
{
try
{
if (MemberInfo.MemberType == MemberTypes.Field)
{
var fi = MemberInfo as FieldInfo;
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, Value);
}
else if (MemberInfo.MemberType == MemberTypes.Property)
{
var pi = MemberInfo as PropertyInfo;
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
}
}
catch (Exception e)
{
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
}
}
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public class CacheOther : CacheObject
{
private MethodInfo m_toStringMethod;
private bool m_triedToGetMethod;
public MethodInfo ToStringMethod
{
get
{
if (m_toStringMethod == null && !m_triedToGetMethod)
{
if (Value == null) return null;
m_triedToGetMethod = true;
try
{
var methods = ReflectionHelpers.GetActualType(Value)
.GetMethods(ReflectionHelpers.CommonFlags)
.Where(x => x.Name == "ToString")
.GetEnumerator();
while (methods.MoveNext())
{
// just get the first (top-most level) method, then break.
m_toStringMethod = methods.Current;
break;
}
}
catch { }
}
return m_toStringMethod;
}
}
public override void DrawValue(Rect window, float width)
{
string label = (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 + 40) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
}
}
}

View File

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

View File

@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using MelonLoader;
using UnhollowerBaseLib;
@ -14,41 +13,22 @@ namespace Explorer
// consts
public const string ID = "com.sinai.cppexplorer";
public const string NAME = "IL2CPP Runtime Explorer";
public const string VERSION = "1.1.0";
public const string VERSION = "1.4.2";
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; } = 100;
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
@ -61,20 +41,13 @@ namespace Explorer
new MainMenu();
new WindowManager();
//var harmony = HarmonyInstance.Create(ID);
//harmony.PatchAll();
// done init
ShowMenu = true;
}
public override void OnLevelWasLoaded(int level)
{
if (ScenePage.Instance != null)
{
ScenePage.Instance.OnSceneChange();
SearchPage.Instance.OnSceneChange();
}
ScenePage.Instance?.OnSceneChange();
SearchPage.Instance?.OnSceneChange();
}
public override void OnUpdate()
@ -86,46 +59,16 @@ namespace Explorer
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 = "";
InspectUnderMouse.Update();
}
}
@ -136,79 +79,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();
}
}
}

View File

@ -7,32 +7,34 @@
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>CppExplorer</RootNamespace>
<AssemblyName>CppExplorer</AssemblyName>
<RootNamespace>Explorer</RootNamespace>
<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>
@ -46,7 +48,7 @@
</Reference>
<Reference Include="mcs">
<HintPath>..\lib\mcs.dll</HintPath>
<Private>True</Private>
<Private>False</Private>
</Reference>
<Reference Include="MelonLoader.ModHandler">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
@ -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>
<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.ParticleSystemModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.ParticleSystemModule.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.Physics2DModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.Physics2DModule.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.PhysicsModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.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.TextRenderingModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.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.UI">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<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.UIElementsModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
<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.UIModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
<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="ILBehaviour.cs" />
<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="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="WindowManager.cs" />
<Compile Include="MainMenu\Pages\WindowPage.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>

View File

@ -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

View 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);
}
}
}

View 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;
}
}
}

View File

@ -0,0 +1,152 @@
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>();
public static Il2CppSystem.Type BehaviourType => Il2CppType.Of<Behaviour>();
private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
public static object Il2CppCast(object obj, Type castTo)
{
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) return obj;
var generic = m_tryCastMethodInfo.MakeGenericMethod(castTo);
return generic.Invoke(obj, null);
}
public static string ExceptionToString(Exception e)
{
if (IsFailedGeneric(e))
{
return "Unable to initialize this type.";
}
else if (IsObjectCollected(e))
{
return "Garbage collected in Il2Cpp.";
}
return e.GetType() + ", " + e.Message;
}
public static bool IsFailedGeneric(Exception e)
{
return IsExceptionOfType(e, typeof(TargetInvocationException)) && IsExceptionOfType(e, typeof(TypeLoadException));
}
public static bool IsObjectCollected(Exception e)
{
return IsExceptionOfType(e, typeof(ObjectCollectedException));
}
public static bool IsExceptionOfType(Exception e, Type t, bool strict = true, bool checkInner = true)
{
bool isType;
if (strict)
isType = e.GetType() == t;
else
isType = t.IsAssignableFrom(e.GetType());
if (isType) return true;
if (e.InnerException != null && checkInner)
return IsExceptionOfType(e.InnerException, t, strict);
else
return false;
}
public static bool IsList(Type t)
{
return t.IsGenericType
&& 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();
}
}
}

111
src/Helpers/UIHelpers.cs Normal file
View File

@ -0,0 +1,111 @@
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<Transform> 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<Transform> 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.transform);
}
else
{
WindowManager.InspectObject(obj, out bool _);
}
}
// ------ small "Inspect" button on the right ------
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUI.color = Color.white;
if (showSmallInspectBtn)
{
SmallInspectButton(obj);
}
GUILayout.EndHorizontal();
}
public static void SmallInspectButton(object obj)
{
if (GUILayout.Button("Inspect", null))
{
WindowManager.InspectObject(obj, out bool _);
}
}
}
}

100
src/Helpers/UIStyles.cs Normal file
View 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;
}
}
}

View 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;
}
}
}
}

View File

@ -1,33 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using UnhollowerRuntimeLib;
namespace Explorer
{
//public class ILBehaviour : MonoBehaviour
//{
// public ILBehaviour(IntPtr intPtr) : base(intPtr) { }
// public static T AddToGameObject<T>(GameObject _go) where T : ILBehaviour
// {
// Il2CppSystem.Type ilType = UnhollowerRuntimeLib.Il2CppType.Of<T>();
// if (ilType == null)
// {
// MelonLogger.Log("Error - could not get MB as ilType");
// return null;
// }
// var obj = typeof(T)
// .GetConstructor(new Type[] { typeof(IntPtr) })
// .Invoke(new object[] { _go.AddComponent(UnhollowerRuntimeLib.Il2CppType.Of<T>()).Pointer });
// return (T)obj;
// }
//}
}

View File

@ -1,552 +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 : WindowManager.UIWindow
{
public override Il2CppSystem.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;
}
m_objectType = type;
GetFields(m_object);
GetProperties(m_object);
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)
{
foreach (var pi in type.GetProperties(At.flags))
{
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);
}
}
}
/* *********************
* PROPERTYINFO HOLDER
*/
public class PropertyInfoHolder
{
public Type classType;
public PropertyInfo propInfo;
public object m_value;
public PropertyInfoHolder(Type _type, PropertyInfo _propInfo)
{
classType = _type;
propInfo = _propInfo;
}
public void Draw(ReflectionWindow window)
{
if (propInfo.CanWrite)
{
UIStyles.DrawMember(ref m_value, propInfo.PropertyType.Name, propInfo.Name, window.m_rect, window.m_object, SetValue);
}
else
{
UIStyles.DrawMember(ref m_value, propInfo.PropertyType.Name, propInfo.Name, window.m_rect, window.m_object);
}
}
public 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 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);
}
}
}
/* *********************
* FIELDINFO HOLDER
*/
public class FieldInfoHolder
{
public Type classType;
public FieldInfo fieldInfo;
public object m_value;
public FieldInfoHolder(Type _type, FieldInfo _fieldInfo)
{
classType = _type;
fieldInfo = _fieldInfo;
}
public void UpdateValue(object obj)
{
m_value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : obj);
}
public void Draw(ReflectionWindow window)
{
bool canSet = !(fieldInfo.IsLiteral && !fieldInfo.IsInitOnly);
if (canSet)
{
UIStyles.DrawMember(ref m_value, fieldInfo.FieldType.Name, fieldInfo.Name, window.m_rect, window.m_object, SetValue);
}
else
{
UIStyles.DrawMember(ref m_value, fieldInfo.FieldType.Name, fieldInfo.Name, window.m_rect, window.m_object);
}
}
public 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);
}
}
}
}

View 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;
}
}
}
}
}

View File

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

View File

@ -36,17 +36,17 @@ namespace Explorer
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("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("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>()

View File

@ -21,14 +21,14 @@ namespace Explorer
return FindObjectsOfType<T>();
}
public object RunCoroutine(IEnumerator enumerator)
{
return MelonCoroutines.Start(enumerator);
}
//public object RunCoroutine(IEnumerator enumerator)
//{
// return MelonCoroutines.Start(enumerator);
//}
public void EndCoroutine(Coroutine c)
{
StopCoroutine(c);
}
//public void EndCoroutine(Coroutine c)
//{
// StopCoroutine(c);
//}
}
}

View File

@ -12,9 +12,9 @@ using MelonLoader;
namespace Explorer
{
public class ConsolePage : MainMenu.WindowPage
public class ConsolePage : WindowPage
{
public override string Name { get => "Console"; set => base.Name = value; }
public override string Name { get => "C# Console"; set => base.Name = value; }
private ScriptEvaluator _evaluator;
private readonly StringBuilder _sb = new StringBuilder();
@ -41,13 +41,18 @@ namespace Explorer
try
{
MethodInput = @"// This is a basic REPL console used to execute a method.
// Some common directives are added by default, you can add more below.
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)
{
@ -65,11 +70,6 @@ MelonLogger.Log(""hello world"");";
_evaluator = new ScriptEvaluator(new StringWriter(_sb)) { InteractiveBaseClass = typeof(REPL) };
UsingDirectives = new List<string>();
UsingDirectives.AddRange(m_defaultUsing);
foreach (string asm in UsingDirectives)
{
Evaluate(AsmToUsing(asm));
}
}
public string AsmToUsing(string asm, bool richtext = false)
@ -111,7 +111,7 @@ MelonLogger.Log(""hello world"");";
public override void DrawWindow()
{
GUILayout.Label("<b><size=15><color=cyan>REPL Console</color></size></b>", null);
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) });
@ -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();
}

View File

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

View File

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -12,15 +13,20 @@ using UnhollowerBaseLib;
namespace Explorer
{
public class SearchPage : MainMenu.WindowPage
public class SearchPage : WindowPage
{
public static SearchPage Instance;
public override string Name { get => "Advanced Search"; set => base.Name = value; }
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_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;
@ -41,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;
@ -52,12 +55,31 @@ namespace Explorer
public void OnSceneChange()
{
m_searchResults.Clear();
m_pageOffset = 0;
}
public override void Update()
{
}
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
@ -67,7 +89,9 @@ 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();
@ -78,20 +102,43 @@ namespace Explorer
GUILayout.BeginVertical(GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label("<b><color=orange>Results</color></b>", null);
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 > this.m_limit)
{
// prev/next page buttons
GUILayout.BeginHorizontal(null);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 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();
}
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)
{
for (int i = 0; i < m_searchResults.Count; i++)
{
var obj = m_searchResults[i];
int offset = m_pageOffset * this.m_limit;
if (offset >= count) m_pageOffset = 0;
UIStyles.DrawValue(ref obj, _temprect);
for (int i = offset; i < offset + m_limit && i < count; i++)
{
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
//m_searchResults[i].DrawValue(MainMenu.MainRect);
}
}
else
@ -100,7 +147,6 @@ namespace Explorer
}
GUILayout.EndScrollView();
GUILayout.EndVertical();
}
catch
@ -124,7 +170,7 @@ namespace Explorer
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("Result limit:", new GUILayoutOption[] { GUILayout.Width(100) });
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)
@ -204,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()
@ -244,191 +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);
MelonLogger.Log("Got type: " + type.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>();
int added = 0;
//MelonLogger.Log("Trying to get IL Type. ASM name: " + type.Assembly.GetName().Name + ", Namespace: " + type.Namespace + ", name: " + type.Name);
//var asmName = type.Assembly.GetName().Name;
//if (asmName.Contains("UnityEngine"))
//{
// asmName = "UnityEngine";
//}
//var intPtr = IL2CPP.GetIl2CppClass(asmName, type.Namespace, type.Name);
//var ilType = Il2CppType.TypeFromPointer(intPtr);
foreach (var obj in Resources.FindObjectsOfTypeAll(type))
{
if (added == m_limit)
{
break;
}
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);
added++;
}
}
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;
}
}
}
}

View File

@ -0,0 +1,22 @@
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();
}
}

View File

@ -1,415 +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 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)
{
if (obj == null)
{
GUILayout.Label("<i><color=red>null</color></i>", null);
return;
}
bool enabled = obj.activeSelf;
bool children = obj.transform.childCount > 0;
GUILayout.BeginHorizontal(null);
GUI.skin.button.alignment = TextAnchor.UpperLeft;
// ------ build name ------
string label = children ? "[" + obj.transform.childCount + " children] " : "";
label += obj.name;
// ------ Color -------
if (enabled)
{
if (children)
{
GUI.color = Color.green;
}
else
{
GUI.color = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f);
}
}
else
{
GUI.color = Color.red;
}
// ------ toggle active button ------
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, 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
{
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);
}
UIStyles.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();
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = "<color=yellow>[" + count + "] " + valueType + "</color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
{
WindowManager.InspectObject(value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
var enumerator = enumerable.GetEnumerator();
if (enumerator != null)
{
int i = 0;
while (enumerator.MoveNext())
{
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)
{
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 + ": <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;
}
}
}
// 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]);
}
}
}
}

View File

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

View File

@ -0,0 +1,355 @@
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 = false;
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;
try
{
var name = member.DeclaringType.Name + "." + member.Name;
if (names.Contains(name)) continue;
names.Add(name);
var cached = CacheObject.GetCacheObject(null, member, target);
if (cached != null)
{
list.Add(cached);
cached.ReflectionException = exception;
}
}
catch (Exception e)
{
MelonLogger.Log("Exception caching member!");
MelonLogger.Log(e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
}
}
}
}
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.FullName + "</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--;
scroll = Vector2.zero;
}
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
if (GUILayout.Button("Next >", null))
{
if (m_pageOffset < maxOffset) m_pageOffset++;
scroll = Vector2.zero;
}
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;
}
}
}

82
src/Windows/UIWindow.cs Normal file
View File

@ -0,0 +1,82 @@
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 abstract void Init();
public abstract void WindowFunction(int windowID);
public abstract void Update();
public static UIWindow CreateWindow<T>(object target) where T : UIWindow
{
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}");
}
}
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;
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -105,7 +104,7 @@ namespace Explorer
foreach (var window in Windows)
{
if (obj == window.Target)
if (ReferenceEquals(obj, window.Target))
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
@ -142,117 +141,49 @@ namespace Explorer
// ============= Resize Window Helper ============
static readonly GUIContent gcDrag = new GUIContent("<->", "drag to resize");
// 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)
{
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))
try
{
isResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
else if (!Input.GetMouseButton(0))
{
isResizing = false;
}
GUILayout.BeginHorizontal(null);
GUILayout.Space(_rect.width - 35);
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.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Width(25), GUILayout.Height(25) });
GUILayout.EndHorizontal();
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;
}
// ============ GENERATED WINDOW HOLDER ============
public abstract class UIWindow
{
public abstract Il2CppSystem.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 component = Activator.CreateInstance<T>();
component.Target = target;
component.windowID = NextWindowID();
component.m_rect = GetNewWindowRect();
Windows.Add(component);
component.Init();
return component;
}
public void DestroyWindow()
{
try
{
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;
}
}
}
}
}

View File

@ -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;
}
}
}