Compare commits

...

56 Commits
1.2 ... 1.5.3

Author SHA1 Message Date
5de771389e 1.5.3
* Added exception handling for scrollview when unstripping fails
* Added some better logging in some places
2020-09-04 01:27:44 +10:00
51cfbe524e Update README.md 2020-09-03 20:59:54 +10:00
217b93ef4f 1.5.2
* Added ability to force Reflection Inspector for GameObjects and Transforms if you hold Left Shift while clicking the Inspect button
* Fixed a bug causing duplicate windows to open when you inspect Transforms, the current active window will now be focused. Note: does not apply if you hold Left Shift for forced reflection.
2020-09-03 20:58:04 +10:00
42156e1160 1.5.2
* Added page view to GameObject Children/Component lists
* Made a generic Page Handler helper class, replaced all page view implementations with the helper (no real change for users but should make things easier to maintain in the future, and they were basically all copy+pastes).
2020-09-03 19:48:50 +10:00
e7208d0c9d 1.5.1 2020-09-01 18:04:38 +10:00
2f3b779199 1.5.1
* Added support for Properties with an index parameter on the Reflection Window (ie. "this[index]")
* Fixed a crash that occured when inspecting Il2CppSystem.Type objects
* Back-end cleanups
2020-09-01 18:03:44 +10:00
916bdea59b 1.5.0 2020-08-31 23:28:44 +10:00
d8688193d5 1.4.7
* Added support for Il2Cpp IList objects
* Improved support for Lists in general, they should now work better.
2020-08-31 18:23:19 +10:00
30b48b1f1f 1.4.6
* Fix a bug with the Scene Explorer Search feature (not Object search)
* Simplified parsing of primitive values to a better method
2020-08-31 16:27:14 +10:00
0fd382c1f6 1.4.5 finalize and release 2020-08-30 23:29:37 +10:00
fd20a1120b Merge branch 'master' of https://github.com/sinai-dev/CppExplorer 2020-08-30 17:32:28 +10:00
abcb548706 1.4.5 finalize
Will be pushed when MelonLoader releases 0.2.7.
2020-08-30 17:32:25 +10:00
b056644385 Update CacheMethod.cs 2020-08-30 16:55:14 +10:00
71f72e8f36 Update README.md 2020-08-30 16:51:03 +10:00
1ab41f5a30 Update README.md 2020-08-30 16:49:44 +10:00
7dc58ea02c 1.4.5 (pre-release)
* Added support for MethodInfos with only primitive arguments on reflection window
* Added backup resize mode incase resizing experiences an exception
2020-08-30 07:01:13 +10:00
68eeee353e 1.4.5 (pre-release) - Implement Tab View
* Implemented Tab View
2020-08-30 01:08:48 +10:00
92fe1dc704 1.4.5 (pre-release)
* Added MethodInfo support for basic methods with no arguments.
* Added support for missing primitive types (char, short, byte)
* Added CacheDictionary class (currently unsupported)
* Cleaned up some stuff, using System.Reflection.MemberType instead of a custom enum.
2020-08-29 21:15:54 +10:00
6e644b4f50 1.4.5 (pre-release)
* Windows now display the gameobject name or the object type in the header
* Added "Set DontDestroyOnLoad" button to Gameobject controls.
* Added dynamic input field size and more intelligent auto-wrap for primitive values
* Resize Drag will now disable itself on Exceptions, and log the error (affects VRChat)
* Various misc UI improvements
* Various small fixes
2020-08-28 00:45:34 +10:00
c47974115b WindowManager.cs, restore 2 lines accidentally deleted 2020-08-27 20:15:38 +10:00
535e88be9a 1.4.5 (pre-release)
* Pre-release. Will be released once MelonLoader bumps to Unhollower 0.4.9.0
* Added global "Force Unlock Mouse" option, should work on almost all games. Has smart behaviour and will maintain the previous value (or the value which should be set).
* Improve performacne of CacheList casting List ->IEnumerable
* Fix a bug causing some Components to not show the GameObject button in the Reflection Window (top-right corner).
* Fix a bug making the Window Manager think that two of the same Il2Cpp Object are not ReferenceEquals.
* Added logging when C# Console fails to compile anything
* Improve display of Reflection Window member name label, now expands with window resize.
2020-08-27 18:05:55 +10:00
e567c16221 Update README.md 2020-08-24 01:53:59 +10:00
d13af7548e Update README.md 2020-08-24 01:52:19 +10:00
5d750aec77 Update README.md 2020-08-24 01:50:03 +10:00
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
38 changed files with 3953 additions and 2104 deletions

View File

