mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-04 04:22:53 +08:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
c5d889f9c7 | |||
ec39c68ffa | |||
0b78b4398f | |||
ab08d9dc96 | |||
ca172189be | |||
308966e664 | |||
0a08dbc495 | |||
13d54477a3 | |||
359f6e6f5c | |||
4f000c5494 | |||
e7866f56fe | |||
ab2770327e | |||
f568be2d3b | |||
3c374f3903 | |||
d5e22e8156 | |||
8bece453e3 | |||
692a721840 | |||
ef73cce92f | |||
2030b5309d | |||
142a75bbbb |
78
README.md
78
README.md
@ -2,48 +2,50 @@
|
|||||||
|
|
||||||
[]()
|
[]()
|
||||||
|
|
||||||
Universal Runtime Inspector/Explorer for Unity IL2CPP games.
|
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).
|
||||||
|
|
||||||
|
This was designed to be an IL2CPP-compatible equivalent to [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Scene exploration (traverse in the same way as the Unity Editor)
|
* Scene hierarchy explorer
|
||||||
* Inspect GameObjects/Transforms and manipulate them
|
* Search loaded assets with filters
|
||||||
* Inspect any object with Reflection, set primitive values, etc
|
* Traverse and manipulate GameObjects
|
||||||
* REPL Console for executing on-the-fly code
|
* Generic Reflection inspector
|
||||||
|
* REPL Console
|
||||||
|
* Inspect-under-mouse
|
||||||
|
|
||||||
|
## How to install
|
||||||
|
|
||||||
|
Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
|
||||||
|
|
||||||
|
1. Download <b>CppExplorer.zip</b> from [Releases](https://github.com/sinaioutlander/CppExplorer/releases).
|
||||||
|
2. Unzip the file into the `Mods` folder in your game's installation directory, created by MelonLoader.
|
||||||
|
3. Make sure it's not in a sub-folder, `CppExplorer.dll` and `mcs.dll` should be directly in the `Mods\` folder.
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
* Press F7 to show or hide the menu.
|
||||||
|
* Simply browse through the scene, search for objects, etc, it's pretty self-explanatory.
|
||||||
|
|
||||||
|
## Images
|
||||||
|
|
||||||
|
Scene explorer, and inspection of a MonoBehaviour object:
|
||||||
|
|
||||||
|
[](https://i.imgur.com/Yxizwcz.png)
|
||||||
|
|
||||||
|
Search feature:
|
||||||
|
|
||||||
|
[](https://i.imgur.com/F9ZfMvz.png)
|
||||||
|
|
||||||
|
|
||||||
|
REPL console:
|
||||||
|
|
||||||
|
[](https://i.imgur.com/14Dbtf8.png)
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
Written by Sinai.
|
Written by Sinai.
|
||||||
|
|
||||||
Credits to ManlyMarco for his [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and used the same MCS that he uses*.
|
Thanks to:
|
||||||
|
* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the UI style.
|
||||||
<i>* note: I commented out the `SkipVisibilityExt` constructor since it was causing an exception for some reason.</i>
|
* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor in `mcs.dll` since it was causing an exception with the Hook it attempted.
|
||||||
|
|
||||||
## How to install
|
|
||||||
|
|
||||||
This requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
|
|
||||||
|
|
||||||
1. Download <b>CppExplorer.dll</b> from the Releases folder.
|
|
||||||
2. Put the file in your `MyGame/Mods/` folder.
|
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
* Press F7 to show or hide the menu.
|
|
||||||
* Currently does <b>not</b> grant locked mouse or prevent clicking-through the menu, be careful of this.
|
|
||||||
* Simply browse through the scene, search for objects, etc, it's pretty self-explanatory.
|
|
||||||
|
|
||||||
If you have any specific questions about it you can contact me here, on NexusMods (Sinaioutlander), or on Discord (Sinai#4637, in MelonLoader discord).
|
|
||||||
|
|
||||||
## Images
|
|
||||||
|
|
||||||
Scene explorer, and inspection of a MonoBehaviour object.
|
|
||||||
|
|
||||||
[]()
|
|
||||||
|
|
||||||
Advanced search feature.
|
|
||||||
|
|
||||||
[]()
|
|
||||||
|
|
||||||
|
|
||||||
REPL console.
|
|
||||||
|
|
||||||
[]()
|
|
||||||
|
BIN
lib/mcs.dll
Normal file
BIN
lib/mcs.dll
Normal file
Binary file not shown.
@ -4,15 +4,8 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using Harmony;
|
|
||||||
using UnhollowerBaseLib.Runtime;
|
|
||||||
using UnhollowerRuntimeLib;
|
|
||||||
using UnhollowerBaseLib;
|
using UnhollowerBaseLib;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using UnhollowerBaseLib.Attributes;
|
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
@ -20,9 +13,9 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
// consts
|
// consts
|
||||||
|
|
||||||
public const string ID = "com.sinai.explorer";
|
public const string ID = "com.sinai.cppexplorer";
|
||||||
public const string NAME = "IL2CPP Runtime Explorer";
|
public const string NAME = "IL2CPP Runtime Explorer";
|
||||||
public const string VERSION = "0.91";
|
public const string VERSION = "1.1.0";
|
||||||
public const string AUTHOR = "Sinai";
|
public const string AUTHOR = "Sinai";
|
||||||
|
|
||||||
// fields
|
// fields
|
||||||
@ -65,32 +58,16 @@ namespace Explorer
|
|||||||
|
|
||||||
Instance = this;
|
Instance = this;
|
||||||
|
|
||||||
LoadMCS();
|
|
||||||
|
|
||||||
new MainMenu();
|
new MainMenu();
|
||||||
new WindowManager();
|
new WindowManager();
|
||||||
|
|
||||||
var harmony = HarmonyInstance.Create(ID);
|
//var harmony = HarmonyInstance.Create(ID);
|
||||||
harmony.PatchAll();
|
//harmony.PatchAll();
|
||||||
|
|
||||||
// done init
|
// done init
|
||||||
ShowMenu = true;
|
ShowMenu = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadMCS()
|
|
||||||
{
|
|
||||||
var mcsPath = @"Mods\mcs.dll";
|
|
||||||
if (File.Exists(mcsPath))
|
|
||||||
{
|
|
||||||
Assembly.Load(File.ReadAllBytes(mcsPath));
|
|
||||||
MelonLogger.Log("Loaded mcs.dll");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
MelonLogger.LogError("Could not find mcs.dll!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnLevelWasLoaded(int level)
|
public override void OnLevelWasLoaded(int level)
|
||||||
{
|
{
|
||||||
if (ScenePage.Instance != null)
|
if (ScenePage.Instance != null)
|
||||||
|
@ -44,10 +44,9 @@
|
|||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="mcs, Version=1.0.0.0, Culture=neutral, processorArchitecture=AMD64">
|
<Reference Include="mcs">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<HintPath>..\lib\mcs.dll</HintPath>
|
||||||
<HintPath>..\Release\mcs.dll</HintPath>
|
<Private>True</Private>
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MelonLoader.ModHandler">
|
<Reference Include="MelonLoader.ModHandler">
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
||||||
|
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
|
using Mono.CSharp;
|
||||||
using UnhollowerBaseLib;
|
using UnhollowerBaseLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@ -275,10 +276,11 @@ namespace Explorer
|
|||||||
|
|
||||||
public static bool IsList(Type t)
|
public static bool IsList(Type t)
|
||||||
{
|
{
|
||||||
return t.IsGenericType && t.GetGenericTypeDefinition().IsAssignableFrom(typeof(List<>));
|
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)
|
private void GetProperties(object m_object, List<string> names = null)
|
||||||
{
|
{
|
||||||
if (names == null)
|
if (names == null)
|
||||||
@ -292,6 +294,11 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
foreach (var pi in type.GetProperties(At.flags))
|
foreach (var pi in type.GetProperties(At.flags))
|
||||||
{
|
{
|
||||||
|
if (pi.Name == "Il2CppType")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (names.Contains(pi.Name))
|
if (names.Contains(pi.Name))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -382,15 +389,15 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
MelonLogger.Log("Exception on PropertyInfoHolder.UpdateValue, Name: " + this.propInfo.Name);
|
//MelonLogger.Log("Exception on PropertyInfoHolder.UpdateValue, Name: " + this.propInfo.Name);
|
||||||
MelonLogger.Log(e.GetType() + ", " + e.Message);
|
//MelonLogger.Log(e.GetType() + ", " + e.Message);
|
||||||
|
|
||||||
var inner = e.InnerException;
|
//var inner = e.InnerException;
|
||||||
while (inner != null)
|
//while (inner != null)
|
||||||
{
|
//{
|
||||||
MelonLogger.Log("inner: " + inner.GetType() + ", " + inner.Message);
|
// MelonLogger.Log("inner: " + inner.GetType() + ", " + inner.Message);
|
||||||
inner = inner.InnerException;
|
// inner = inner.InnerException;
|
||||||
}
|
//}
|
||||||
|
|
||||||
m_value = null;
|
m_value = null;
|
||||||
}
|
}
|
||||||
@ -402,7 +409,7 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
if (propInfo.PropertyType.IsEnum)
|
if (propInfo.PropertyType.IsEnum)
|
||||||
{
|
{
|
||||||
if (Enum.Parse(propInfo.PropertyType, m_value.ToString()) is object enumValue && enumValue != null)
|
if (System.Enum.Parse(propInfo.PropertyType, m_value.ToString()) is object enumValue && enumValue != null)
|
||||||
{
|
{
|
||||||
m_value = enumValue;
|
m_value = enumValue;
|
||||||
}
|
}
|
||||||
@ -451,7 +458,7 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
MelonLogger.Log("Exception trying to set property " + this.propInfo.Name);
|
//MelonLogger.Log("Exception trying to set property " + this.propInfo.Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -496,7 +503,7 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
if (fieldInfo.FieldType.IsEnum)
|
if (fieldInfo.FieldType.IsEnum)
|
||||||
{
|
{
|
||||||
if (Enum.Parse(fieldInfo.FieldType, m_value.ToString()) is object enumValue && enumValue != null)
|
if (System.Enum.Parse(fieldInfo.FieldType, m_value.ToString()) is object enumValue && enumValue != null)
|
||||||
{
|
{
|
||||||
m_value = enumValue;
|
m_value = enumValue;
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,12 @@ using MelonLoader;
|
|||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
// set of attributes. Change these attribute values to modify the information
|
// set of attributes. Change these attribute values to modify the information
|
||||||
// associated with an assembly.
|
// associated with an assembly.
|
||||||
[assembly: AssemblyTitle("CppExplorer")]
|
[assembly: AssemblyTitle(CppExplorer.NAME)]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany(CppExplorer.AUTHOR)]
|
||||||
[assembly: AssemblyProduct("CppExplorer")]
|
[assembly: AssemblyProduct(CppExplorer.NAME)]
|
||||||
[assembly: AssemblyCopyright("By Sinai")]
|
[assembly: AssemblyCopyright("")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
@ -2,7 +2,12 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Il2CppSystem.Collections;
|
||||||
|
using Il2CppSystem.Reflection;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
@ -234,39 +239,49 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.Label(value.ToString(), null);
|
GUILayout.Label(value.ToString(), null);
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (valueType.IsArray || ReflectionWindow.IsList(valueType))
|
else if (value is System.Collections.IEnumerable || ReflectionWindow.IsList(valueType))
|
||||||
{
|
{
|
||||||
object[] m_array;
|
System.Collections.IEnumerable enumerable;
|
||||||
if (valueType.IsArray)
|
|
||||||
|
if (value is System.Collections.IEnumerable isEnumerable)
|
||||||
{
|
{
|
||||||
m_array = (value as Array).Cast<object>().ToArray();
|
enumerable = isEnumerable;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_array = (value as IEnumerable).Cast<object>().ToArray();
|
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;
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
if (GUILayout.Button("<color=yellow>[" + m_array.Length + "] " + valueType + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
string btnLabel = "<color=yellow>[" + count + "] " + valueType + "</color>";
|
||||||
|
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
||||||
{
|
{
|
||||||
WindowManager.InspectObject(value, out bool _);
|
WindowManager.InspectObject(value, out bool _);
|
||||||
}
|
}
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
for (int i = 0; i < m_array.Length; i++)
|
var enumerator = enumerable.GetEnumerator();
|
||||||
|
if (enumerator != null)
|
||||||
{
|
{
|
||||||
var obj = m_array[i];
|
int i = 0;
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
var obj = enumerator.Current;
|
||||||
|
|
||||||
// collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
GUILayout.Space(190);
|
GUILayout.Space(190);
|
||||||
|
|
||||||
if (i > CppExplorer.ArrayLimit)
|
if (i > CppExplorer.ArrayLimit)
|
||||||
{
|
{
|
||||||
GUILayout.Label($"<i><color=red>{m_array.Length - CppExplorer.ArrayLimit} results omitted, array is too long!</color></i>", null);
|
GUILayout.Label($"<i><color=red>{count - CppExplorer.ArrayLimit} results omitted, array is too long!</color></i>", null);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,7 +292,26 @@ namespace Explorer
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var type = obj.GetType();
|
var type = obj.GetType();
|
||||||
DrawMember(ref obj, type.ToString(), i.ToString(), rect, setTarget, setAction, 25, true);
|
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++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,6 +323,30 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
label = (value as Object).name;
|
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;
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
||||||
|
Reference in New Issue
Block a user