@ -1,17 +1,30 @@
# CppExplorer
# CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.7.1-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>
### Known issue
Some games are experiencing `MissingMethodException`s or exceptions about failed unstripping, which prevent the CppExplorer menu from showing properly or at all. This is a bug with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower) and there isn't much I can do about it myself.
If you're familiar with C# and Unity, one possibility for now is making a fork of this repo and manually fixing all the broken methods to ones which aren't broken (if possible). There may be another overload of the same method which wasn't stripped or was unstripped successfully, which you can use instead.
## 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
@ -25,22 +38,65 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
## 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.
* Simply browse through the scene, search for objects, etc, most of it is pretty self-explanatory.
## Images
### Scene Explorer
Scene explorer, and inspection of a MonoBehaviour object:
* A simple menu which allows you to traverse the Transform heirarchy of the scene.
* Click on a GameObject to set it as the current path, or <b>Inspect</b> it to send it to an Inspector Window.
[![](https://i.imgur.com/Yxizwcz.png)](https://i.imgur.com/Yxizwcz.png)
[![](https://i.imgur.com/2b0q0jL.png)](https://i.imgur.com/2b0q0jL.png)
Search feature:
### Inspectors
[![](https://i.imgur.com/F9ZfMvz.png)](https://i.imgur.com/F9ZfMvz.png)
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
<b>Tips:</b>
* When in Tab View, GameObjects are denoted by a [G] prefix, and Reflection objects are denoted by a [R] prefix.
* Hold <b>Left Shift</b> when you click the Inspect button to force Reflection mode for GameObjects and Transforms.
REPL console:
### GameObject Inspector
[![](https://i.imgur.com/14Dbtf8.png)](https://i.imgur.com/14Dbtf8.png)
* Allows you to see the children and components on a GameObject.
* Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
[![](https://i.imgur.com/JTxqlx4.png)](https://i.imgur.com/JTxqlx4.png)
### Reflection Inspector
* The Reflection Inspector is used for all other supported objects.
* Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods.
* Can search and filter members for the ones you are interested in.
[![](https://i.imgur.com/iq92m0l.png)](https://i.imgur.com/iq92m0l.png)
### Object Search
* You can search for an `UnityEngine.Object` with the Object Search feature.
* Filter by name, type, etc.
* For GameObjects and Transforms you can filter which scene they are found in too.
[![](https://i.imgur.com/lK2RthM.png)](https://i.imgur.com/lK2RthM.png)
### C# REPL console
* A simple C# REPL console, allows you to execute a method body on the fly.
[![](https://i.imgur.com/5U4D1a8.png)](https://i.imgur.com/5U4D1a8.png)
### Inspect-under-mouse
* Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse.
* Hover over your desired object, if you see the name appear then you can click on it to inspect it.
* Only objects with Colliders are supported.
### Mouse Control
CppExplorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). However, you may also want to prevent the mouse clicking-through onto the game behind CppExplorer, this is possible but it requires specific patches for that game.
* For VRChat, use [VRCExplorerMouseControl](https://github.com/sinai-dev/VRCExplorerMouseControl)
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
* You can create your own plugin using one of the two plugins above as an example. Usually only a few simple Harmony patches are needed to fix the problem.
## Credits

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheDictionary : CacheObjectBase
{
public override void Init()
{
//base.Init();
Value = "Unsupported";
}
public override void UpdateValue()
{
//base.UpdateValue();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label("<color=red>Dictionary (unsupported)</color>", null);
}
}
}

View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheEnum : CacheObjectBase
{
public Type EnumType;
public string[] EnumNames;
public override void Init()
{
try
{
EnumType = Value.GetType();
}
catch
{
EnumType = (MemInfo as FieldInfo)?.FieldType ?? (MemInfo as PropertyInfo).PropertyType;
}
if (EnumType != null)
{
EnumNames = Enum.GetNames(EnumType);
}
else
{
ReflectionException = "Unknown, could not get Enum names.";
}
}
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,23 @@
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 : CacheObjectBase
{
public override void DrawValue(Rect window, float width)
{
UIHelpers.GameobjButton(Value, null, false, width);
}
public override void UpdateValue()
{
base.UpdateValue();
}
}
}

View File

@ -0,0 +1,348 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheList : CacheObjectBase
{
public bool IsExpanded { get; set; }
public PageHelper Pages = new PageHelper();
public float WhiteSpace = 215f;
public float ButtonWidthOffset = 290f;
private CacheObjectBase[] m_cachedEntries;
// Type of Entries in the Array
public Type EntryType
{
get => GetEntryType();
set => m_entryType = value;
}
private Type m_entryType;
// Cached IEnumerable object
public IEnumerable Enumerable
{
get => GetEnumerable();
}
private IEnumerable m_enumerable;
// Generic Type Definition for Lists
public Type GenericTypeDef
{
get => GetGenericTypeDef();
}
private Type m_genericTypeDef;
// Cached ToArray method for Lists
public MethodInfo GenericToArrayMethod
{
get => GetGenericToArrayMethod();
}
private MethodInfo m_genericToArray;
// Cached Item Property for ILists
public PropertyInfo ItemProperty
{
get => GetItemProperty();
}
private PropertyInfo m_itemProperty;
// ========== Methods ==========
private IEnumerable GetEnumerable()
{
if (m_enumerable == null && Value != null)
{
m_enumerable = Value as IEnumerable ?? GetEnumerableFromIl2CppList();
}
return m_enumerable;
}
private Type GetGenericTypeDef()
{
if (m_genericTypeDef == null && Value != null)
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_genericTypeDef = type.GetGenericTypeDefinition();
}
}
return m_genericTypeDef;
}
private MethodInfo GetGenericToArrayMethod()
{
if (GenericTypeDef == null) return null;
if (m_genericToArray == null)
{
m_genericToArray = GenericTypeDef
.MakeGenericType(new Type[] { this.EntryType })
.GetMethod("ToArray");
}
return m_genericToArray;
}
private PropertyInfo GetItemProperty()
{
if (m_itemProperty == null)
{
m_itemProperty = Value?.GetType().GetProperty("Item");
}
return m_itemProperty;
}
private IEnumerable GetEnumerableFromIl2CppList()
{
if (Value == null) return null;
if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>))
{
return (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]);
}
else
{
return ConvertIListToMono();
}
}
private IList ConvertIListToMono()
{
try
{
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.EntryType });
var list = (IList)Activator.CreateInstance(genericType);
for (int i = 0; ; i++)
{
try
{
var itm = ItemProperty.GetValue(Value, new object[] { i });
list.Add(itm);
}
catch { break; }
}
return list;
}
catch (Exception e)
{
MelonLogger.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
return null;
}
}
private Type GetEntryType()
{
if (m_entryType == null)
{
if (this.MemInfo != null)
{
Type memberType = null;
switch (this.MemInfo.MemberType)
{
case MemberTypes.Field:
memberType = (MemInfo as FieldInfo).FieldType;
break;
case MemberTypes.Property:
memberType = (MemInfo as PropertyInfo).PropertyType;
break;
}
if (memberType != null && memberType.IsGenericType)
{
m_entryType = memberType.GetGenericArguments()[0];
}
}
else if (Value != null)
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_entryType = type.GetGenericArguments()[0];
}
}
}
// IList probably won't be able to get any EntryType.
if (m_entryType == null)
{
m_entryType = typeof(object);
}
return m_entryType;
}
public override void UpdateValue()
{
base.UpdateValue();
if (Value == null || Enumerable == null)
{
return;
}
var enumerator = Enumerable.GetEnumerator();
if (enumerator == null)
{
return;
}
var list = new List<CacheObjectBase>();
while (enumerator.MoveNext())
{
var obj = enumerator.Current;
if (obj != null && ReflectionHelpers.GetActualType(obj) is Type t)
{
if (obj is Il2CppSystem.Object iObj)
{
try
{
var cast = iObj.Il2CppCast(t);
if (cast != null)
{
obj = cast;
}
}
catch { }
}
if (GetCacheObject(obj, t) is CacheObjectBase cached)
{
list.Add(cached);
}
else
{
list.Add(null);
}
}
else
{
list.Add(null);
}
}
m_cachedEntries = list.ToArray();
}
// ============= GUI Draw =============
public override void DrawValue(Rect window, float width)
{
if (m_cachedEntries == null)
{
GUILayout.Label("m_cachedEntries is null!", null);
return;
}
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 - ButtonWidthOffset) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUILayout.Space(5);
if (IsExpanded)
{
float whitespace = WhiteSpace;
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
Pages.Count = count;
if (count > Pages.PageLimit)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
//int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
Pages.CalculateMaxOffset();
//GUILayout.Label($"Page {PH.ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
Pages.CurrentPageLabel();
// prev/next page buttons
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Left);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Right);
}
Pages.DrawLimitInputArea();
GUILayout.Space(5);
}
//int offset = ArrayOffset * ArrayLimit;
//if (offset >= count)
//{
// offset = 0;
// ArrayOffset = 0;
//}
int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.PageLimit && 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(whitespace);
if (entry == null || entry.Value == null)
{
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
}
else
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
entry.DrawValue(window, window.width - (whitespace + 85));
}
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
}
}
}

View File

@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
using MelonLoader;
namespace Explorer
{
public class CacheMethod : CacheObjectBase
{
private bool m_evaluated = false;
private CacheObjectBase m_cachedReturnValue;
private bool m_isEvaluating;
private ParameterInfo[] m_arguments;
private string[] m_argumentInput;
public bool HasParameters
{
get
{
if (m_hasParams == null)
{
m_hasParams = (MemInfo as MethodInfo).GetParameters().Length > 0;
}
return (bool)m_hasParams;
}
}
private bool? m_hasParams;
public static bool CanEvaluate(MethodInfo mi)
{
// generic type args not supported yet
if (mi.GetGenericArguments().Length > 0)
{
return false;
}
// only primitive and string args supported
foreach (var param in mi.GetParameters())
{
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
{
return false;
}
}
return true;
}
public override void Init()
{
base.Init();
var mi = MemInfo as MethodInfo;
m_arguments = mi.GetParameters();
m_argumentInput = new string[m_arguments.Length];
}
public override void UpdateValue()
{
//base.UpdateValue();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.BeginVertical(null);
string evaluateLabel = "<color=lime>Evaluate</color>";
if (HasParameters)
{
if (m_isEvaluating)
{
for (int i = 0; i < m_arguments.Length; i++)
{
var name = m_arguments[i].Name;
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType.Name;
GUILayout.BeginHorizontal(null);
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUILayout.Label(i + ": <color=cyan>" + name + "</color> <color=yellow>(" + type + ")</color>", null);
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(null);
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
{
Evaluate();
m_isEvaluating = false;
}
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
{
m_isEvaluating = false;
}
}
else
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
{
m_isEvaluating = true;
}
}
}
else
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
{
Evaluate();
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (m_evaluated)
{
if (m_cachedReturnValue != null)
{
try
{
m_cachedReturnValue.DrawValue(window, width);
}
catch (Exception e)
{
MelonLogger.Log("Exception drawing m_cachedReturnValue!");
MelonLogger.Log(e.ToString());
}
}
else
{
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
}
}
else
{
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueTypeName}</color>)", null);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
private void Evaluate()
{
var mi = MemInfo as MethodInfo;
object ret = null;
if (!HasParameters)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
m_evaluated = true;
}
else
{
var arguments = new List<object>();
for (int i = 0; i < m_arguments.Length; i++)
{
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType;
if (type == typeof(string))
{
arguments.Add(input);
}
else
{
try
{
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
{
arguments.Add(parsed);
}
else
{
throw new Exception();
}
}
catch
{
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
break;
}
}
}
if (arguments.Count == m_arguments.Length)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, arguments.ToArray());
m_evaluated = true;
}
else
{
MelonLogger.Log($"Did not invoke because {m_arguments.Length - arguments.Count} arguments could not be parsed!");
}
}
if (ret != null)
{
m_cachedReturnValue = GetCacheObject(ret);
if (m_cachedReturnValue is CacheList cacheList)
{
cacheList.WhiteSpace = 0f;
cacheList.ButtonWidthOffset += 70f;
}
m_cachedReturnValue.UpdateValue();
}
else
{
m_cachedReturnValue = null;
}
}
}
}

View File

@ -0,0 +1,345 @@
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 CacheObjectBase
{
public object Value;
public string ValueTypeName;
// Reflection Inspector only
public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public string ReflectionException { get; set; }
public int PropertyIndex { get; private set; }
private string m_propertyIndexInput = "0";
public string RichTextName => m_richTextName ?? GetRichTextName();
private string m_richTextName;
public bool CanWrite
{
get
{
if (MemInfo is FieldInfo fi)
return !(fi.IsLiteral && !fi.IsInitOnly);
else if (MemInfo is PropertyInfo pi)
return pi.CanWrite;
else
return false;
}
}
// ===== Abstract/Virtual Methods ===== //
public virtual void Init() { }
public abstract void DrawValue(Rect window, float width);
// ===== Static Methods ===== //
/// <summary>
/// Get CacheObject from only an object instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
public static CacheObjectBase GetCacheObject(object obj)
{
return GetCacheObject(obj, null, null);
}
/// <summary>
/// Get CacheObject from an object instance and provide the value type
/// Calls GetCacheObjectImpl directly</summary>
public static CacheObjectBase GetCacheObject(object obj, Type valueType)
{
return GetCacheObjectImpl(obj, null, null, valueType);
}
/// <summary>
/// Get CacheObject from only a MemberInfo and declaring instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (null, memberInfo, declaringInstance)</summary>
public static CacheObjectBase GetCacheObject(MemberInfo memberInfo, object declaringInstance)
{
return GetCacheObject(null, memberInfo, declaringInstance);
}
/// <summary>
/// Get CacheObject from either an object or MemberInfo, and don't provide the type.
/// This gets the type and then calls GetCacheObjectImpl</summary>
public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
{
Type type = null;
if (obj != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
else if (memberInfo != null)
{
if (memberInfo is FieldInfo fi)
{
type = fi.FieldType;
}
else if (memberInfo is PropertyInfo pi)
{
type = pi.PropertyType;
}
else if (memberInfo is MethodInfo mi)
{
type = mi.ReturnType;
}
}
if (type == null)
{
return null;
}
return GetCacheObjectImpl(obj, memberInfo, declaringInstance, type);
}
/// <summary>
/// Actual GetCacheObject implementation (private)
/// </summary>
private static CacheObjectBase GetCacheObjectImpl(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
{
CacheObjectBase holder;
if (memberInfo is MethodInfo mi)
{
if (CacheMethod.CanEvaluate(mi))
{
holder = new CacheMethod();
}
else
{
return null;
}
}
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
{
holder = new CacheGameObject();
}
else if (valueType.IsPrimitive || valueType == typeof(string))
{
holder = new CachePrimitive();
}
else if (valueType.IsEnum)
{
holder = new CacheEnum();
}
else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType))
{
holder = new CacheList();
}
else if (ReflectionHelpers.IsDictionary(valueType))
{
holder = new CacheDictionary();
}
else
{
holder = new CacheOther();
}
holder.Value = obj;
holder.ValueTypeName = valueType.FullName;
if (memberInfo != null)
{
holder.MemInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType;
holder.DeclaringInstance = declaringInstance;
holder.UpdateValue();
}
holder.Init();
return holder;
}
// ======== Instance Methods =========
public virtual void UpdateValue()
{
if (MemInfo == null || !string.IsNullOrEmpty(ReflectionException))
{
return;
}
try
{
if (MemInfo.MemberType == MemberTypes.Field)
{
var fi = MemInfo as FieldInfo;
Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
}
else if (MemInfo.MemberType == MemberTypes.Property)
{
var pi = MemInfo as PropertyInfo;
bool isStatic = pi.GetAccessors()[0].IsStatic;
var target = isStatic ? null : DeclaringInstance;
if (pi.GetIndexParameters().Length > 0)
{
var indexes = new object[] { PropertyIndex };
Value = pi.GetValue(target, indexes);
}
else
{
Value = pi.GetValue(target, null);
}
}
ReflectionException = null;
}
catch (Exception e)
{
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
}
public void SetValue()
{
try
{
if (MemInfo.MemberType == MemberTypes.Field)
{
var fi = MemInfo as FieldInfo;
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, Value);
}
else if (MemInfo.MemberType == MemberTypes.Property)
{
var pi = MemInfo as PropertyInfo;
if (pi.GetIndexParameters().Length > 0)
{
var indexes = new object[] { PropertyIndex };
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, indexes);
}
else
{
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
}
}
}
catch (Exception e)
{
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
}
}
// ========= Instance Gui Draw ==========
public const float MAX_LABEL_WIDTH = 400f;
public static void ClampLabelWidth(Rect window, ref float labelWidth)
{
float min = window.width * 0.37f;
if (min > MAX_LABEL_WIDTH) min = MAX_LABEL_WIDTH;
labelWidth = Mathf.Clamp(labelWidth, min, MAX_LABEL_WIDTH);
}
public void Draw(Rect window, float labelWidth = 215f)
{
if (labelWidth > 0)
{
ClampLabelWidth(window, ref labelWidth);
}
if (MemInfo != null)
{
var name = RichTextName;
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
{
name += $"[{PropertyIndex}]";
}
GUILayout.Label(name, 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 && MemInfo?.MemberType != MemberTypes.Method)
{
GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
}
else
{
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
{
GUILayout.Label("index:", new GUILayoutOption[] { GUILayout.Width(50) });
m_propertyIndexInput = GUILayout.TextField(m_propertyIndexInput, new GUILayoutOption[] { GUILayout.Width(100) });
if (GUILayout.Button("Set", new GUILayoutOption[] { GUILayout.Width(60) }))
{
if (int.TryParse(m_propertyIndexInput, out int i))
{
PropertyIndex = i;
UpdateValue();
}
else
{
MelonLogger.Log($"Could not parse '{m_propertyIndexInput}' to an int!");
}
}
// new line and space
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(labelWidth);
}
DrawValue(window, window.width - labelWidth - 90);
}
}
private string GetRichTextName()
{
string memberColor = "";
switch (MemInfo.MemberType)
{
case MemberTypes.Field:
memberColor = "#c266ff"; break;
case MemberTypes.Property:
memberColor = "#72a6a6"; break;
case MemberTypes.Method:
memberColor = "#ff8000"; break;
};
m_richTextName = $"<color=#2df7b2>{MemInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemInfo.Name}</color>";
if (MemInfo is MethodInfo mi)
{
m_richTextName += "(";
var _params = "";
foreach (var param in mi.GetParameters())
{
if (_params != "") _params += ", ";
_params += $"<color=#a6e9e9>{param.Name}</color>";
}
m_richTextName += _params;
m_richTextName += ")";
}
return m_richTextName;
}
}
}

View File

@ -0,0 +1,60 @@
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 : CacheObjectBase
{
private MethodInfo m_toStringMethod;
public MethodInfo ToStringMethod
{
get
{
if (m_toStringMethod == null)
{
try
{
m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0])
?? typeof(object).GetMethod("ToString", new Type[0]);
// test invoke
m_toStringMethod.Invoke(Value, null);
}
catch
{
m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]);
}
}
return m_toStringMethod;
}
}
public override void DrawValue(Rect window, float width)
{
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
if (!label.Contains(ValueTypeName))
{
label += $" ({ValueTypeName})";
}
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.Width(width) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
}
}
}

View File

@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CachePrimitive : CacheObjectBase
{
public enum PrimitiveTypes
{
Bool,
Double,
Float,
Int,
String,
Char
}
private string m_valueToString;
public PrimitiveTypes PrimitiveType;
public MethodInfo ParseMethod
{
get
{
if (m_parseMethod == null)
{
m_parseMethod = Value.GetType().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 (IsInteger(type))
{
PrimitiveType = PrimitiveTypes.Int;
}
else if (type == typeof(char))
{
PrimitiveType = PrimitiveTypes.Char;
}
else
{
PrimitiveType = PrimitiveTypes.String;
}
}
private static bool IsInteger(Type type)
{
// For our purposes, all types of int can be treated the same, including IntPtr.
return _integerTypes.Contains(type);
}
private static readonly HashSet<Type> _integerTypes = new HashSet<Type>
{
typeof(int),
typeof(uint),
typeof(short),
typeof(ushort),
typeof(long),
typeof(ulong),
typeof(byte),
typeof(sbyte),
typeof(IntPtr)
};
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>")}";
var label = $"{color}{b}</color>";
if (CanWrite)
{
b = GUILayout.Toggle(b, label, null);
if (b != (bool)Value)
{
SetValueFromInput(b.ToString());
}
}
else
{
GUILayout.Label(label, null);
}
}
else
{
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f;
if (CanWrite) maxwidth -= 60;
if (dynSize > maxwidth)
{
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
}
else
{
m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) });
}
if (CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
SetValueFromInput(m_valueToString);
}
}
GUILayout.Space(5);
}
}
public void SetValueFromInput(string value)
{
if (MemInfo == 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

@ -1,84 +1,83 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.Reflection;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
namespace Explorer
{
public class CppExplorer : MelonMod
{
// 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 GUID = "com.sinai.cppexplorer";
public const string VERSION = "1.5.3";
public const string AUTHOR = "Sinai";
// fields
public const string NAME = "CppExplorer"
#if Release_Unity2018
+ " (Unity 2018)"
#endif
;
public static CppExplorer Instance;
private string m_objUnderMouseName = "";
private Camera m_main;
public static CppExplorer Instance { get; private set; }
// 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
public static bool ShowMenu
{
get
{
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
}
get => m_showMenu;
set => SetShowMenu(value);
}
private static bool m_showMenu;
public static bool ForceUnlockMouse
{
get => m_forceUnlock;
set => SetForceUnlock(value);
}
private static bool m_forceUnlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
public static bool ShouldForceMouse => ShowMenu && ForceUnlockMouse;
private static void SetShowMenu(bool show)
{
m_showMenu = show;
UpdateCursorControl();
}
public Camera MainCamera
{
get
{
if (m_main == null)
{
m_main = Camera.main;
}
return m_main;
}
}
// methods
// ========== MonoBehaviour methods ==========
public override void OnApplicationStart()
{
base.OnApplicationStart();
Instance = this;
new MainMenu();
new WindowManager();
//var harmony = HarmonyInstance.Create(ID);
//harmony.PatchAll();
// Get current cursor state and enable cursor
m_lastLockMode = Cursor.lockState;
m_lastVisibleState = Cursor.visible;
// done init
ShowMenu = true;
// Enable ShowMenu and ForceUnlockMouse
// (set m_showMenu to not call UpdateCursorState twice)
m_showMenu = true;
SetForceUnlock(true);
MelonLogger.Log($"CppExplorer {VERSION} initialized.");
}
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()
{
// Check main toggle key input
if (Input.GetKeyDown(KeyCode.F7))
{
ShowMenu = !ShowMenu;
@ -86,128 +85,116 @@ namespace Explorer
if (ShowMenu)
{
// Check Force-Unlock input
if (Input.GetKeyDown(KeyCode.LeftAlt))
{
ForceUnlockMouse = !ForceUnlockMouse;
}
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();
}
}
public override void OnGUI()
{
base.OnGUI();
if (!ShowMenu) return;
MainMenu.Instance.OnGUI();
WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI();
}
if (MouseInspect)
// =========== Cursor control ===========
private static void SetForceUnlock(bool unlock)
{
m_forceUnlock = unlock;
UpdateCursorControl();
}
private static void UpdateCursorControl()
{
m_currentlySettingCursor = true;
if (ShouldForceMouse)
{
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;
}
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
}
// ************** 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)
else
{
gameObject = gameObject.transform.parent.gameObject;
text = "/" + gameObject.name + text;
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
}
return text;
m_currentlySettingCursor = false;
}
public static Type GetType(string _type)
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
// value that we set back to when we close the menu or disable force-unlock.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)]
public class Cursor_set_lockState
{
try
[HarmonyPrefix]
public static void Prefix(ref CursorLockMode value)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
if (!m_currentlySettingCursor)
{
try
m_lastLockMode = value;
if (ShouldForceMouse)
{
if (asm.GetType(_type) is Type type)
{
return type;
}
value = CursorLockMode.None;
}
catch { }
}
return null;
}
}
catch
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Setter)]
public class Cursor_set_visible
{
[HarmonyPrefix]
public static void Prefix(ref bool value)
{
return null;
if (!m_currentlySettingCursor)
{
m_lastVisibleState = value;
if (ShouldForceMouse)
{
value = true;
}
}
}
}
// Make it appear as though UnlockMouse is disabled to the rest of the application.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Getter)]
public class Cursor_get_lockState
{
[HarmonyPostfix]
public static void Postfix(ref CursorLockMode __result)
{
if (ShouldForceMouse)
{
__result = m_lastLockMode;
}
}
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Getter)]
public class Cursor_get_visible
{
[HarmonyPostfix]
public static void Postfix(ref bool __result)
{
if (ShouldForceMouse)
{
__result = m_lastVisibleState;
}
}
}
}

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>
@ -40,13 +42,9 @@
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
<Private>False</Private>
</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>
@ -62,74 +60,97 @@
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnhollowerRuntimeLib">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll</HintPath>
<!-- 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="Unity.TextMeshPro">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Unity.TextMeshPro.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.CoreModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.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.IMGUIModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.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.InputLegacyModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.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.InputModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputModule.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.ParticleSystemModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.ParticleSystemModule.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.Physics2DModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.Physics2DModule.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.PhysicsModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.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.TextRenderingModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.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.UI">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.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.UIElementsModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
<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.UIModule">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIModule.dll</HintPath>
<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>
</ItemGroup>
<ItemGroup>
<Compile Include="ILBehaviour.cs" />
<Compile Include="CachedObjects\CacheDictionary.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="CachedObjects\CacheMethod.cs" />
<Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="Extensions\UnityExtensions.cs" />
<Compile Include="Helpers\PageHelper.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" />
<Compile Include="Helpers\UIHelpers.cs" />
<Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="MainMenu\InspectUnderMouse.cs" />
<Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="Windows\ResizeDrag.cs" />
<Compile Include="Windows\TabViewWindow.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;
}
}
}

88
src/Helpers/PageHelper.cs Normal file
View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public enum Turn
{
Left,
Right
}
public class PageHelper
{
public int PageOffset { get; set; }
public int PageLimit { get; set; } = 20;
public int Count { get; set; }
public int MaxOffset { get; set; } = -1;
public int CalculateMaxOffset()
{
return MaxOffset = (int)Mathf.Ceil((float)(Count / (decimal)PageLimit)) - 1;
}
public void CurrentPageLabel()
{
var orig = GUI.skin.label.alignment;
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"Page {PageOffset + 1}/{MaxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
GUI.skin.label.alignment = orig;
}
public void TurnPage(Turn direction)
{
var _ = Vector2.zero;
TurnPage(direction, ref _);
}
public void TurnPage(Turn direction, ref Vector2 scroll)
{
if (direction == Turn.Left)
{
if (PageOffset > 0)
{
PageOffset--;
scroll = Vector2.zero;
}
}
else
{
if (PageOffset < MaxOffset)
{
PageOffset++;
scroll = Vector2.zero;
}
}
}
public int CalculateOffsetIndex()
{
int offset = PageOffset * PageLimit;
if (offset >= Count)
{
offset = 0;
PageOffset = 0;
}
return offset;
}
public void DrawLimitInputArea()
{
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.PageLimit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != PageLimit.ToString() && int.TryParse(limit, out int i))
{
PageLimit = i;
}
}
}
}

View File

@ -0,0 +1,156 @@
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 MelonLoader;
namespace Explorer
{
public class ReflectionHelpers
{
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.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;
return m_tryCastMethodInfo
.MakeGenericMethod(castTo)
.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 IsArray(Type t)
{
return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
}
public static bool IsList(Type t)
{
if (t.IsGenericType)
{
var generic = t.GetGenericTypeDefinition();
return generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))
|| generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.IList<>));
}
else
{
return t.IsAssignableFrom(typeof(Il2CppSystem.Collections.IList));
}
}
public static bool IsDictionary(Type t)
{
return t.IsGenericType
&& t.GetGenericTypeDefinition() is Type typeDef
&& typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>));
}
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 obj)
{
if (obj == null) return null;
if (obj is Il2CppSystem.Object ilObject)
{
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
{
return t;
}
return ilObject.GetType();
}
return obj.GetType();
}
public static Type[] GetAllBaseTypes(object obj)
{
var list = new List<Type>();
var type = GetActualType(obj);
list.Add(type);
while (type.BaseType != null)
{
type = type.BaseType;
list.Add(type);
}
return list.ToArray();
}
}
}

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

@ -0,0 +1,144 @@
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
{
private static bool ScrollUnstrippingFailed = false;
public static Vector2 BeginScrollView(Vector2 scroll) => BeginScrollView(scroll, null);
public static Vector2 BeginScrollView(Vector2 scroll, GUIStyle style, params GUILayoutOption[] layoutOptions)
{
if (ScrollUnstrippingFailed) return scroll;
try
{
if (style != null)
return GUILayout.BeginScrollView(scroll, style, layoutOptions);
else
return GUILayout.BeginScrollView(scroll, layoutOptions);
}
catch
{
ScrollUnstrippingFailed = true;
return scroll;
}
}
public static void EndScrollView()
{
if (ScrollUnstrippingFailed) return;
GUILayout.EndScrollView();
}
// 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(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{
var obj = (_obj as GameObject) ?? (_obj as Transform).gameObject;
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(object _obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{
var obj = _obj as GameObject ?? (_obj as Transform).gameObject;
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 _);
}
}
}
}

118
src/Helpers/UIStyles.cs Normal file
View File

@ -0,0 +1,118 @@
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, bool small = false)
{
var orig = GUI.color;
GUI.color = _color;
GUILayout.Box(GUIContent.none, !small ? HorizontalBar : HorizontalBarSmall, null);
GUI.color = orig;
}
private static GUISkin _customSkin;
public static Texture2D m_nofocusTex;
public static Texture2D m_focusTex;
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 GUIStyle _horizBarStyle;
private static GUIStyle HorizontalBarSmall
{
get
{
if (_horizBarSmallStyle == null)
{
_horizBarSmallStyle = new GUIStyle();
_horizBarSmallStyle.normal.background = Texture2D.whiteTexture;
_horizBarSmallStyle.margin = new RectOffset(0, 0, 2, 2);
_horizBarSmallStyle.fixedHeight = 1;
}
return _horizBarSmallStyle;
}
}
private static GUIStyle _horizBarSmallStyle;
private static GUISkin CreateWindowSkin()
{
var newSkin = Object.Instantiate(GUI.skin);
Object.DontDestroyOnLoad(newSkin);
m_nofocusTex = MakeTex(1, 1, new Color(0.1f, 0.1f, 0.1f, 0.7f));
m_focusTex = MakeTex(1, 1, 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

@ -51,15 +51,12 @@ namespace Explorer
public void OnGUI()
{
if (CppExplorer.ShowMenu)
{
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
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;
}
GUI.skin = origSkin;
}
private void MainWindow(int id)
@ -77,30 +74,20 @@ namespace Explorer
MainHeader();
var page = Pages[m_currentPage];
page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView);
page.DrawWindow();
GUILayout.EndScrollView();
MainRect = WindowManager.ResizeWindow(MainRect, MainWindowID);
page.scroll = UIHelpers.BeginScrollView(page.scroll);
page.DrawWindow();
UIHelpers.EndScrollView();
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);
GUILayout.EndArea();
}
private void MainHeader()
{
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Options:</b>", new GUILayoutOption[] { GUILayout.Width(70) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("Array Limit:", new GUILayoutOption[] { GUILayout.Width(70) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
var _input = GUILayout.TextField(CppExplorer.ArrayLimit.ToString(), new GUILayoutOption[] { GUILayout.Width(60) });
if (int.TryParse(_input, out int _lim))
{
CppExplorer.ArrayLimit = _lim;
}
CppExplorer.Instance.MouseInspect = GUILayout.Toggle(CppExplorer.Instance.MouseInspect, "Inspect Under Mouse (Shift + RMB)", null);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
for (int i = 0; i < Pages.Count; i++)
{
@ -116,21 +103,19 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUI.color = Color.white;
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
bool mouseState = CppExplorer.ForceUnlockMouse;
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", null);
if (setMouse != mouseState) CppExplorer.ForceUnlockMouse = setMouse;
WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", null);
GUILayout.EndHorizontal();
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)
@ -86,11 +86,11 @@ MelonLogger.Log(""hello world"");";
if (!UsingDirectives.Contains(asm))
{
UsingDirectives.Add(asm);
Evaluate(AsmToUsing(asm));
Evaluate(AsmToUsing(asm), true);
}
}
public object Evaluate(string str)
public object Evaluate(string str, bool suppressWarning = false)
{
object ret = VoidType.Value;
@ -98,11 +98,19 @@ MelonLogger.Log(""hello world"");";
try
{
compiled?.Invoke(ref ret);
if (compiled == null)
{
throw new Exception("Mono.Csharp Service was unable to compile the code provided.");
}
compiled.Invoke(ref ret);
}
catch (Exception e)
{
MelonLogger.LogWarning(e.ToString());
if (!suppressWarning)
{
MelonLogger.LogWarning(e.GetType() + ", " + e.Message);
}
}
return ret;
@ -111,10 +119,12 @@ 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) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.Label("Enter code here as though it is a method body:", null);
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
{
@ -139,22 +149,24 @@ MelonLogger.Log(""hello world"");";
}
GUILayout.Label("<b>Using directives:</b>", null);
foreach (var asm in UsingDirectives)
{
GUILayout.Label(AsmToUsing(asm, true), null);
}
GUILayout.BeginHorizontal(null);
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(110) });
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
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();
}
GUILayout.EndHorizontal();
foreach (var asm in UsingDirectives)
{
GUILayout.Label(AsmToUsing(asm, true), null);
}
}
public override void Update() { }

View File

@ -8,24 +8,28 @@ 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 ----- //
public PageHelper Pages = new PageHelper();
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 +40,83 @@ namespace Explorer
public void OnSceneChange()
{
m_currentScene = CppExplorer.ActiveSceneName;
m_currentTransform = null;
CancelSearch();
m_currentScene = UnityHelpers.ActiveSceneName;
SetTransformTarget(null);
}
//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>();
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);
}
}
Pages.Count = allTransforms.Count;
int offset = Pages.CalculateOffsetIndex();
// sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
for (int i = offset; i < offset + Pages.PageLimit && i < Pages.Count; 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 +124,7 @@ namespace Explorer
{
m_searchResults = SearchSceneObjects(m_searchInput);
m_searching = true;
Pages.Count = m_searchResults.Count;
}
public void CancelSearch()
@ -201,19 +132,263 @@ 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);
Pages.DrawLimitInputArea();
if (Pages.Count > Pages.PageLimit)
{
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.scroll);
Update();
}
Pages.CurrentPageLabel();
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Right, ref this.scroll);
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 = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.PageLimit && i < m_searchResults.Count; i++)
{
var obj = m_searchResults[i];
if (obj.RefGameObject)
{
UIHelpers.FastGameobjButton(obj.RefGameObject,
obj.EnabledColor,
obj.Label,
obj.RefGameObject.activeSelf,
SetTransformTarget,
true,
MainMenu.MainRect.width - 170);
}
else
{
GUILayout.Label("<i><color=red>Null or destroyed!</color></i>", null);
}
}
}
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,26 +1,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.Reflection;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
using UnhollowerRuntimeLib;
using MelonLoader;
using UnhollowerBaseLib;
namespace Explorer
{
public class SearchPage : 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 Vector2 resultsScroll = Vector2.zero;
public PageHelper Pages = new PageHelper();
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
public SceneFilter SceneMode = SceneFilter.Any;
public TypeFilter TypeMode = TypeFilter.Object;
@ -41,9 +43,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 +51,34 @@ namespace Explorer
public void OnSceneChange()
{
m_searchResults.Clear();
Pages.PageOffset = 0;
}
public override void Update()
{
}
private void CacheResults(IEnumerable results)
{
m_searchResults = new List<CacheObjectBase>();
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 = CacheObjectBase.GetCacheObject(toCache);
m_searchResults.Add(cache);
}
Pages.Count = m_searchResults.Count;
Pages.PageOffset = 0;
}
public override void DrawWindow()
{
try
@ -67,7 +88,8 @@ namespace Explorer
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
{
m_searchResults = GetInstanceClassScanner().ToList();
//m_searchResults = GetInstanceClassScanner().ToList();
CacheResults(GetInstanceClassScanner());
}
GUILayout.EndHorizontal();
@ -78,20 +100,52 @@ 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;
resultsScroll = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView);
int count = m_searchResults.Count;
Pages.CalculateMaxOffset();
GUILayout.BeginHorizontal(null);
Pages.DrawLimitInputArea();
if (count > Pages.PageLimit)
{
// prev/next page buttons
if (Pages.Count > Pages.PageLimit)
{
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.resultsScroll);
}
Pages.CurrentPageLabel();
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Right, ref this.resultsScroll);
}
}
}
GUILayout.EndHorizontal();
resultsScroll = UIHelpers.BeginScrollView(resultsScroll);
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;
int offset = Pages.CalculateOffsetIndex();
UIStyles.DrawValue(ref obj, _temprect);
for (int i = offset; i < offset + Pages.PageLimit && i < count; i++)
{
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
//m_searchResults[i].DrawValue(MainMenu.MainRect);
}
}
else
@ -99,8 +153,7 @@ namespace Explorer
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
}
GUILayout.EndScrollView();
UIHelpers.EndScrollView();
GUILayout.EndVertical();
}
catch
@ -123,15 +176,15 @@ namespace Explorer
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("Result limit:", new GUILayoutOption[] { GUILayout.Width(100) });
var resultinput = m_limit.ToString();
resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
if (int.TryParse(resultinput, out int _i) && _i > 0)
{
m_limit = _i;
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
//GUI.skin.label.alignment = TextAnchor.MiddleRight;
//GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
//var resultinput = m_limit.ToString();
//resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
//if (int.TryParse(resultinput, out int _i) && _i > 0)
//{
// m_limit = _i;
//}
//GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.EndHorizontal();
@ -204,7 +257,125 @@ namespace Explorer
}
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
// ======= search functions =======
private void Search()
{
Pages.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 +415,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

@ -1,258 +0,0 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace Explorer
{
public class WindowManager
{
public static WindowManager Instance;
public static List<UIWindow> Windows = new List<UIWindow>();
public static int CurrentWindowID { get; set; } = 500000;
private static Rect m_lastWindowRect;
public WindowManager()
{
Instance = this;
}
public void Update()
{
foreach (var window in Windows)
{
window.Update();
}
}
public void OnGUI()
{
foreach (var window in Windows)
{
window.OnGUI();
}
}
// ========= Public Helpers =========
public static bool IsMouseInWindow
{
get
{
if (!CppExplorer.ShowMenu)
{
return false;
}
foreach (var window in Windows)
{
if (RectContainsMouse(window.m_rect))
{
return true;
}
}
return RectContainsMouse(MainMenu.MainRect);
}
}
private static bool RectContainsMouse(Rect rect)
{
return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
}
public static int NextWindowID()
{
return CurrentWindowID++;
}
public static Rect GetNewWindowRect()
{
return GetNewWindowRect(ref m_lastWindowRect);
}
public static Rect GetNewWindowRect(ref Rect lastRect)
{
Rect rect = new Rect(0, 0, 550, 700);
var mainrect = MainMenu.MainRect;
if (mainrect.x <= (Screen.width - mainrect.width - 100))
{
rect = new Rect(mainrect.x + mainrect.width + 20, mainrect.y, rect.width, rect.height);
}
if (lastRect.x == rect.x)
{
rect = new Rect(rect.x + 25, rect.y + 25, rect.width, rect.height);
}
lastRect = rect;
return rect;
}
public static UIWindow InspectObject(object obj, out bool createdNew)
{
createdNew = false;
foreach (var window in Windows)
{
if (obj == window.Target)
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
return window;
}
}
createdNew = true;
if (obj is GameObject || obj is Transform)
{
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
}
else
{
return InspectReflection(obj);
}
}
private static UIWindow InspectGameObject(GameObject obj)
{
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
GUI.FocusWindow(new_window.windowID);
return new_window;
}
public static UIWindow InspectReflection(object obj)
{
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
GUI.FocusWindow(new_window.windowID);
return new_window;
}
// ============= Resize Window Helper ============
static readonly GUIContent gcDrag = new GUIContent("<->", "drag to resize");
private static 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))
{
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();
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

@ -9,9 +9,11 @@ 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 Title => WindowManager.TabView
? $"<color=cyan>[G]</color> {m_object.name}"
: $"GameObject Inspector ({m_object.name})";
public GameObject m_object;
@ -19,20 +21,22 @@ namespace Explorer
private string m_name;
private string m_scene;
private Vector2 m_transformScroll = Vector2.zero;
private Transform[] m_children;
private Vector2 m_transformScroll = Vector2.zero;
private PageHelper ChildPages = new PageHelper();
private Component[] m_components;
private Vector2 m_compScroll = Vector2.zero;
//private Component[] m_components;
private PageHelper CompPages = new PageHelper();
private float m_translateAmount = 0.3f;
private float m_rotateAmount = 50f;
private float m_scaleAmount = 0.1f;
List<Component> m_cachedDestroyList = new List<Component>();
private readonly List<Component> m_cachedDestroyList = new List<Component>();
//private string m_addComponentInput = "";
private string m_setParentInput = "";
private string m_setParentInput = "Enter a GameObject name or path";
public bool GetObjectAsGameObject()
{
@ -68,30 +72,60 @@ namespace Explorer
}
m_name = m_object.name;
m_scene = m_object.scene == null ? "null" : m_object.scene.name;
m_scene = string.IsNullOrEmpty(m_object.scene.name)
? "None"
: m_object.scene.name;
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>();
//m_object.GetComponents(listComps);
//m_components = listComps.ToArray();
var list = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++)
{
list.Add(m_object.transform.GetChild(i));
}
m_children = list.ToArray();
Update();
}
public override void Update()
{
if (!m_object && !GetObjectAsGameObject())
try
{
MelonLogger.Log("Object is null! Destroying window...");
DestroyWindow();
if (!m_object && !GetObjectAsGameObject())
{
throw new Exception("Object is null!");
}
var list = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++)
{
list.Add(m_object.transform.GetChild(i));
}
list.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_children = list.ToArray();
ChildPages.Count = m_children.Length;
var list2 = new List<Component>();
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
{
var ilType = comp.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
list2.Add(comp);
}
m_components = list2.ToArray();
CompPages.Count = m_components.Length;
}
catch (Exception e)
{
DestroyOnException(e);
}
}
private void InspectGameObject(GameObject obj)
private void DestroyOnException(Exception e)
{
MelonLogger.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
DestroyWindow();
}
private void InspectGameObject(Transform obj)
{
var window = WindowManager.InspectObject(obj, out bool created);
@ -125,89 +159,127 @@ namespace Explorer
public override void WindowFunction(int windowID)
{
Header();
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
GUILayout.BeginHorizontal(null);
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
if (m_scene == CppExplorer.ActiveSceneName)
try
{
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
if (!WindowManager.TabView)
{
ScenePage.Instance.SetTransformTarget(m_object);
MainMenu.SetCurrentPage(0);
Header();
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
}
scroll = UIHelpers.BeginScrollView(scroll);
GUILayout.BeginHorizontal(null);
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
if (m_scene == UnityHelpers.ActiveSceneName)
{
if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
{
ScenePage.Instance.SetTransformTarget(m_object.transform);
MainMenu.SetCurrentPage(0);
}
}
if (GUILayout.Button("Reflection Inspect", new GUILayoutOption[] { GUILayout.Width(150) }))
{
WindowManager.InspectObject(Target, out _, true);
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
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);
}
}
GUILayout.TextArea(pathlabel, null);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
GUILayout.TextArea(m_name, null);
GUILayout.EndHorizontal();
// --- Horizontal Columns section ---
GUILayout.BeginHorizontal(null);
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
TransformList(rect);
GUILayout.EndVertical();
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
ComponentList(rect);
GUILayout.EndVertical();
GUILayout.EndHorizontal(); // end horiz columns
GameObjectControls();
UIHelpers.EndScrollView();
if (!WindowManager.TabView)
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
string pathlabel = CppExplorer.GetGameObjectPath(m_object.transform);
if (m_object.transform.parent != null)
catch (Exception e)
{
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
InspectGameObject(m_object.transform.parent.gameObject);
}
DestroyOnException(e);
}
GUILayout.TextArea(pathlabel, null);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
GUILayout.TextArea(m_name, null);
GUILayout.EndHorizontal();
// --- Horizontal Columns section ---
GUILayout.BeginHorizontal(null);
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
TransformList();
GUILayout.EndVertical();
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
ComponentList();
GUILayout.EndVertical();
GUILayout.EndHorizontal(); // end horiz columns
GameObjectControls();
GUILayout.EndScrollView();
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
GUILayout.EndArea();
}
private void TransformList()
private void TransformList(Rect m_rect)
{
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView);
GUILayout.BeginVertical(GUI.skin.box, null);
m_transformScroll = UIHelpers.BeginScrollView(m_transformScroll);
GUILayout.Label("<b><size=15>Children</size></b>", null);
GUILayout.BeginHorizontal(null);
ChildPages.DrawLimitInputArea();
if (ChildPages.Count > ChildPages.PageLimit)
{
ChildPages.CalculateMaxOffset();
ChildPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
ChildPages.TurnPage(Turn.Left, ref this.m_transformScroll);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
ChildPages.TurnPage(Turn.Right, ref this.m_transformScroll);
}
}
GUILayout.EndHorizontal();
GUILayout.Label("<b>Children:</b>", null);
if (m_children != null && m_children.Length > 0)
{
foreach (var obj in m_children.Where(x => x.childCount > 0))
int start = ChildPages.CalculateOffsetIndex();
for (int j = start; (j < start + ChildPages.PageLimit && j < ChildPages.Count); j++)
{
var obj = m_children[j];
if (!obj)
{
GUILayout.Label("null", null);
continue;
}
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
}
foreach (var obj in m_children.Where(x => x.childCount == 0))
{
if (!obj)
{
GUILayout.Label("null", null);
continue;
}
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
}
}
else
@ -215,53 +287,78 @@ namespace Explorer
GUILayout.Label("<i>None</i>", null);
}
GUILayout.EndScrollView();
UIHelpers.EndScrollView();
GUILayout.EndVertical();
}
private void ComponentList()
private void ComponentList(Rect m_rect)
{
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView);
GUILayout.BeginVertical(GUI.skin.box, null);
m_compScroll = UIHelpers.BeginScrollView(m_compScroll);
GUILayout.Label("<b><size=15>Components</size></b>", null);
GUILayout.BeginHorizontal(null);
CompPages.DrawLimitInputArea();
if (CompPages.Count > CompPages.PageLimit)
{
CompPages.CalculateMaxOffset();
CompPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
CompPages.TurnPage(Turn.Left, ref this.m_compScroll);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
CompPages.TurnPage(Turn.Right, ref this.m_compScroll);
}
}
GUILayout.EndHorizontal();
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (m_cachedDestroyList.Count > 0)
{
m_cachedDestroyList.Clear();
}
var m_components = new Il2CppSystem.Collections.Generic.List<Component>();
m_object.GetComponentsInternal(Il2CppType.Of<Component>(), false, false, true, false, m_components);
var ilTypeOfTransform = Il2CppType.Of<Transform>();
var ilTypeOfBehaviour = Il2CppType.Of<Behaviour>();
foreach (var component in m_components)
if (m_components != null)
{
var ilType = component.GetIl2CppType();
if (ilType == ilTypeOfTransform)
{
continue;
}
int start = CompPages.CalculateOffsetIndex();
GUILayout.BeginHorizontal(null);
if (ilTypeOfBehaviour.IsAssignableFrom(ilType))
for (int j = start; (j < start + CompPages.PageLimit && j < CompPages.Count); j++)
{
BehaviourEnabledBtn(component.TryCast<Behaviour>());
var component = m_components[j];
if (!component) continue;
var ilType = component.GetIl2CppType();
GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
{
BehaviourEnabledBtn(component.TryCast<Behaviour>());
}
else
{
GUILayout.Space(26);
}
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
{
ReflectObject(component);
}
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
{
m_cachedDestroyList.Add(component);
}
GUILayout.EndHorizontal();
}
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
{
ReflectObject(component);
}
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
{
m_cachedDestroyList.Add(component);
}
GUILayout.EndHorizontal();
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
if (m_cachedDestroyList.Count > 0)
{
@ -272,25 +369,7 @@ 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();
UIHelpers.EndScrollView();
GUILayout.EndVertical();
}
@ -320,7 +399,7 @@ namespace Explorer
private void GameObjectControls()
{
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(530) });
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
GUILayout.BeginHorizontal(null);
@ -329,17 +408,18 @@ 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);
if (GUILayout.Button("Set DontDestroyOnLoad", new GUILayoutOption[] { GUILayout.Width(170) }))
{
GameObject.DontDestroyOnLoad(m_object);
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("Remove from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
{
m_object.transform.parent = null;
}
m_setParentInput = GUILayout.TextField(m_setParentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width - 280) });
m_setParentInput = GUILayout.TextField(m_setParentInput, null);
if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
{
if (GameObject.Find(m_setParentInput) is GameObject newparent)
@ -351,6 +431,11 @@ namespace Explorer
MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'");
}
}
if (GUILayout.Button("Detach from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
{
m_object.transform.parent = null;
}
GUILayout.EndHorizontal();
GUILayout.BeginVertical(GUI.skin.box, null);
@ -362,7 +447,7 @@ namespace Explorer
GUILayout.EndVertical();
if (GUILayout.Button("<color=red><b>Destroy</b></color>", null))
if (GUILayout.Button("<color=red><b>Destroy</b></color>", new GUILayoutOption[] { GUILayout.Width(120) }))
{
GameObject.Destroy(m_object);
DestroyWindow();

View File

@ -0,0 +1,362 @@
using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
namespace Explorer
{
public class ReflectionWindow : UIWindow
{
public override string Title => WindowManager.TabView
? $"<color=cyan>[R]</color> {TargetType.Name}"
: $"Reflection Inspector ({TargetType.Name})";
public Type TargetType;
private CacheObjectBase[] m_allCachedMembers;
private CacheObjectBase[] m_cachedMembersFiltered;
public PageHelper Pages = new PageHelper();
//private int m_pageOffset;
//private int m_limitPerPage = 20;
private bool m_autoUpdate = false;
private string m_search = "";
public MemberTypes m_filter = MemberTypes.Property;
private bool m_hideFailedReflection = false;
// some extra caching
private UnityEngine.Object m_uObj;
private Component m_component;
public override void Init()
{
var type = ReflectionHelpers.GetActualType(Target);
TargetType = type;
var types = ReflectionHelpers.GetAllBaseTypes(Target);
CacheMembers(types);
if (Target is Il2CppSystem.Object ilObject)
{
var unityObj = ilObject.TryCast<UnityEngine.Object>();
if (unityObj)
{
m_uObj = unityObj;
var component = ilObject.TryCast<Component>();
if (component)
{
m_component = component;
}
}
}
m_filter = MemberTypes.All;
m_autoUpdate = true;
Update();
m_autoUpdate = false;
m_filter = MemberTypes.Property;
}
public override void Update()
{
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
if (m_autoUpdate)
{
UpdateValues();
}
}
private void UpdateValues()
{
foreach (var member in m_cachedMembersFiltered)
{
member.UpdateValue();
}
}
private bool ShouldProcessMember(CacheObjectBase holder)
{
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType) return false;
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
if (m_search == "" || holder.MemInfo == null) return true;
var name = holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name;
return name.ToLower().Contains(m_search.ToLower());
}
private void CacheMembers(Type[] types)
{
var list = new List<CacheObjectBase>();
var names = new List<string>();
foreach (var declaringType in types)
{
MemberInfo[] infos;
string exception = null;
try
{
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
}
catch
{
MelonLogger.Log($"Exception getting members for type: {declaringType.FullName}");
continue;
}
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 || member.MemberType == MemberTypes.Method)
{
var name = $"{member.DeclaringType.Name}.{member.Name}";
// blacklist (should probably make a proper implementation)
if (name == "Type.DeclaringMethod" || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) //|| member.Name.Contains("Il2CppType")
{
continue;
}
if (member is MethodInfo mi)
{
name += " (";
foreach (var param in mi.GetParameters())
{
name += param.ParameterType.Name + ", ";
}
name += ")";
}
if (names.Contains(name))
{
continue;
}
try
{
var cached = CacheObjectBase.GetCacheObject(member, target);
if (cached != null)
{
names.Add(name);
list.Add(cached);
cached.ReflectionException = exception;
}
}
catch (Exception e)
{
MelonLogger.LogWarning($"Exception caching member {name}!");
MelonLogger.Log(e.ToString());
}
}
}
}
m_allCachedMembers = list.ToArray();
}
// =========== GUI DRAW =========== //
public override void WindowFunction(int windowID)
{
try
{
// ====== HEADER ======
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
if (!WindowManager.TabView)
{
Header();
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
}
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Type:</b> <color=cyan>" + TargetType.FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(245f) });
if (m_uObj)
{
GUILayout.Label("Name: " + m_uObj.name, null);
}
GUILayout.EndHorizontal();
if (m_uObj)
{
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
UIHelpers.InstantiateButton(m_uObj);
if (m_component && m_component.gameObject is GameObject obj)
{
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("GameObject:", new GUILayoutOption[] { GUILayout.Width(135) });
var charWidth = obj.name.Length * 15;
var maxWidth = rect.width - 350;
var labelWidth = charWidth < maxWidth ? charWidth : maxWidth;
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) }))
{
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(MemberTypes.All, "All");
FilterToggle(MemberTypes.Property, "Properties");
FilterToggle(MemberTypes.Field, "Fields");
FilterToggle(MemberTypes.Method, "Methods");
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);
Pages.Count = m_cachedMembersFiltered.Length;
// prev/next page buttons
GUILayout.BeginHorizontal(null);
Pages.DrawLimitInputArea();
if (Pages.Count > Pages.PageLimit)
{
Pages.CalculateMaxOffset();
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.scroll);
}
Pages.CurrentPageLabel();
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Right, ref this.scroll);
}
}
GUILayout.EndHorizontal();
// ====== BODY ======
scroll = UIHelpers.BeginScrollView(scroll);
GUILayout.Space(10);
UIStyles.HorizontalLine(Color.grey);
GUILayout.BeginVertical(GUI.skin.box, null);
var members = this.m_cachedMembersFiltered;
int start = Pages.CalculateOffsetIndex();
for (int j = start; (j < start + Pages.PageLimit && j < members.Length); j++)
{
var holder = members[j];
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
try
{
holder.Draw(rect, 180f);
}
catch
{
GUILayout.EndHorizontal();
continue;
}
GUILayout.EndHorizontal();
// if not last element
if (!(j == (start + Pages.PageLimit - 1) || j == (members.Length - 1)))
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
}
GUILayout.EndVertical();
UIHelpers.EndScrollView();
if (!WindowManager.TabView)
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
}
}
catch (Il2CppException e)
{
if (!e.Message.Contains("in a group with only"))
{
throw;
}
}
catch (Exception e)
{
MelonLogger.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message);
DestroyWindow();
return;
}
}
private void FilterToggle(MemberTypes 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;
Pages.PageOffset = 0;
scroll = Vector2.zero;
}
GUI.color = Color.white;
}
}
}

106
src/Windows/ResizeDrag.cs Normal file
View File

@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using UnhollowerBaseLib;
namespace Explorer
{
public class ResizeDrag
{
private static bool RESIZE_FAILED = false;
private static readonly GUIContent gcDrag = new GUIContent("<-- Drag to resize -->");
private static bool isResizing = false;
private static Rect m_currentResize;
private static int m_currentWindow;
public static Rect ResizeWindow(Rect _rect, int ID)
{
if (!RESIZE_FAILED)
{
var origRect = _rect;
try
{
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
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 (Il2CppException e) when (e.Message.StartsWith("System.ArgumentException"))
{
// suppress
return origRect;
}
catch (Exception e)
{
RESIZE_FAILED = true;
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
return origRect;
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
else
{
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUILayout.Label("Resize window:", new GUILayoutOption[] { GUILayout.Width(100) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>Width:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width -= 5f;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width += 5f;
}
GUILayout.Label("<color=cyan>Height:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height -= 5f;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height += 5f;
}
GUILayout.EndHorizontal();
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
return _rect;
}
}
}

View File

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class TabViewWindow : UIWindow
{
public override string Title => $"Tabs ({WindowManager.Windows.Count})";
public static TabViewWindow Instance => m_instance ?? (m_instance = new TabViewWindow());
private static TabViewWindow m_instance;
private UIWindow m_targetWindow;
public int TargetTabID = 0;
public override bool IsTabViewWindow => true;
public TabViewWindow()
{
m_rect = new Rect(570, 0, 550, 700);
}
public override void Init() { }
public override void Update()
{
while (TargetTabID >= WindowManager.Windows.Count)
{
TargetTabID--;
}
if (TargetTabID == -1 && WindowManager.Windows.Count > 0)
{
TargetTabID = 0;
}
if (TargetTabID >= 0)
{
m_targetWindow = WindowManager.Windows[TargetTabID];
}
else
{
m_targetWindow = null;
}
m_targetWindow?.Update();
}
public override void WindowFunction(int windowID)
{
try
{
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>Close All</color>"))
{
foreach (var window in WindowManager.Windows)
{
window.DestroyWindow();
}
return;
}
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginHorizontal(null);
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
int tabPerRow = Mathf.FloorToInt((float)((decimal)m_rect.width / 238));
int rowCount = 0;
for (int i = 0; i < WindowManager.Windows.Count; i++)
{
if (rowCount >= tabPerRow)
{
rowCount = 0;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
rowCount++;
bool focused = i == TargetTabID;
string color = focused ? "<color=lime>" : "<color=orange>";
GUI.color = focused ? Color.green : Color.white;
var window = WindowManager.Windows[i];
if (GUILayout.Button(color + window.Title + "</color>", new GUILayoutOption[] { GUILayout.Width(200) }))
{
TargetTabID = i;
}
if (GUILayout.Button("<color=red><b>X</b></color>", new GUILayoutOption[] { GUILayout.Width(22) }))
{
window.DestroyWindow();
}
}
GUI.color = Color.white;
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
m_targetWindow.WindowFunction(m_targetWindow.windowID);
try
{
m_rect = ResizeDrag.ResizeWindow(m_rect, windowID);
}
catch { }
GUILayout.EndArea();
}
catch { }
}
}
}

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

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
namespace Explorer
{
public abstract class UIWindow
{
public abstract string Title { get; }
public object Target;
public int windowID;
public Rect m_rect = new Rect(0, 0, 550, 700);
public Vector2 scroll = Vector2.zero;
public virtual bool IsTabViewWindow => false;
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()
{
WindowManager.DestroyWindow(this);
}
public void OnGUI()
{
if (CppExplorer.ShowMenu)
{
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Title);
GUI.skin = origSkin;
}
}
public void Header()
{
if (!WindowManager.TabView)
{
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

@ -0,0 +1,228 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace Explorer
{
public class WindowManager
{
public static WindowManager Instance;
public static bool TabView = true;
public static List<UIWindow> Windows = new List<UIWindow>();
public static int CurrentWindowID { get; set; } = 500000;
private static Rect m_lastWindowRect;
private static readonly List<UIWindow> m_windowsToDestroy = new List<UIWindow>();
public WindowManager()
{
Instance = this;
}
public static void DestroyWindow(UIWindow window)
{
m_windowsToDestroy.Add(window);
}
public void Update()
{
if (m_windowsToDestroy.Count > 0)
{
foreach (var window in m_windowsToDestroy)
{
if (Windows.Contains(window))
{
Windows.Remove(window);
}
}
m_windowsToDestroy.Clear();
}
if (TabView)
{
TabViewWindow.Instance.Update();
}
else
{
for (int i = 0; i < Windows.Count; i++)
{
var window = Windows[i];
if (window != null)
{
window.Update();
}
}
}
}
public void OnGUI()
{
if (TabView)
{
if (Windows.Count > 0)
{
TabViewWindow.Instance.OnGUI();
}
}
else
{
foreach (var window in Windows)
{
window.OnGUI();
}
}
}
// ========= Public Helpers =========
public static UIWindow InspectObject(object obj, out bool createdNew, bool forceReflection = false)
{
createdNew = false;
if (Input.GetKey(KeyCode.LeftShift))
{
forceReflection = true;
}
Il2CppSystem.Object iObj = null;
if (obj is Il2CppSystem.Object isObj)
{
iObj = isObj;
}
if (!forceReflection)
{
foreach (var window in Windows)
{
bool equals = ReferenceEquals(obj, window.Target);
if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
{
if (iCurrent.GetIl2CppType() != iTarget.GetIl2CppType())
{
if (iCurrent is Transform transform)
{
iCurrent = transform.gameObject;
}
}
equals = iCurrent.Pointer == iTarget.Pointer;
}
if (equals)
{
FocusWindow(window);
return window;
}
}
}
createdNew = true;
if (!forceReflection && (obj is GameObject || obj is Transform))
{
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
}
else
{
return InspectReflection(obj);
}
}
private static void FocusWindow(UIWindow window)
{
if (!TabView)
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
}
else
{
TabViewWindow.Instance.TargetTabID = Windows.IndexOf(window);
}
}
private static UIWindow InspectGameObject(GameObject obj)
{
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
FocusWindow(new_window);
return new_window;
}
private static UIWindow InspectReflection(object obj)
{
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
FocusWindow(new_window);
return new_window;
}
// === Misc Helpers ===
public static bool IsMouseInWindow
{
get
{
if (!CppExplorer.ShowMenu)
{
return false;
}
foreach (var window in Windows)
{
if (RectContainsMouse(window.m_rect))
{
return true;
}
}
return RectContainsMouse(MainMenu.MainRect);
}
}
private static bool RectContainsMouse(Rect rect)
{
return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
}
public static int NextWindowID()
{
return CurrentWindowID++;
}
public static Rect GetNewWindowRect()
{
return GetNewWindowRect(ref m_lastWindowRect);
}
public static Rect GetNewWindowRect(ref Rect lastRect)
{
Rect rect = new Rect(0, 0, 550, 700);
var mainrect = MainMenu.MainRect;
if (mainrect.x <= (Screen.width - mainrect.width - 100))
{
rect = new Rect(mainrect.x + mainrect.width + 20, mainrect.y, rect.width, rect.height);
}
if (lastRect.x == rect.x)
{
rect = new Rect(rect.x + 25, rect.y + 25, rect.width, rect.height);
}
lastRect = rect;
return rect;
}
}
}

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