mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-22 08:32:51 +08:00
Compare commits
78 Commits
Author | SHA1 | Date | |
---|---|---|---|
72d31eaa64 | |||
4e8b84b67e | |||
5b94e31a12 | |||
692a37635e | |||
9cb1cea025 | |||
e13f198815 | |||
9a059c1056 | |||
ffb6cad8c2 | |||
d0a4863139 | |||
bb8837d58c | |||
a236b272c1 | |||
18de1eaf1c | |||
b1264c6912 | |||
9836566e55 | |||
d20461fa0e | |||
72ec34090d | |||
883a8705c3 | |||
6adaaf5500 | |||
5de771389e | |||
51cfbe524e | |||
217b93ef4f | |||
42156e1160 | |||
e7208d0c9d | |||
2f3b779199 | |||
916bdea59b | |||
d8688193d5 | |||
30b48b1f1f | |||
0fd382c1f6 | |||
fd20a1120b | |||
abcb548706 | |||
b056644385 | |||
71f72e8f36 | |||
1ab41f5a30 | |||
7dc58ea02c | |||
68eeee353e | |||
92fe1dc704 | |||
6e644b4f50 | |||
c47974115b | |||
535e88be9a | |||
e567c16221 | |||
d13af7548e | |||
5d750aec77 | |||
45b5ce0ef8 | |||
e3d1add090 | |||
a59bcc95e4 | |||
ac4414ca86 | |||
19263092fe | |||
6bafab785b | |||
62b1688d53 | |||
4d015cbe93 | |||
0da8f4faea | |||
b264151c46 | |||
3d2bc7cd4b | |||
85c26e6af7 | |||
b149efa234 | |||
72c222d59a | |||
153ad2268b | |||
1ba9b2eae1 | |||
be2da96cc0 | |||
a2405d69c5 | |||
b2a90c832f | |||
9a784fd467 | |||
d399b6acd1 | |||
0c3067973e | |||
411593590d | |||
a2677e2321 | |||
13c2d6b92d | |||
2f3bb80eeb | |||
e58cf45e07 | |||
7144b6a44c | |||
e8b17d3583 | |||
10ee2a837f | |||
5acc5a78d8 | |||
2ba6f27a27 | |||
c5d889f9c7 | |||
ec39c68ffa | |||
0b78b4398f | |||
ab08d9dc96 |
87
README.md
87
README.md
@ -1,15 +1,29 @@
|
||||
# CppExplorer
|
||||
# CppExplorer [](https://github.com/HerpDerpinstine/MelonLoader)
|
||||
|
||||
[]()
|
||||
<p align="center">
|
||||
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
|
||||
</p>
|
||||
|
||||
Simple in-game explorer and debugging tool for IL2CPP Unity games, using MelonLoader.
|
||||
<p align="center">
|
||||
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> Unity games, using <a href="https://github.com/HerpDerpinstine/MelonLoader">MelonLoader</a>.<br><br>
|
||||
|
||||
<a href="../../releases/latest">
|
||||
<img src="https://img.shields.io/github/release/sinai-dev/CppExplorer.svg" />
|
||||
</a>
|
||||
|
||||
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
|
||||
</p>
|
||||
|
||||
### Known issues
|
||||
* CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
|
||||
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
|
||||
|
||||
## 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
|
||||
@ -23,27 +37,70 @@ 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/BzTOCvp.png)
|
||||
|
||||
Search feature:
|
||||
### Inspectors
|
||||
|
||||
[](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)
|
||||
* 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/DiDvl0Q.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/Pq127XD.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)
|
||||
|
||||
### C# REPL console
|
||||
|
||||
* A simple C# REPL console, allows you to execute a method body on the fly.
|
||||
|
||||
[](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
|
||||
|
||||
Written by Sinai.
|
||||
|
||||
Credits to ManlyMarco for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the MCS* version.
|
||||
|
||||
<i>* note: I commented out the `SkipVisibilityExt` constructor in `mcs.dll` since it was causing an exception with the Hook it attempted.</i>
|
||||
Thanks to:
|
||||
* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the UI style.
|
||||
* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor in `mcs.dll` since it was causing an exception with the Hook it attempted.
|
||||
|
368
src/CachedObjects/CacheObjectBase.cs
Normal file
368
src/CachedObjects/CacheObjectBase.cs
Normal file
@ -0,0 +1,368 @@
|
||||
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;
|
||||
public Type ValueType;
|
||||
|
||||
public MemberInfo MemInfo { get; set; }
|
||||
public Type DeclaringType { get; set; }
|
||||
public object DeclaringInstance { get; set; }
|
||||
public int PropertyIndex { get; private set; }
|
||||
private string m_propertyIndexInput = "0";
|
||||
|
||||
public string ReflectionException { get; set; }
|
||||
|
||||
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 (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;
|
||||
}
|
||||
}
|
||||
else if (obj != null)
|
||||
{
|
||||
type = ReflectionHelpers.GetActualType(obj);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// This is pretty ugly, could probably make a cleaner implementation.
|
||||
// However, the only cleaner ways I can think of are slower and probably not worth it.
|
||||
|
||||
// Note: the order is somewhat important.
|
||||
|
||||
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 (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
|
||||
{
|
||||
holder = new CacheVector();
|
||||
}
|
||||
else if (valueType == typeof(Quaternion))
|
||||
{
|
||||
holder = new CacheQuaternion();
|
||||
}
|
||||
else if (valueType == typeof(Color))
|
||||
{
|
||||
holder = new CacheColor();
|
||||
}
|
||||
else if (valueType == typeof(Rect))
|
||||
{
|
||||
holder = new CacheRect();
|
||||
}
|
||||
// must check this before IsEnumerable
|
||||
else if (ReflectionHelpers.IsDictionary(valueType))
|
||||
{
|
||||
holder = new CacheDictionary();
|
||||
}
|
||||
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType))
|
||||
{
|
||||
holder = new CacheList();
|
||||
}
|
||||
else
|
||||
{
|
||||
holder = new CacheOther();
|
||||
}
|
||||
|
||||
holder.Value = obj;
|
||||
holder.ValueType = valueType;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
269
src/CachedObjects/Object/CacheDictionary.cs
Normal file
269
src/CachedObjects/Object/CacheDictionary.cs
Normal file
@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheDictionary : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
public float ButtonWidthOffset { get; set; } = 290f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private CacheObjectBase[] m_cachedKeys;
|
||||
private CacheObjectBase[] m_cachedValues;
|
||||
|
||||
public Type TypeOfKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_keysType == null) GetGenericArguments();
|
||||
return m_keysType;
|
||||
}
|
||||
}
|
||||
private Type m_keysType;
|
||||
|
||||
public Type TypeOfValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_valuesType == null) GetGenericArguments();
|
||||
return m_valuesType;
|
||||
}
|
||||
}
|
||||
private Type m_valuesType;
|
||||
|
||||
public IDictionary IDict
|
||||
{
|
||||
get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono();
|
||||
set => m_iDictionary = value;
|
||||
}
|
||||
private IDictionary m_iDictionary;
|
||||
|
||||
// ========== Methods ==========
|
||||
|
||||
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
|
||||
private IDictionary Il2CppDictionaryToMono()
|
||||
{
|
||||
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
|
||||
|
||||
// make generic dictionary from key and value type
|
||||
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
|
||||
.MakeGenericType(TypeOfKeys, TypeOfValues));
|
||||
|
||||
// get keys and values
|
||||
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
|
||||
var values = ValueType.GetProperty("Values").GetValue(Value);
|
||||
|
||||
// create lists to hold them
|
||||
var keyList = new List<object>();
|
||||
var valueList = new List<object>();
|
||||
|
||||
// get keys enumerator and store keys
|
||||
var keyEnumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null);
|
||||
var keyCollectionType = keyEnumerator.GetType();
|
||||
var keyMoveNext = keyCollectionType.GetMethod("MoveNext");
|
||||
var keyCurrent = keyCollectionType.GetProperty("Current");
|
||||
while ((bool)keyMoveNext.Invoke(keyEnumerator, null))
|
||||
{
|
||||
keyList.Add(keyCurrent.GetValue(keyEnumerator));
|
||||
}
|
||||
|
||||
// get values enumerator and store values
|
||||
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
|
||||
var valueCollectionType = valueEnumerator.GetType();
|
||||
var valueMoveNext = valueCollectionType.GetMethod("MoveNext");
|
||||
var valueCurrent = valueCollectionType.GetProperty("Current");
|
||||
while ((bool)valueMoveNext.Invoke(valueEnumerator, null))
|
||||
{
|
||||
valueList.Add(valueCurrent.GetValue(valueEnumerator));
|
||||
}
|
||||
|
||||
// finally iterate into actual dictionary
|
||||
for (int i = 0; i < keyList.Count; i++)
|
||||
{
|
||||
dict.Add(keyList[i], valueList[i]);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private void GetGenericArguments()
|
||||
{
|
||||
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_keysType = memberType.GetGenericArguments()[0];
|
||||
m_valuesType = memberType.GetGenericArguments()[1];
|
||||
}
|
||||
}
|
||||
else if (Value != null)
|
||||
{
|
||||
var type = Value.GetType();
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
m_keysType = type.GetGenericArguments()[0];
|
||||
m_valuesType = type.GetGenericArguments()[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
// reset
|
||||
IDict = null;
|
||||
|
||||
if (Value == null || IDict == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = new List<CacheObjectBase>();
|
||||
foreach (var key in IDict.Keys)
|
||||
{
|
||||
var cache = GetCacheObject(key, TypeOfKeys);
|
||||
cache.UpdateValue();
|
||||
keys.Add(cache);
|
||||
}
|
||||
|
||||
var values = new List<CacheObjectBase>();
|
||||
foreach (var val in IDict.Values)
|
||||
{
|
||||
var cache = GetCacheObject(val, TypeOfValues);
|
||||
cache.UpdateValue();
|
||||
values.Add(cache);
|
||||
}
|
||||
|
||||
m_cachedKeys = keys.ToArray();
|
||||
m_cachedValues = values.ToArray();
|
||||
}
|
||||
|
||||
// ============= GUI Draw =============
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_cachedKeys == null || m_cachedValues == null)
|
||||
{
|
||||
GUILayout.Label("Cached keys or values is null!", null);
|
||||
return;
|
||||
}
|
||||
|
||||
int count = m_cachedKeys.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}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></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.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
|
||||
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 = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
var key = m_cachedKeys[i];
|
||||
var val = m_cachedValues[i];
|
||||
|
||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
//GUILayout.Space(whitespace);
|
||||
|
||||
if (key == null || val == 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) });
|
||||
|
||||
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
key.DrawValue(window, (window.width / 2) - 30f);
|
||||
|
||||
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
val.DrawValue(window, (window.width / 2) - 30f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
src/CachedObjects/Object/CacheGameObject.cs
Normal file
23
src/CachedObjects/Object/CacheGameObject.cs
Normal 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.GOButton(Value, null, false, width);
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
}
|
||||
}
|
||||
}
|
340
src/CachedObjects/Object/CacheList.cs
Normal file
340
src/CachedObjects/Object/CacheList.cs
Normal file
@ -0,0 +1,340 @@
|
||||
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, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
public float ButtonWidthOffset { get; set; } = 290f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
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)
|
||||
{
|
||||
cached.UpdateValue();
|
||||
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.FullName + "</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.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
|
||||
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 = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
209
src/CachedObjects/Other/CacheMethod.cs
Normal file
209
src/CachedObjects/Other/CacheMethod.cs
Normal file
@ -0,0 +1,209 @@
|
||||
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 => m_arguments != null && m_arguments.Length > 0;
|
||||
|
||||
public static bool CanEvaluate(MethodInfo mi)
|
||||
{
|
||||
// TODO generic args
|
||||
if (mi.GetGenericArguments().Length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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 parsedArgs = 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))
|
||||
{
|
||||
parsedArgs.Add(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
|
||||
{
|
||||
parsedArgs.Add(parsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// try add a null arg i guess
|
||||
parsedArgs.Add(null);
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, parsedArgs.ToArray());
|
||||
m_evaluated = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Exception evaluating: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != null)
|
||||
{
|
||||
m_cachedReturnValue = GetCacheObject(ret);
|
||||
|
||||
if (m_cachedReturnValue is IExpandHeight expander)
|
||||
{
|
||||
expander.WhiteSpace = 0f;
|
||||
expander.ButtonWidthOffset += 70f;
|
||||
}
|
||||
|
||||
m_cachedReturnValue.UpdateValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedReturnValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==== GUI DRAW ====
|
||||
|
||||
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)
|
||||
{
|
||||
m_cachedReturnValue.DrawValue(window, width);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
60
src/CachedObjects/Other/CacheOther.cs
Normal file
60
src/CachedObjects/Other/CacheOther.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
87
src/CachedObjects/Struct/CacheColor.cs
Normal file
87
src/CachedObjects/Struct/CacheColor.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheColor : CacheObjectBase
|
||||
{
|
||||
private string r = "0";
|
||||
private string g = "0";
|
||||
private string b = "0";
|
||||
private string a = "0";
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
var color = (Color)Value;
|
||||
|
||||
r = color.r.ToString();
|
||||
g = color.g.ToString();
|
||||
b = color.b.ToString();
|
||||
a = color.a.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Color</color>: {((Color)Value).ToString()}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
r = GUILayout.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
g = GUILayout.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
b = GUILayout.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
a = GUILayout.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(r, out float fR)
|
||||
&& float.TryParse(g, out float fG)
|
||||
&& float.TryParse(b, out float fB)
|
||||
&& float.TryParse(a, out float fA))
|
||||
{
|
||||
Value = new Color(fR, fB, fG, fA);
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
src/CachedObjects/Struct/CacheEnum.cs
Normal file
69
src/CachedObjects/Struct/CacheEnum.cs
Normal 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() + "<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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
126
src/CachedObjects/Struct/CachePrimitive.cs
Normal file
126
src/CachedObjects/Struct/CachePrimitive.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CachePrimitive : CacheObjectBase
|
||||
{
|
||||
private bool m_isBool;
|
||||
private bool m_isString;
|
||||
|
||||
private string m_valueToString;
|
||||
|
||||
public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
|
||||
private MethodInfo m_parseMethod;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (ValueType == null)
|
||||
{
|
||||
ValueType = Value?.GetType();
|
||||
|
||||
// has to be a string at this point
|
||||
if (ValueType == null)
|
||||
{
|
||||
ValueType = typeof(string);
|
||||
}
|
||||
}
|
||||
|
||||
if (ValueType == typeof(string))
|
||||
{
|
||||
m_isString = true;
|
||||
}
|
||||
else if (ValueType == typeof(bool))
|
||||
{
|
||||
m_isBool = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
m_valueToString = Value?.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_isBool)
|
||||
{
|
||||
var b = (bool)Value;
|
||||
var label = $"<color={(b ? "lime" : "red")}>{b}</color>";
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
b = GUILayout.Toggle(b, label, null);
|
||||
if (b != (bool)Value)
|
||||
{
|
||||
SetValueFromInput(b.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(label, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
|
||||
GUILayout.Label("<color=yellow><i>" + ValueType.Name + "</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 valueString)
|
||||
{
|
||||
if (MemInfo == null)
|
||||
{
|
||||
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_isString)
|
||||
{
|
||||
Value = valueString;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Value = ParseMethod.Invoke(null, new object[] { valueString });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
78
src/CachedObjects/Struct/CacheQuaternion.cs
Normal file
78
src/CachedObjects/Struct/CacheQuaternion.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheQuaternion : CacheObjectBase
|
||||
{
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string z = "0";
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
var euler = ((Quaternion)Value).eulerAngles;
|
||||
|
||||
x = euler.x.ToString();
|
||||
y = euler.y.ToString();
|
||||
z = euler.z.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(z, out float fZ))
|
||||
{
|
||||
Value = Quaternion.Euler(new Vector3(fX, fY, fZ));
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/CachedObjects/Struct/CacheRect.cs
Normal file
87
src/CachedObjects/Struct/CacheRect.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheRect : CacheObjectBase
|
||||
{
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string w = "0";
|
||||
private string h = "0";
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
var rect = (Rect)Value;
|
||||
|
||||
x = rect.x.ToString();
|
||||
y = rect.y.ToString();
|
||||
w = rect.width.ToString();
|
||||
h = rect.height.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
h = GUILayout.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(w, out float fW)
|
||||
&& float.TryParse(h, out float fH))
|
||||
{
|
||||
Value = new Rect(fX, fY, fW, fH);
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
src/CachedObjects/Struct/CacheVector.cs
Normal file
142
src/CachedObjects/Struct/CacheVector.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheVector : CacheObjectBase
|
||||
{
|
||||
public int VectorSize = 2;
|
||||
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string z = "0";
|
||||
private string w = "0";
|
||||
|
||||
private MethodInfo m_toStringMethod;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (Value is Vector2)
|
||||
{
|
||||
VectorSize = 2;
|
||||
}
|
||||
else if (Value is Vector3)
|
||||
{
|
||||
VectorSize = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
VectorSize = 4;
|
||||
}
|
||||
|
||||
m_toStringMethod = Value.GetType().GetMethod("ToString", new Type[0]);
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value is Vector2 vec2)
|
||||
{
|
||||
x = vec2.x.ToString();
|
||||
y = vec2.y.ToString();
|
||||
}
|
||||
else if (Value is Vector3 vec3)
|
||||
{
|
||||
x = vec3.x.ToString();
|
||||
y = vec3.y.ToString();
|
||||
z = vec3.z.ToString();
|
||||
}
|
||||
else if (Value is Vector4 vec4)
|
||||
{
|
||||
x = vec4.x.ToString();
|
||||
y = vec4.y.ToString();
|
||||
z = vec4.z.ToString();
|
||||
w = vec4.w.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
// always draw x and y
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (VectorSize > 2)
|
||||
{
|
||||
// draw z
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
if (VectorSize > 3)
|
||||
{
|
||||
// draw w
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(z, out float fZ)
|
||||
&& float.TryParse(w, out float fW))
|
||||
{
|
||||
object vector = null;
|
||||
|
||||
switch (VectorSize)
|
||||
{
|
||||
case 2: vector = new Vector2(fX, fY); break;
|
||||
case 3: vector = new Vector3(fX, fY, fZ); break;
|
||||
case 4: vector = new Vector4(fX, fY, fZ, fW); break;
|
||||
}
|
||||
|
||||
if (vector != null)
|
||||
{
|
||||
Value = vector;
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,83 +2,83 @@
|
||||
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.0.0";
|
||||
public const string GUID = "com.sinai.cppexplorer";
|
||||
public const string VERSION = "1.6.1";
|
||||
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 +86,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
<OutputPath>..\Release\2019\</OutputPath>
|
||||
<DefineConstants>Release_2019</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\2018\</OutputPath>
|
||||
<DefineConstants>Release_Unity2018</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
@ -40,10 +42,6 @@
|
||||
<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>
|
||||
@ -62,74 +60,106 @@
|
||||
<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="Helpers\IExpandHeight.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheColor.cs" />
|
||||
<Compile Include="CachedObjects\Object\CacheDictionary.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
|
||||
<Compile Include="CachedObjects\Object\CacheGameObject.cs" />
|
||||
<Compile Include="CachedObjects\Object\CacheList.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CachePrimitive.cs" />
|
||||
<Compile Include="CachedObjects\Other\CacheOther.cs" />
|
||||
<Compile Include="CachedObjects\Other\CacheMethod.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
|
||||
<Compile Include="CppExplorer.cs" />
|
||||
<Compile Include="Extensions\ReflectionExtensions.cs" />
|
||||
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\ScrollViewStateUnstrip.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="UnstripFixes\SliderHandlerUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\UnstripExtensions.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>
|
@ -8,13 +8,13 @@ EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release_Unity2018|Any CPU = Release_Unity2018|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.ActiveCfg = Release_Unity2018|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.Build.0 = Release_Unity2018|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
16
src/Extensions/ReflectionExtensions.cs
Normal file
16
src/Extensions/ReflectionExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
public static object Il2CppCast(this object obj, Type castTo)
|
||||
{
|
||||
return ReflectionHelpers.Il2CppCast(obj, castTo);
|
||||
}
|
||||
}
|
||||
}
|
30
src/Extensions/UnityExtensions.cs
Normal file
30
src/Extensions/UnityExtensions.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnhollowerBaseLib;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
16
src/Helpers/IExpandHeight.cs
Normal file
16
src/Helpers/IExpandHeight.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
interface IExpandHeight
|
||||
{
|
||||
bool IsExpanded { get; set; }
|
||||
|
||||
float WhiteSpace { get; set; }
|
||||
float ButtonWidthOffset { get; set; }
|
||||
}
|
||||
}
|
109
src/Helpers/PageHelper.cs
Normal file
109
src/Helpers/PageHelper.cs
Normal file
@ -0,0 +1,109 @@
|
||||
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 ItemsPerPage
|
||||
{
|
||||
get => m_itemsPerPage;
|
||||
set
|
||||
{
|
||||
m_itemsPerPage = value;
|
||||
CalculateMaxOffset();
|
||||
}
|
||||
}
|
||||
private int m_itemsPerPage = 20;
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get => m_count;
|
||||
set
|
||||
{
|
||||
m_count = value;
|
||||
CalculateMaxOffset();
|
||||
}
|
||||
}
|
||||
private int m_count;
|
||||
|
||||
public int MaxPageOffset { get; private set; } = -1;
|
||||
|
||||
private int CalculateMaxOffset()
|
||||
{
|
||||
return MaxPageOffset = (int)Mathf.Ceil((float)(ItemCount / (decimal)ItemsPerPage)) - 1;
|
||||
}
|
||||
|
||||
public void CurrentPageLabel()
|
||||
{
|
||||
var orig = GUI.skin.label.alignment;
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUILayout.Label($"Page {PageOffset + 1}/{MaxPageOffset + 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 < MaxPageOffset)
|
||||
{
|
||||
PageOffset++;
|
||||
scroll = Vector2.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int CalculateOffsetIndex()
|
||||
{
|
||||
int offset = PageOffset * ItemsPerPage;
|
||||
|
||||
if (offset >= ItemCount)
|
||||
{
|
||||
offset = 0;
|
||||
PageOffset = 0;
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void DrawLimitInputArea()
|
||||
{
|
||||
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
var limit = this.ItemsPerPage.ToString();
|
||||
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
if (limit != ItemsPerPage.ToString() && int.TryParse(limit, out int i))
|
||||
{
|
||||
ItemsPerPage = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
174
src/Helpers/ReflectionHelpers.cs
Normal file
174
src/Helpers/ReflectionHelpers.cs
Normal file
@ -0,0 +1,174 @@
|
||||
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;
|
||||
using System.Collections;
|
||||
using Mono.CSharp;
|
||||
|
||||
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 IsEnumerable(Type t)
|
||||
{
|
||||
return typeof(IEnumerable).IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
// Only Il2Cpp List needs this check. C# List is IEnumerable.
|
||||
public static bool IsCppList(Type t)
|
||||
{
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDictionary(Type t)
|
||||
{
|
||||
if (typeof(IDictionary).IsAssignableFrom(t))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetTypeByName(string typeName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (var type in GetTypesSafe(asm))
|
||||
{
|
||||
if (type.FullName == typeName)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 IEnumerable<Type> GetTypesSafe(Assembly asm)
|
||||
{
|
||||
try { return asm.GetTypes(); }
|
||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
||||
catch { return Enumerable.Empty<Type>(); }
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
117
src/Helpers/UIHelpers.cs
Normal file
117
src/Helpers/UIHelpers.cs
Normal file
@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class UIHelpers
|
||||
{
|
||||
// helper for "Instantiate" button on UnityEngine.Objects
|
||||
public static void InstantiateButton(Object obj, float width = 100)
|
||||
{
|
||||
if (GUILayout.Button("Instantiate", new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
{
|
||||
var newobj = Object.Instantiate(obj);
|
||||
|
||||
WindowManager.InspectObject(newobj, out _);
|
||||
}
|
||||
}
|
||||
|
||||
// helper for drawing a styled button for a GameObject or Transform
|
||||
public static void GOButton(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||
{
|
||||
var obj = (_obj as GameObject) ?? (_obj as Transform).gameObject;
|
||||
|
||||
bool hasChild = obj.transform.childCount > 0;
|
||||
|
||||
string label = hasChild ? $"[{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;
|
||||
}
|
||||
|
||||
GOButton_Impl(_obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
|
||||
}
|
||||
|
||||
public static void GOButton_Impl(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 _);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
124
src/Helpers/UIStyles.cs
Normal file
124
src/Helpers/UIStyles.cs
Normal file
@ -0,0 +1,124 @@
|
||||
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;
|
||||
var rectOffset = new RectOffset();
|
||||
rectOffset.top = 4;
|
||||
rectOffset.bottom = 4;
|
||||
_horizBarStyle.margin = rectOffset;
|
||||
_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;
|
||||
var rectOffset = new RectOffset();
|
||||
rectOffset.top = 2;
|
||||
rectOffset.bottom = 2;
|
||||
_horizBarSmallStyle.margin = rectOffset;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
34
src/Helpers/UnityHelpers.cs
Normal file
34
src/Helpers/UnityHelpers.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class UnityHelpers
|
||||
{
|
||||
private static Camera m_mainCamera;
|
||||
|
||||
public static Camera MainCamera
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_mainCamera == null)
|
||||
{
|
||||
m_mainCamera = Camera.main;
|
||||
}
|
||||
return m_mainCamera;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ActiveSceneName
|
||||
{
|
||||
get
|
||||
{
|
||||
return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,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;
|
||||
// }
|
||||
//}
|
||||
}
|
@ -1,448 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MelonLoader;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class GameObjectWindow : WindowManager.UIWindow
|
||||
{
|
||||
public override Il2CppSystem.String Name { get => "GameObject Inspector"; set => Name = value; }
|
||||
|
||||
public GameObject m_object;
|
||||
|
||||
// gui element holders
|
||||
private string m_name;
|
||||
private string m_scene;
|
||||
|
||||
private Vector2 m_transformScroll = Vector2.zero;
|
||||
private Transform[] m_children;
|
||||
|
||||
private Vector2 m_compScroll = Vector2.zero;
|
||||
//private Component[] m_components;
|
||||
|
||||
private float m_translateAmount = 0.3f;
|
||||
private float m_rotateAmount = 50f;
|
||||
private float m_scaleAmount = 0.1f;
|
||||
|
||||
List<Component> m_cachedDestroyList = new List<Component>();
|
||||
//private string m_addComponentInput = "";
|
||||
|
||||
private string m_setParentInput = "";
|
||||
|
||||
public bool GetObjectAsGameObject()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
MelonLogger.Log("Target is null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetType = Target.GetType();
|
||||
|
||||
if (targetType == typeof(GameObject))
|
||||
{
|
||||
m_object = Target as GameObject;
|
||||
return true;
|
||||
}
|
||||
else if (targetType == typeof(Transform))
|
||||
{
|
||||
m_object = (Target as Transform).gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
MelonLogger.Log("Error: Target is null or not a GameObject/Transform!");
|
||||
DestroyWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (!GetObjectAsGameObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_name = m_object.name;
|
||||
m_scene = m_object.scene == null ? "null" : m_object.scene.name;
|
||||
|
||||
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
//m_object.GetComponents(listComps);
|
||||
//m_components = listComps.ToArray();
|
||||
|
||||
var list = new List<Transform>();
|
||||
for (int i = 0; i < m_object.transform.childCount; i++)
|
||||
{
|
||||
list.Add(m_object.transform.GetChild(i));
|
||||
}
|
||||
m_children = list.ToArray();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (!m_object && !GetObjectAsGameObject())
|
||||
{
|
||||
MelonLogger.Log("Object is null! Destroying window...");
|
||||
DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void InspectGameObject(GameObject obj)
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height);
|
||||
DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReflectObject(Il2CppSystem.Object obj)
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100))
|
||||
{
|
||||
window.m_rect = new Rect(
|
||||
this.m_rect.x + this.m_rect.width + 20,
|
||||
this.m_rect.y,
|
||||
550,
|
||||
700);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
Header();
|
||||
|
||||
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
||||
|
||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
|
||||
if (m_scene == CppExplorer.ActiveSceneName)
|
||||
{
|
||||
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
|
||||
{
|
||||
ScenePage.Instance.SetTransformTarget(m_object);
|
||||
MainMenu.SetCurrentPage(0);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
string pathlabel = CppExplorer.GetGameObjectPath(m_object.transform);
|
||||
if (m_object.transform.parent != null)
|
||||
{
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
InspectGameObject(m_object.transform.parent.gameObject);
|
||||
}
|
||||
}
|
||||
GUILayout.TextArea(pathlabel, null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
GUILayout.TextArea(m_name, null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// --- Horizontal Columns section ---
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
|
||||
TransformList();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 17) });
|
||||
ComponentList();
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUILayout.EndHorizontal(); // end horiz columns
|
||||
|
||||
GameObjectControls();
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void TransformList()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
||||
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView);
|
||||
|
||||
GUILayout.Label("<b>Children:</b>", null);
|
||||
if (m_children != null && m_children.Length > 0)
|
||||
{
|
||||
foreach (var obj in m_children.Where(x => x.childCount > 0))
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
}
|
||||
foreach (var obj in m_children.Where(x => x.childCount == 0))
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
UIStyles.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<i>None</i>", null);
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
|
||||
private void ComponentList()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
||||
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView);
|
||||
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
m_cachedDestroyList.Clear();
|
||||
}
|
||||
|
||||
var m_components = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
m_object.GetComponentsInternal(Il2CppType.Of<Component>(), false, false, true, false, m_components);
|
||||
|
||||
var ilTypeOfTransform = Il2CppType.Of<Transform>();
|
||||
var ilTypeOfBehaviour = Il2CppType.Of<Behaviour>();
|
||||
foreach (var component in m_components)
|
||||
{
|
||||
var ilType = component.GetIl2CppType();
|
||||
if (ilType == ilTypeOfTransform)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (ilTypeOfBehaviour.IsAssignableFrom(ilType))
|
||||
{
|
||||
BehaviourEnabledBtn(component.TryCast<Behaviour>());
|
||||
}
|
||||
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
|
||||
{
|
||||
ReflectObject(component);
|
||||
}
|
||||
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
m_cachedDestroyList.Add(component);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var comp = m_cachedDestroyList[i];
|
||||
GameObject.Destroy(comp);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
//GUILayout.BeginHorizontal(null);
|
||||
//m_addComponentInput = GUILayout.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 150) });
|
||||
//if (GUILayout.Button("Add Component", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
//{
|
||||
// if (HPExplorer.GetType(m_addComponentInput) is Type type && typeof(Component).IsAssignableFrom(type))
|
||||
// {
|
||||
// var comp = m_object.AddComponent(type);
|
||||
// var list = m_components.ToList();
|
||||
// list.Add(comp);
|
||||
// m_components = list.ToArray();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// MelonLogger.LogWarning($"Could not get type '{m_addComponentInput}'. If it's not a typo, try the fully qualified name.");
|
||||
// }
|
||||
//}
|
||||
//GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void BehaviourEnabledBtn(Behaviour obj)
|
||||
{
|
||||
var _col = GUI.color;
|
||||
bool _enabled = obj.enabled;
|
||||
if (_enabled)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
}
|
||||
|
||||
// ------ toggle active button ------
|
||||
|
||||
_enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
||||
if (obj.enabled != _enabled)
|
||||
{
|
||||
obj.enabled = _enabled;
|
||||
}
|
||||
GUI.color = _col;
|
||||
}
|
||||
|
||||
private void GameObjectControls()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(530) });
|
||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
bool m_active = m_object.activeSelf;
|
||||
m_active = GUILayout.Toggle(m_active, (m_active ? "<color=lime>Enabled " : "<color=red>Disabled") + "</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); }
|
||||
|
||||
UIStyles.InstantiateButton(m_object, 100);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
if (GUILayout.Button("Remove from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
|
||||
{
|
||||
m_object.transform.parent = null;
|
||||
}
|
||||
m_setParentInput = GUILayout.TextField(m_setParentInput, new GUILayoutOption[] { GUILayout.Width(m_rect.width - 280) });
|
||||
if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
if (GameObject.Find(m_setParentInput) is GameObject newparent)
|
||||
{
|
||||
m_object.transform.parent = newparent.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'");
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
|
||||
var t = m_object.transform;
|
||||
TranslateControl(t, TranslateType.Position, ref m_translateAmount, false);
|
||||
TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true);
|
||||
TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false);
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
if (GUILayout.Button("<color=red><b>Destroy</b></color>", null))
|
||||
{
|
||||
GameObject.Destroy(m_object);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
public enum TranslateType
|
||||
{
|
||||
Position,
|
||||
Rotation,
|
||||
Scale
|
||||
}
|
||||
|
||||
private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<color=cyan><b>" + mode + "</b></color>:", new GUILayoutOption[] { GUILayout.Width(65) });
|
||||
|
||||
Vector3 vector = Vector3.zero;
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position: vector = transform.localPosition; break;
|
||||
case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break;
|
||||
case TranslateType.Scale: vector = transform.localScale; break;
|
||||
}
|
||||
GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
|
||||
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.x, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.y, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.z, amount, multByTime);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position: transform.localPosition = vector; break;
|
||||
case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break;
|
||||
case TranslateType.Scale: transform.localScale = vector; break;
|
||||
}
|
||||
|
||||
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
var input = amount.ToString("F3");
|
||||
input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
if (float.TryParse(input, out float f))
|
||||
{
|
||||
amount = f;
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
|
||||
{
|
||||
string s = f.ToString("F3");
|
||||
s = GUILayout.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(s, out float f2))
|
||||
{
|
||||
f = f2;
|
||||
}
|
||||
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f -= multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f += multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,545 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
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().IsAssignableFrom(typeof(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 (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 (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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
87
src/MainMenu/InspectUnderMouse.cs
Normal file
87
src/MainMenu/InspectUnderMouse.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class InspectUnderMouse
|
||||
{
|
||||
public static bool EnableInspect { get; set; } = false;
|
||||
|
||||
private static string m_objUnderMouseName = "";
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (CppExplorer.ShowMenu)
|
||||
{
|
||||
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
|
||||
{
|
||||
EnableInspect = !EnableInspect;
|
||||
}
|
||||
|
||||
if (EnableInspect)
|
||||
{
|
||||
InspectRaycast();
|
||||
}
|
||||
}
|
||||
else if (EnableInspect)
|
||||
{
|
||||
EnableInspect = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InspectRaycast()
|
||||
{
|
||||
Ray ray = UnityHelpers.MainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||
{
|
||||
var obj = hit.transform.gameObject;
|
||||
|
||||
m_objUnderMouseName = obj.transform.GetGameObjectPath();
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
{
|
||||
EnableInspect = false;
|
||||
m_objUnderMouseName = "";
|
||||
|
||||
WindowManager.InspectObject(obj, out _);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_objUnderMouseName = "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnGUI()
|
||||
{
|
||||
if (EnableInspect)
|
||||
{
|
||||
if (m_objUnderMouseName != "")
|
||||
{
|
||||
var pos = Input.mousePosition;
|
||||
var rect = new Rect(
|
||||
pos.x - (Screen.width / 2), // x
|
||||
Screen.height - pos.y - 50, // y
|
||||
Screen.width, // w
|
||||
50 // h
|
||||
);
|
||||
|
||||
var origAlign = GUI.skin.label.alignment;
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
//shadow text
|
||||
GUI.Label(rect, $"<color=black>{m_objUnderMouseName}</color>");
|
||||
//white text
|
||||
GUI.Label(new Rect(rect.x - 1, rect.y + 1, rect.width, rect.height), m_objUnderMouseName);
|
||||
|
||||
GUI.skin.label.alignment = origAlign;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = GUIUnstrip.BeginScrollView(page.scroll);
|
||||
|
||||
page.DrawWindow();
|
||||
|
||||
GUIUnstrip.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>()
|
||||
|
@ -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);
|
||||
//}
|
||||
}
|
||||
}
|
@ -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"; }
|
||||
|
||||
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() { }
|
||||
|
@ -8,26 +8,29 @@ 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; }
|
||||
public override string Name { get => "Scene Explorer"; }
|
||||
|
||||
// ----- Holders for GUI elements ----- //
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private string m_currentScene = "";
|
||||
private float m_timeOfLastUpdate = -1f;
|
||||
private const int PASSIVE_UPDATE_INTERVAL = 1;
|
||||
|
||||
private static bool m_getRootObjectsFailed;
|
||||
|
||||
private static string m_currentScene = "";
|
||||
|
||||
// gameobject list
|
||||
private Transform m_currentTransform;
|
||||
private List<GameObject> m_objectList = new List<GameObject>();
|
||||
private readonly 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>();
|
||||
|
||||
// ------------ Init and Update ------------ //
|
||||
private List<GameObjectCache> m_searchResults = new List<GameObjectCache>();
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
@ -36,157 +39,29 @@ namespace Explorer
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_currentScene = CppExplorer.ActiveSceneName;
|
||||
|
||||
m_currentTransform = null;
|
||||
CancelSearch();
|
||||
|
||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
public void SetTransformTarget(Transform t)
|
||||
{
|
||||
if (!m_searching)
|
||||
{
|
||||
m_objectList = new List<GameObject>();
|
||||
if (m_currentTransform)
|
||||
{
|
||||
var noChildren = new List<GameObject>();
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
var child = m_currentTransform.GetChild(i);
|
||||
m_currentTransform = t;
|
||||
|
||||
if (child)
|
||||
{
|
||||
if (child.childCount > 0)
|
||||
m_objectList.Add(child.gameObject);
|
||||
else
|
||||
noChildren.Add(child.gameObject);
|
||||
}
|
||||
}
|
||||
m_objectList.AddRange(noChildren);
|
||||
noChildren = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var scene = SceneManager.GetActiveScene();
|
||||
var rootObjects = scene.GetRootGameObjects();
|
||||
if (m_searching)
|
||||
CancelSearch();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --------- 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)
|
||||
{
|
||||
m_currentTransform = obj.transform;
|
||||
CancelSearch();
|
||||
Update_Impl(true);
|
||||
}
|
||||
|
||||
public void TraverseUp()
|
||||
{
|
||||
if (m_currentTransform.parent != null)
|
||||
{
|
||||
m_currentTransform = m_currentTransform.parent;
|
||||
SetTransformTarget(m_currentTransform.parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentTransform = null;
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,26 +69,385 @@ namespace Explorer
|
||||
{
|
||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
||||
m_searching = true;
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
}
|
||||
|
||||
public void CancelSearch()
|
||||
{
|
||||
m_searching = false;
|
||||
|
||||
if (m_getRootObjectsFailed && !m_currentTransform)
|
||||
{
|
||||
GetRootObjectsManual_Impl();
|
||||
}
|
||||
}
|
||||
|
||||
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>())
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
|
||||
{
|
||||
if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == CppExplorer.ActiveSceneName)
|
||||
var go = obj.TryCast<GameObject>();
|
||||
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
|
||||
{
|
||||
matches.Add(obj);
|
||||
matches.Add(new GameObjectCache(go));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_searching) return;
|
||||
|
||||
if (Time.time - m_timeOfLastUpdate < PASSIVE_UPDATE_INTERVAL) return;
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
|
||||
private void Update_Impl(bool manual = false)
|
||||
{
|
||||
List<Transform> allTransforms = new List<Transform>();
|
||||
|
||||
// get current list of all transforms (either scene root or our current transform children)
|
||||
if (m_currentTransform)
|
||||
{
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
allTransforms.Add(m_currentTransform.GetChild(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_getRootObjectsFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var scene = SceneManager.GetSceneByName(m_currentScene);
|
||||
|
||||
allTransforms.AddRange(scene.GetRootGameObjects()
|
||||
.Select(it => it.transform));
|
||||
}
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log("Exception getting root scene objects, falling back to backup method...");
|
||||
|
||||
m_getRootObjectsFailed = true;
|
||||
allTransforms.AddRange(GetRootObjectsManual_Impl());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!manual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allTransforms.AddRange(GetRootObjectsManual_Impl());
|
||||
}
|
||||
}
|
||||
|
||||
Pages.ItemCount = allTransforms.Count;
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
// sort by childcount
|
||||
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
|
||||
m_objectList.Clear();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
|
||||
{
|
||||
var child = allTransforms[i];
|
||||
m_objectList.Add(new GameObjectCache(child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Transform> GetRootObjectsManual_Impl()
|
||||
{
|
||||
try
|
||||
{
|
||||
var array = Resources.FindObjectsOfTypeAll(ReflectionHelpers.TransformType);
|
||||
|
||||
var list = new List<Transform>();
|
||||
foreach (var obj in array)
|
||||
{
|
||||
var transform = obj.TryCast<Transform>();
|
||||
if (transform.parent == null && transform.gameObject.scene.name == m_currentScene)
|
||||
{
|
||||
list.Add(transform);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception getting root scene objects (manual): "
|
||||
+ e.GetType() + ", " + e.Message + "\r\n"
|
||||
+ e.StackTrace);
|
||||
return new Transform[0];
|
||||
}
|
||||
}
|
||||
|
||||
// --------- 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.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
}
|
||||
|
||||
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_getRootObjectsFailed)
|
||||
{
|
||||
if (GUILayout.Button("Update Root Object List (auto-update failed!)", null))
|
||||
{
|
||||
Update_Impl(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_objectList.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < m_objectList.Count; i++)
|
||||
{
|
||||
var obj = m_objectList[i];
|
||||
|
||||
if (obj == null) continue;
|
||||
|
||||
if (!obj.RefGameObject)
|
||||
{
|
||||
string label = "<color=red><i>null";
|
||||
|
||||
if (obj.RefGameObject != null)
|
||||
{
|
||||
label += " (Destroyed)";
|
||||
}
|
||||
|
||||
label += "</i></color>";
|
||||
GUILayout.Label(label, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
UIHelpers.GOButton_Impl(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.ItemsPerPage && i < m_searchResults.Count; i++)
|
||||
{
|
||||
var obj = m_searchResults[i];
|
||||
|
||||
if (obj.RefGameObject)
|
||||
{
|
||||
UIHelpers.GOButton_Impl(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"; }
|
||||
|
||||
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.ItemCount = 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,48 @@ 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;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
// prev/next page buttons
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
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 = GUIUnstrip.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 = Pages.CalculateOffsetIndex();
|
||||
|
||||
UIStyles.DrawValue(ref obj, _temprect);
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -99,8 +149,7 @@ namespace Explorer
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch
|
||||
@ -123,16 +172,6 @@ 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;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
@ -204,231 +243,199 @@ 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 searchQuery, string typeName)
|
||||
{
|
||||
Il2CppSystem.Type searchType = null;
|
||||
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ReflectionHelpers.GetTypeByName(typeName) is Type t)
|
||||
{
|
||||
searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Could not find a Type by the name of '{typeName}'!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
if (searchType != null)
|
||||
{
|
||||
MelonLogger.LogWarning("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 (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
|
||||
&& 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 ========
|
||||
|
||||
private static bool FilterName(string name)
|
||||
{
|
||||
// Don't really want these instances.
|
||||
return !name.StartsWith("Mono")
|
||||
&& !name.StartsWith("System")
|
||||
&& !name.StartsWith("Il2CppSystem")
|
||||
&& !name.StartsWith("Iced");
|
||||
}
|
||||
|
||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
||||
public static IEnumerable<object> GetInstanceClassScanner()
|
||||
{
|
||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(x => !x.FullName.StartsWith("Mono"))
|
||||
.SelectMany(GetTypesSafe)
|
||||
.SelectMany(ReflectionHelpers.GetTypesSafe)
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||
var flatFlags = flags | BindingFlags.FlattenHierarchy;
|
||||
|
||||
foreach (var type in query)
|
||||
{
|
||||
object obj = null;
|
||||
try
|
||||
{
|
||||
obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
var pi = type.GetProperty("Instance", flags);
|
||||
|
||||
if (pi == null)
|
||||
{
|
||||
obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null);
|
||||
pi = type.GetProperty("Instance", flatFlags);
|
||||
}
|
||||
catch
|
||||
|
||||
if (pi != null)
|
||||
{
|
||||
obj = pi.GetValue(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fi = type.GetField("Instance", flags);
|
||||
|
||||
if (fi == null)
|
||||
{
|
||||
fi = type.GetField("Instance", flatFlags);
|
||||
}
|
||||
|
||||
if (fi != null)
|
||||
{
|
||||
obj = fi.GetValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj != null && !obj.ToString().StartsWith("Mono"))
|
||||
catch { }
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
var t = ReflectionHelpers.GetActualType(obj);
|
||||
|
||||
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
|
||||
{
|
||||
try { return asm.GetTypes(); }
|
||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
||||
catch { return Enumerable.Empty<Type>(); }
|
||||
}
|
||||
|
||||
// ======= search functions =======
|
||||
|
||||
private void Search()
|
||||
{
|
||||
m_searchResults = FindAllObjectsOfType(m_searchInput, m_typeInput);
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||
{
|
||||
Il2CppSystem.Type type = null;
|
||||
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
try
|
||||
{
|
||||
var findType = CppExplorer.GetType(_type);
|
||||
type = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
22
src/MainMenu/Pages/WindowPage.cs
Normal file
22
src/MainMenu/Pages/WindowPage.cs
Normal 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; }
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
public abstract void Init();
|
||||
|
||||
public abstract void DrawWindow();
|
||||
|
||||
public abstract void Update();
|
||||
}
|
||||
}
|
357
src/UIStyles.cs
357
src/UIStyles.cs
@ -1,357 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
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 (valueType.IsArray || ReflectionWindow.IsList(valueType))
|
||||
{
|
||||
object[] m_array;
|
||||
if (valueType.IsArray)
|
||||
{
|
||||
m_array = (value as Array).Cast<object>().ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_array = (value as IEnumerable).Cast<object>().ToArray();
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (GUILayout.Button("<color=yellow>[" + m_array.Length + "] " + valueType + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(rect.width - 230) }))
|
||||
{
|
||||
WindowManager.InspectObject(value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
for (int i = 0; i < m_array.Length; i++)
|
||||
{
|
||||
var obj = m_array[i];
|
||||
|
||||
// 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>{m_array.Length - 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();
|
||||
DrawMember(ref obj, type.ToString(), i.ToString(), rect, setTarget, setAction, 25, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var label = value.ToString();
|
||||
|
||||
if (valueType == typeof(Object))
|
||||
{
|
||||
label = (value as Object).name;
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
412
src/UnstripFixes/GUIUnstrip.cs
Normal file
412
src/UnstripFixes/GUIUnstrip.cs
Normal file
@ -0,0 +1,412 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using System.Reflection;
|
||||
using UnityEngineInternal;
|
||||
using Harmony;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class GUIUnstrip
|
||||
{
|
||||
public static int s_ScrollControlId;
|
||||
|
||||
public static bool ScrollFailed = false;
|
||||
public static bool ManualUnstripFailed = false;
|
||||
|
||||
private static GenericStack ScrollStack
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_scrollViewStatesInfo == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_scrollViewStatesInfo = typeof(GUI).GetProperty("scrollViewStates");
|
||||
if (m_scrollViewStatesInfo == null) throw new Exception();
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_scrollViewStatesInfo = typeof(GUI).GetProperty("s_scrollViewStates");
|
||||
}
|
||||
}
|
||||
|
||||
return (GenericStack)m_scrollViewStatesInfo?.GetValue(null, null);
|
||||
}
|
||||
}
|
||||
private static PropertyInfo m_scrollViewStatesInfo;
|
||||
|
||||
// ======= public methods ======= //
|
||||
|
||||
public static Rect GetLastRect()
|
||||
{
|
||||
EventType type = Event.current.type;
|
||||
Rect last;
|
||||
if (type != EventType.Layout && type != EventType.Used)
|
||||
{
|
||||
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
|
||||
}
|
||||
else
|
||||
{
|
||||
last = GUILayoutUtility.kDummyRect;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
// Fix for BeginScrollView.
|
||||
|
||||
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
|
||||
{
|
||||
// First, just try normal way, may not have been stripped or was unstripped successfully.
|
||||
if (!ScrollFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GUILayout.BeginScrollView(scroll, options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ScrollFailed = true;
|
||||
return scroll;
|
||||
}
|
||||
}
|
||||
|
||||
// Try manual implementation.
|
||||
if (!ManualUnstripFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
return BeginScrollView_ImplLayout(scroll, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_ImplLayout: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
||||
|
||||
ManualUnstripFailed = true;
|
||||
return scroll;
|
||||
}
|
||||
}
|
||||
|
||||
// Sorry! No scrolling for you.
|
||||
return scroll;
|
||||
}
|
||||
|
||||
public static void EndScrollView(bool handleScrollWheel = true)
|
||||
{
|
||||
// Only end the scroll view for the relevant BeginScrollView option, if any.
|
||||
|
||||
if (!ScrollFailed)
|
||||
{
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
else if (!ManualUnstripFailed)
|
||||
{
|
||||
GUILayoutUtility.EndLayoutGroup();
|
||||
|
||||
EndScrollView_Impl(handleScrollWheel);
|
||||
}
|
||||
}
|
||||
|
||||
// ======= private methods ======= //
|
||||
|
||||
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
|
||||
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
var guiscrollGroup = GUILayoutUtility.BeginLayoutGroup(background, null, Il2CppType.Of<GUIScrollGroup>())
|
||||
.TryCast<GUIScrollGroup>();
|
||||
|
||||
EventType type = Event.current.type;
|
||||
if (type == EventType.Layout)
|
||||
{
|
||||
guiscrollGroup.resetCoords = true;
|
||||
guiscrollGroup.isVertical = true;
|
||||
guiscrollGroup.stretchWidth = 1;
|
||||
guiscrollGroup.stretchHeight = 1;
|
||||
guiscrollGroup.verticalScrollbar = verticalScrollbar;
|
||||
guiscrollGroup.horizontalScrollbar = horizontalScrollbar;
|
||||
guiscrollGroup.needsVerticalScrollbar = alwaysShowVertical;
|
||||
guiscrollGroup.needsHorizontalScrollbar = alwaysShowHorizontal;
|
||||
guiscrollGroup.ApplyOptions(options);
|
||||
}
|
||||
|
||||
return BeginScrollView_Impl(guiscrollGroup.rect,
|
||||
scrollPosition,
|
||||
new Rect(0f, 0f, guiscrollGroup.clientWidth, guiscrollGroup.clientHeight),
|
||||
alwaysShowHorizontal,
|
||||
alwaysShowVertical,
|
||||
horizontalScrollbar,
|
||||
verticalScrollbar,
|
||||
background
|
||||
);
|
||||
}
|
||||
|
||||
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
|
||||
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive);
|
||||
|
||||
var scrollViewState = GUIUtility.GetStateObject(Il2CppType.Of<ScrollViewState>(), controlID).TryCast<ScrollViewState>();
|
||||
|
||||
var scrollExt = ScrollViewStateUnstrip.FromPointer(scrollViewState.Pointer);
|
||||
|
||||
if (scrollExt == null) throw new Exception($"Could not get scrollExt for pointer '{scrollViewState.Pointer}'!");
|
||||
|
||||
bool apply = scrollExt.apply;
|
||||
if (apply)
|
||||
{
|
||||
scrollPosition = scrollExt.scrollPosition;
|
||||
scrollExt.apply = false;
|
||||
}
|
||||
|
||||
scrollExt.position = position;
|
||||
|
||||
scrollExt.scrollPosition = scrollPosition;
|
||||
scrollExt.visibleRect = scrollExt.viewRect = viewRect;
|
||||
|
||||
var rect = scrollExt.visibleRect;
|
||||
rect.width = position.width;
|
||||
rect.height = position.height;
|
||||
|
||||
ScrollStack.Push(scrollViewState);
|
||||
|
||||
Rect screenRect = new Rect(position.x, position.y, position.width, position.height);
|
||||
EventType type = Event.current.type;
|
||||
if (type != EventType.Layout)
|
||||
{
|
||||
if (type != EventType.Used)
|
||||
{
|
||||
bool flag = alwaysShowVertical;
|
||||
bool flag2 = alwaysShowHorizontal;
|
||||
if (flag2 || viewRect.width > screenRect.width)
|
||||
{
|
||||
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
|
||||
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
flag2 = true;
|
||||
}
|
||||
if (flag || viewRect.height > screenRect.height)
|
||||
{
|
||||
rect.width = position.width - verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
|
||||
|
||||
screenRect.width -= verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
|
||||
flag = true;
|
||||
if (!flag2 && viewRect.width > screenRect.width)
|
||||
{
|
||||
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
flag2 = true;
|
||||
}
|
||||
}
|
||||
if (Event.current.type == EventType.Repaint && background != GUIStyle.none)
|
||||
{
|
||||
background.Draw(position, position.Contains(Event.current.mousePosition), false, flag2 && flag, false);
|
||||
}
|
||||
if (flag2 && horizontalScrollbar != GUIStyle.none)
|
||||
{
|
||||
scrollPosition.x = HorizBar_Impl(
|
||||
new Rect(
|
||||
position.x,
|
||||
position.yMax - horizontalScrollbar.fixedHeight,
|
||||
screenRect.width,
|
||||
horizontalScrollbar.fixedHeight),
|
||||
scrollPosition.x,
|
||||
Mathf.Min(screenRect.width, viewRect.width),
|
||||
0f,
|
||||
viewRect.width,
|
||||
horizontalScrollbar
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
scrollPosition.x = ((horizontalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.x, 0f, Mathf.Max(viewRect.width - position.width, 0f)) : 0f);
|
||||
}
|
||||
if (flag && verticalScrollbar != GUIStyle.none)
|
||||
{
|
||||
scrollPosition.y = VertBar_Impl(
|
||||
new Rect(
|
||||
screenRect.xMax + (float)verticalScrollbar.margin.left,
|
||||
screenRect.y,
|
||||
verticalScrollbar.fixedWidth,
|
||||
screenRect.height),
|
||||
scrollPosition.y,
|
||||
Mathf.Min(screenRect.height, viewRect.height),
|
||||
0f,
|
||||
viewRect.height,
|
||||
verticalScrollbar
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
scrollPosition.y = ((verticalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.y, 0f, Mathf.Max(viewRect.height - position.height, 0f)) : 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
}
|
||||
GUIClip.Push(screenRect, new Vector2(Mathf.Round(-scrollPosition.x - viewRect.x), Mathf.Round(-scrollPosition.y - viewRect.y)), Vector2.zero, false);
|
||||
|
||||
return scrollPosition;
|
||||
}
|
||||
|
||||
public static float HorizBar_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
|
||||
{
|
||||
return Scroller_Impl(position, value, size, leftValue, rightValue, style,
|
||||
GUI.skin.GetStyle(style.name + "thumb"),
|
||||
GUI.skin.GetStyle(style.name + "leftbutton"),
|
||||
GUI.skin.GetStyle(style.name + "rightbutton"),
|
||||
true);
|
||||
}
|
||||
|
||||
public static float VertBar_Impl(Rect position, float value, float size, float topValue, float bottomValue, GUIStyle style)
|
||||
{
|
||||
return Scroller_Impl(position, value, size, topValue, bottomValue, style,
|
||||
GUI.skin.GetStyle(style.name + "thumb"),
|
||||
GUI.skin.GetStyle(style.name + "upbutton"),
|
||||
GUI.skin.GetStyle(style.name + "downbutton"),
|
||||
false);
|
||||
}
|
||||
|
||||
private static void EndScrollView_Impl(bool handleScrollWheel)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
if (ScrollStack.Count <= 0) return;
|
||||
|
||||
var state = ScrollStack.Peek().TryCast<ScrollViewState>();
|
||||
var scrollExt = ScrollViewStateUnstrip.FromPointer(state.Pointer);
|
||||
|
||||
if (scrollExt == null) throw new Exception("Could not get scrollExt!");
|
||||
|
||||
GUIClip.Pop();
|
||||
|
||||
ScrollStack.Pop();
|
||||
|
||||
var position = scrollExt.position;
|
||||
|
||||
if (handleScrollWheel && Event.current.type == EventType.ScrollWheel && position.Contains(Event.current.mousePosition))
|
||||
{
|
||||
var pos = scrollExt.scrollPosition;
|
||||
pos.x = Mathf.Clamp(scrollExt.scrollPosition.x + Event.current.delta.x * 20f, 0f, scrollExt.viewRect.width - scrollExt.visibleRect.width);
|
||||
pos.y = Mathf.Clamp(scrollExt.scrollPosition.y + Event.current.delta.y * 20f, 0f, scrollExt.viewRect.height - scrollExt.visibleRect.height);
|
||||
|
||||
if (scrollExt.scrollPosition.x < 0f)
|
||||
{
|
||||
pos.x = 0f;
|
||||
}
|
||||
if (pos.y < 0f)
|
||||
{
|
||||
pos.y = 0f;
|
||||
}
|
||||
|
||||
scrollExt.apply = true;
|
||||
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private static float Scroller_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
int controlID = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
|
||||
Rect position2;
|
||||
Rect rect;
|
||||
Rect rect2;
|
||||
if (horiz)
|
||||
{
|
||||
position2 = new Rect(position.x + leftButton.fixedWidth, position.y, position.width - leftButton.fixedWidth - rightButton.fixedWidth, position.height);
|
||||
rect = new Rect(position.x, position.y, leftButton.fixedWidth, position.height);
|
||||
rect2 = new Rect(position.xMax - rightButton.fixedWidth, position.y, rightButton.fixedWidth, position.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
position2 = new Rect(position.x, position.y + leftButton.fixedHeight, position.width, position.height - leftButton.fixedHeight - rightButton.fixedHeight);
|
||||
rect = new Rect(position.x, position.y, position.width, leftButton.fixedHeight);
|
||||
rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight);
|
||||
}
|
||||
|
||||
value = Slider_Impl(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
|
||||
|
||||
bool flag = Event.current.type == EventType.MouseUp;
|
||||
if (ScrollerRepeatButton_Impl(controlID, rect, leftButton))
|
||||
{
|
||||
value -= 10f * ((leftValue >= rightValue) ? -1f : 1f);
|
||||
}
|
||||
if (ScrollerRepeatButton_Impl(controlID, rect2, rightButton))
|
||||
{
|
||||
value += 10f * ((leftValue >= rightValue) ? -1f : 1f);
|
||||
}
|
||||
if (flag && Event.current.type == EventType.Used)
|
||||
{
|
||||
s_ScrollControlId = 0;
|
||||
}
|
||||
if (leftValue < rightValue)
|
||||
{
|
||||
value = Mathf.Clamp(value, leftValue, rightValue - size);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = Mathf.Clamp(value, rightValue, leftValue - size);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static float Slider_Impl(Rect position, float value, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
id = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
|
||||
}
|
||||
var sliderHandler = new SliderHandlerUnstrip(position, value, size, start, end, slider, thumb, horiz, id);
|
||||
return sliderHandler.Handle();
|
||||
}
|
||||
|
||||
private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style)
|
||||
{
|
||||
bool result = false;
|
||||
if (GUI.DoRepeatButton(rect, GUIContent.none, style, FocusType.Passive))
|
||||
{
|
||||
bool flag = s_ScrollControlId != scrollerID;
|
||||
s_ScrollControlId = scrollerID;
|
||||
|
||||
if (flag)
|
||||
{
|
||||
result = true;
|
||||
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(250.0);
|
||||
}
|
||||
else if (Il2CppSystem.DateTime.Now >= GUI.nextScrollStepTime)
|
||||
{
|
||||
result = true;
|
||||
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(30.0);
|
||||
}
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
GUI.InternalRepaintEditorWindow();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
32
src/UnstripFixes/ScrollViewStateUnstrip.cs
Normal file
32
src/UnstripFixes/ScrollViewStateUnstrip.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Harmony;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ScrollViewStateUnstrip
|
||||
{
|
||||
public Rect position;
|
||||
public Rect visibleRect;
|
||||
public Rect viewRect;
|
||||
public Vector2 scrollPosition;
|
||||
public bool apply;
|
||||
|
||||
public static Dictionary<IntPtr, ScrollViewStateUnstrip> Dict = new Dictionary<IntPtr, ScrollViewStateUnstrip>();
|
||||
|
||||
public static ScrollViewStateUnstrip FromPointer(IntPtr ptr)
|
||||
{
|
||||
if (!Dict.ContainsKey(ptr))
|
||||
{
|
||||
Dict.Add(ptr, new ScrollViewStateUnstrip());
|
||||
}
|
||||
|
||||
return Dict[ptr];
|
||||
}
|
||||
}
|
||||
}
|
371
src/UnstripFixes/SliderHandlerUnstrip.cs
Normal file
371
src/UnstripFixes/SliderHandlerUnstrip.cs
Normal file
@ -0,0 +1,371 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnhollowerRuntimeLib;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public struct SliderHandlerUnstrip
|
||||
{
|
||||
private readonly Rect position;
|
||||
private readonly float currentValue;
|
||||
private readonly float size;
|
||||
private readonly float start;
|
||||
private readonly float end;
|
||||
private readonly GUIStyle slider;
|
||||
private readonly GUIStyle thumb;
|
||||
private readonly bool horiz;
|
||||
private readonly int id;
|
||||
|
||||
public SliderHandlerUnstrip(Rect position, float currentValue, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
|
||||
{
|
||||
this.position = position;
|
||||
this.currentValue = currentValue;
|
||||
this.size = size;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.slider = slider;
|
||||
this.thumb = thumb;
|
||||
this.horiz = horiz;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public float Handle()
|
||||
{
|
||||
float result;
|
||||
if (this.slider == null || this.thumb == null)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (this.CurrentEventType())
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
return this.OnMouseDown();
|
||||
case EventType.MouseUp:
|
||||
return this.OnMouseUp();
|
||||
case EventType.MouseDrag:
|
||||
return this.OnMouseDrag();
|
||||
case EventType.Repaint:
|
||||
return this.OnRepaint();
|
||||
}
|
||||
result = this.currentValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float OnMouseDown()
|
||||
{
|
||||
float result;
|
||||
if (!this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.scrollTroughSide = 0;
|
||||
GUIUtility.hotControl = this.id;
|
||||
this.CurrentEvent().Use();
|
||||
if (this.ThumbSelectionRect().Contains(this.CurrentEvent().mousePosition))
|
||||
{
|
||||
this.StartDraggingWithValue(this.ClampedCurrentValue());
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.changed = true;
|
||||
if (this.SupportsPageMovements())
|
||||
{
|
||||
this.SliderState().isDragging = false;
|
||||
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(250.0);
|
||||
GUI.scrollTroughSide = this.CurrentScrollTroughSide();
|
||||
result = this.PageMovementValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
float num = this.ValueForCurrentMousePosition();
|
||||
this.StartDraggingWithValue(num);
|
||||
result = this.Clamp(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float OnMouseDrag()
|
||||
{
|
||||
float result;
|
||||
if (GUIUtility.hotControl != this.id)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
SliderState sliderState = this.SliderState();
|
||||
if (!sliderState.isDragging)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.changed = true;
|
||||
this.CurrentEvent().Use();
|
||||
float num = this.MousePosition() - sliderState.dragStartPos;
|
||||
float value = sliderState.dragStartValue + num / this.ValuesPerPixel();
|
||||
result = this.Clamp(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float OnMouseUp()
|
||||
{
|
||||
if (GUIUtility.hotControl == this.id)
|
||||
{
|
||||
this.CurrentEvent().Use();
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
return this.currentValue;
|
||||
}
|
||||
|
||||
private float OnRepaint()
|
||||
{
|
||||
this.slider.Draw(this.position, GUIContent.none, this.id);
|
||||
if (!this.IsEmptySlider() && this.currentValue >= this.MinValue() && this.currentValue <= this.MaxValue())
|
||||
{
|
||||
this.thumb.Draw(this.ThumbRect(), GUIContent.none, this.id);
|
||||
}
|
||||
float result;
|
||||
if (GUIUtility.hotControl != this.id || !this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else if (this.ThumbRect().Contains(this.CurrentEvent().mousePosition))
|
||||
{
|
||||
if (GUI.scrollTroughSide != 0)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.InternalRepaintEditorWindow();
|
||||
if (SystemClock.now < GUI.nextScrollStepTime)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else if (this.CurrentScrollTroughSide() != GUI.scrollTroughSide)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(30.0);
|
||||
if (this.SupportsPageMovements())
|
||||
{
|
||||
this.SliderState().isDragging = false;
|
||||
GUI.changed = true;
|
||||
result = this.PageMovementValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this.ClampedCurrentValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private EventType CurrentEventType()
|
||||
{
|
||||
return this.CurrentEvent().GetTypeForControl(this.id);
|
||||
}
|
||||
|
||||
|
||||
private int CurrentScrollTroughSide()
|
||||
{
|
||||
float num = (!this.horiz) ? this.CurrentEvent().mousePosition.y : this.CurrentEvent().mousePosition.x;
|
||||
float num2 = (!this.horiz) ? this.ThumbRect().y : this.ThumbRect().x;
|
||||
return (num <= num2) ? -1 : 1;
|
||||
}
|
||||
|
||||
private bool IsEmptySlider()
|
||||
{
|
||||
return this.start == this.end;
|
||||
}
|
||||
|
||||
private bool SupportsPageMovements()
|
||||
{
|
||||
return this.size != 0f && GUI.usePageScrollbars;
|
||||
}
|
||||
|
||||
private float PageMovementValue()
|
||||
{
|
||||
float num = this.currentValue;
|
||||
int num2 = (this.start <= this.end) ? 1 : -1;
|
||||
if (this.MousePosition() > this.PageUpMovementBound())
|
||||
{
|
||||
num += this.size * (float)num2 * 0.9f;
|
||||
}
|
||||
else
|
||||
{
|
||||
num -= this.size * (float)num2 * 0.9f;
|
||||
}
|
||||
return this.Clamp(num);
|
||||
}
|
||||
|
||||
private float PageUpMovementBound()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = this.ThumbRect().xMax - this.position.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this.ThumbRect().yMax - this.position.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Event CurrentEvent()
|
||||
{
|
||||
return Event.current;
|
||||
}
|
||||
|
||||
private float ValueForCurrentMousePosition()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = (this.MousePosition() - this.ThumbRect().width * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (this.MousePosition() - this.ThumbRect().height * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float Clamp(float value)
|
||||
{
|
||||
return Mathf.Clamp(value, this.MinValue(), this.MaxValue());
|
||||
}
|
||||
|
||||
private Rect ThumbSelectionRect()
|
||||
{
|
||||
return this.ThumbRect();
|
||||
}
|
||||
|
||||
private void StartDraggingWithValue(float dragStartValue)
|
||||
{
|
||||
SliderState sliderState = this.SliderState();
|
||||
sliderState.dragStartPos = this.MousePosition();
|
||||
sliderState.dragStartValue = dragStartValue;
|
||||
sliderState.isDragging = true;
|
||||
}
|
||||
|
||||
private SliderState SliderState()
|
||||
{
|
||||
return (SliderState)GUIUtility.GetStateObject(Il2CppType.Of<SliderState>(), this.id).TryCast<SliderState>();
|
||||
}
|
||||
|
||||
private Rect ThumbRect()
|
||||
{
|
||||
return (!this.horiz) ? this.VerticalThumbRect() : this.HorizontalThumbRect();
|
||||
}
|
||||
|
||||
private Rect VerticalThumbRect()
|
||||
{
|
||||
float num = this.ValuesPerPixel();
|
||||
Rect result;
|
||||
if (this.start < this.end)
|
||||
{
|
||||
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * num + this.ThumbSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() + this.size - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * -num + this.ThumbSize());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Rect HorizontalThumbRect()
|
||||
{
|
||||
float num = this.ValuesPerPixel();
|
||||
Rect result;
|
||||
if (this.start < this.end)
|
||||
{
|
||||
result = new Rect((this.ClampedCurrentValue() - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y + (float)this.slider.padding.top, this.size * num + this.ThumbSize(), this.position.height - (float)this.slider.padding.vertical);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Rect((this.ClampedCurrentValue() + this.size - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y, this.size * -num + this.ThumbSize(), this.position.height);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float ClampedCurrentValue()
|
||||
{
|
||||
return this.Clamp(this.currentValue);
|
||||
}
|
||||
|
||||
private float MousePosition()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = this.CurrentEvent().mousePosition.x - this.position.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this.CurrentEvent().mousePosition.y - this.position.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float ValuesPerPixel()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = (this.position.width - (float)this.slider.padding.horizontal - this.ThumbSize()) / (this.end - this.start);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (this.position.height - (float)this.slider.padding.vertical - this.ThumbSize()) / (this.end - this.start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float ThumbSize()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = ((this.thumb.fixedWidth == 0f) ? ((float)this.thumb.padding.horizontal) : this.thumb.fixedWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ((this.thumb.fixedHeight == 0f) ? ((float)this.thumb.padding.vertical) : this.thumb.fixedHeight);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float MaxValue()
|
||||
{
|
||||
return Mathf.Max(this.start, this.end) - this.size;
|
||||
}
|
||||
|
||||
private float MinValue()
|
||||
{
|
||||
return Mathf.Min(this.start, this.end);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
27
src/UnstripFixes/UnstripExtensions.cs
Normal file
27
src/UnstripFixes/UnstripExtensions.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class UnstripExtensions
|
||||
{
|
||||
public static Rect GetLastUnstripped(this GUILayoutGroup group)
|
||||
{
|
||||
Rect result;
|
||||
if (group.m_Cursor > 0 && group.m_Cursor <= group.entries.Count)
|
||||
{
|
||||
GUILayoutEntry guilayoutEntry = group.entries[group.m_Cursor - 1];
|
||||
result = guilayoutEntry.rect;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = GUILayoutEntry.kDummyRect;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
652
src/Windows/GameObjectWindow.cs
Normal file
652
src/Windows/GameObjectWindow.cs
Normal file
@ -0,0 +1,652 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MelonLoader;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class GameObjectWindow : UIWindow
|
||||
{
|
||||
public override string Title => WindowManager.TabView
|
||||
? $"<color=cyan>[G]</color> {m_object.name}"
|
||||
: $"GameObject Inspector ({m_object.name})";
|
||||
|
||||
public GameObject m_object;
|
||||
|
||||
// gui element holders
|
||||
private string m_name;
|
||||
private string m_scene;
|
||||
|
||||
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 PageHelper CompPages = new PageHelper();
|
||||
|
||||
private readonly Vector3[] m_cachedInput = new Vector3[3];
|
||||
private float m_translateAmount = 0.3f;
|
||||
private float m_rotateAmount = 50f;
|
||||
private float m_scaleAmount = 0.1f;
|
||||
private bool m_freeze;
|
||||
private Vector3 m_frozenPosition;
|
||||
private Quaternion m_frozenRotation;
|
||||
private Vector3 m_frozenScale;
|
||||
private bool m_autoApplyTransform;
|
||||
private bool m_autoUpdateTransform;
|
||||
private bool m_localContext;
|
||||
|
||||
private readonly List<Component> m_cachedDestroyList = new List<Component>();
|
||||
//private string m_addComponentInput = "";
|
||||
|
||||
private string m_setParentInput = "Enter a GameObject name or path";
|
||||
|
||||
public bool GetObjectAsGameObject()
|
||||
{
|
||||
var targetType = Target.GetType();
|
||||
|
||||
if (targetType == typeof(GameObject))
|
||||
{
|
||||
m_object = Target as GameObject;
|
||||
return true;
|
||||
}
|
||||
else if (targetType == typeof(Transform))
|
||||
{
|
||||
m_object = (Target as Transform).gameObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
MelonLogger.Log("Error: Target is null or not a GameObject/Transform!");
|
||||
DestroyWindow();
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (!GetObjectAsGameObject())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_name = m_object.name;
|
||||
m_scene = string.IsNullOrEmpty(m_object.scene.name)
|
||||
? "None (Asset/Resource)"
|
||||
: m_object.scene.name;
|
||||
|
||||
CacheTransformValues();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private void CacheTransformValues()
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_cachedInput[0] = m_object.transform.localPosition;
|
||||
m_cachedInput[1] = m_object.transform.localEulerAngles;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedInput[0] = m_object.transform.position;
|
||||
m_cachedInput[1] = m_object.transform.eulerAngles;
|
||||
}
|
||||
m_cachedInput[2] = m_object.transform.localScale;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
MelonLogger.Log("Target is null!");
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
else if (Target is UnityEngine.Object uObj)
|
||||
{
|
||||
if (!uObj)
|
||||
{
|
||||
MelonLogger.Log("Target was destroyed!");
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_object && !GetObjectAsGameObject())
|
||||
{
|
||||
throw new Exception("Object is null!");
|
||||
}
|
||||
|
||||
if (m_freeze)
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_object.transform.localPosition = m_frozenPosition;
|
||||
m_object.transform.localRotation = m_frozenRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_object.transform.position = m_frozenPosition;
|
||||
m_object.transform.rotation = m_frozenRotation;
|
||||
}
|
||||
m_object.transform.localScale = m_frozenScale;
|
||||
}
|
||||
|
||||
// update child objects
|
||||
var childList = new List<Transform>();
|
||||
for (int i = 0; i < m_object.transform.childCount; i++)
|
||||
{
|
||||
childList.Add(m_object.transform.GetChild(i));
|
||||
}
|
||||
childList.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
m_children = childList.ToArray();
|
||||
|
||||
ChildPages.ItemCount = m_children.Length;
|
||||
|
||||
// update components
|
||||
var compList = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
|
||||
|
||||
m_components = compList.ToArray();
|
||||
|
||||
CompPages.ItemCount = m_components.Length;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DestroyOnException(e);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (created)
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x, this.m_rect.y, this.m_rect.width, this.m_rect.height);
|
||||
DestroyWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReflectObject(Il2CppSystem.Object obj)
|
||||
{
|
||||
var window = WindowManager.InspectObject(obj, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
if (this.m_rect.x <= (Screen.width - this.m_rect.width - 100))
|
||||
{
|
||||
window.m_rect = new Rect(
|
||||
this.m_rect.x + this.m_rect.width + 20,
|
||||
this.m_rect.y,
|
||||
550,
|
||||
700);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.m_rect = new Rect(this.m_rect.x + 50, this.m_rect.y + 50, 550, 700);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void WindowFunction(int windowID)
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
scroll = GUIUnstrip.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();
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DestroyOnException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void TransformList(Rect m_rect)
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
m_transformScroll = GUIUnstrip.BeginScrollView(m_transformScroll);
|
||||
|
||||
GUILayout.Label("<b><size=15>Children</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
ChildPages.DrawLimitInputArea();
|
||||
|
||||
if (ChildPages.ItemCount > ChildPages.ItemsPerPage)
|
||||
{
|
||||
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();
|
||||
|
||||
if (m_children != null && m_children.Length > 0)
|
||||
{
|
||||
int start = ChildPages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + ChildPages.ItemsPerPage && j < ChildPages.ItemCount); j++)
|
||||
{
|
||||
var obj = m_children[j];
|
||||
|
||||
if (!obj)
|
||||
{
|
||||
GUILayout.Label("null", null);
|
||||
continue;
|
||||
}
|
||||
|
||||
UIHelpers.GOButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<i>None</i>", null);
|
||||
}
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ComponentList(Rect m_rect)
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
m_compScroll = GUIUnstrip.BeginScrollView(m_compScroll);
|
||||
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
CompPages.DrawLimitInputArea();
|
||||
|
||||
if (CompPages.ItemCount > CompPages.ItemsPerPage)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
if (m_components != null)
|
||||
{
|
||||
int start = CompPages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + CompPages.ItemsPerPage && j < CompPages.ItemCount); j++)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
if (m_cachedDestroyList.Count > 0)
|
||||
{
|
||||
for (int i = m_cachedDestroyList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var comp = m_cachedDestroyList[i];
|
||||
GameObject.Destroy(comp);
|
||||
}
|
||||
}
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void BehaviourEnabledBtn(Behaviour obj)
|
||||
{
|
||||
var _col = GUI.color;
|
||||
bool _enabled = obj.enabled;
|
||||
if (_enabled)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
}
|
||||
|
||||
// ------ toggle active button ------
|
||||
|
||||
_enabled = GUILayout.Toggle(_enabled, "", new GUILayoutOption[] { GUILayout.Width(18) });
|
||||
if (obj.enabled != _enabled)
|
||||
{
|
||||
obj.enabled = _enabled;
|
||||
}
|
||||
GUI.color = _col;
|
||||
}
|
||||
|
||||
private void GameObjectControls()
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
|
||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
bool m_active = m_object.activeSelf;
|
||||
m_active = GUILayout.Toggle(m_active, (m_active ? "<color=lime>Enabled " : "<color=red>Disabled") + "</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); }
|
||||
|
||||
UIHelpers.InstantiateButton(m_object, 100);
|
||||
|
||||
if (GUILayout.Button("Set DontDestroyOnLoad", new GUILayoutOption[] { GUILayout.Width(170) }))
|
||||
{
|
||||
GameObject.DontDestroyOnLoad(m_object);
|
||||
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||
}
|
||||
|
||||
var lbl = m_freeze ? "<color=lime>Unfreeze</color>" : "<color=orange>Freeze Pos/Rot</color>";
|
||||
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(110) }))
|
||||
{
|
||||
m_freeze = !m_freeze;
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
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)
|
||||
{
|
||||
m_object.transform.parent = newparent.transform;
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
|
||||
m_cachedInput[0] = TranslateControl(TranslateType.Position, ref m_translateAmount, false);
|
||||
m_cachedInput[1] = TranslateControl(TranslateType.Rotation, ref m_rotateAmount, true);
|
||||
m_cachedInput[2] = TranslateControl(TranslateType.Scale, ref m_scaleAmount, false);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button("<color=lime>Apply to Transform</color>", null) || m_autoApplyTransform)
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_object.transform.localPosition = m_cachedInput[0];
|
||||
m_object.transform.localEulerAngles = m_cachedInput[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_object.transform.position = m_cachedInput[0];
|
||||
m_object.transform.eulerAngles = m_cachedInput[1];
|
||||
}
|
||||
m_object.transform.localScale = m_cachedInput[2];
|
||||
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("<color=lime>Update from Transform</color>", null) || m_autoUpdateTransform)
|
||||
{
|
||||
CacheTransformValues();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?");
|
||||
BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
bool b = m_localContext;
|
||||
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "red") + ">Use local transform values?</color>", null);
|
||||
if (b != m_localContext)
|
||||
{
|
||||
m_localContext = b;
|
||||
CacheTransformValues();
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
if (GUILayout.Button("<color=red><b>Destroy</b></color>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
GameObject.Destroy(m_object);
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void UpdateFreeze()
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_frozenPosition = m_object.transform.localPosition;
|
||||
m_frozenRotation = m_object.transform.localRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_frozenPosition = m_object.transform.position;
|
||||
m_frozenRotation = m_object.transform.rotation;
|
||||
}
|
||||
m_frozenScale = m_object.transform.localScale;
|
||||
}
|
||||
|
||||
private void BoolToggle(ref bool value, string message)
|
||||
{
|
||||
string lbl = "<color=";
|
||||
lbl += value ? "lime" : "red";
|
||||
lbl += $">{message}</color>";
|
||||
|
||||
value = GUILayout.Toggle(value, lbl, null);
|
||||
}
|
||||
|
||||
public enum TranslateType
|
||||
{
|
||||
Position,
|
||||
Rotation,
|
||||
Scale
|
||||
}
|
||||
|
||||
private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label($"<color=cyan><b>{(m_localContext ? "Local " : "")}{mode}</b></color>:",
|
||||
new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) });
|
||||
|
||||
var transform = m_object.transform;
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position:
|
||||
var pos = m_localContext ? transform.localPosition : transform.position;
|
||||
GUILayout.Label(pos.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
case TranslateType.Rotation:
|
||||
var rot = m_localContext ? transform.localEulerAngles : transform.eulerAngles;
|
||||
GUILayout.Label(rot.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
case TranslateType.Scale:
|
||||
GUILayout.Label(transform.localScale.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
Vector3 input = m_cachedInput[(int)mode];
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
|
||||
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref input.x, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref input.y, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref input.z, amount, multByTime);
|
||||
|
||||
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
var amountInput = amount.ToString("F3");
|
||||
amountInput = GUILayout.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(amountInput, out float f))
|
||||
{
|
||||
amount = f;
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
|
||||
{
|
||||
string s = f.ToString("F3");
|
||||
s = GUILayout.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(s, out float f2))
|
||||
{
|
||||
f = f2;
|
||||
}
|
||||
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f -= multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||
{
|
||||
f += multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
372
src/Windows/ReflectionWindow.cs
Normal file
372
src/Windows/ReflectionWindow.cs
Normal file
@ -0,0 +1,372 @@
|
||||
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 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()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
else if (Target is UnityEngine.Object uObj)
|
||||
{
|
||||
if (!uObj)
|
||||
{
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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.ItemCount = m_cachedMembersFiltered.Length;
|
||||
|
||||
// prev/next page buttons
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
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 = GUIUnstrip.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.ItemsPerPage && 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.ItemsPerPage - 1) || j == (members.Length - 1)))
|
||||
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUIUnstrip.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;
|
||||
}
|
||||
}
|
||||
}
|
108
src/Windows/ResizeDrag.cs
Normal file
108
src/Windows/ResizeDrag.cs
Normal file
@ -0,0 +1,108 @@
|
||||
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();
|
||||
var r = GUIUnstrip.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);
|
||||
MelonLogger.Log(e.StackTrace);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
117
src/Windows/TabViewWindow.cs
Normal file
117
src/Windows/TabViewWindow.cs
Normal file
@ -0,0 +1,117 @@
|
||||
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
77
src/Windows/UIWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
228
src/Windows/WindowManager.cs
Normal file
228
src/Windows/WindowManager.cs
Normal 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().FullName != iTarget.GetIl2CppType().FullName)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
/// <summary>
|
||||
/// AccessTools
|
||||
/// Some helpers for Reflection (GetValue, SetValue, Call, InheritBaseValues)
|
||||
/// </summary>
|
||||
public static class At
|
||||
{
|
||||
public static Il2CppSystem.Reflection.BindingFlags ilFlags = Il2CppSystem.Reflection.BindingFlags.Public
|
||||
| Il2CppSystem.Reflection.BindingFlags.NonPublic
|
||||
| Il2CppSystem.Reflection.BindingFlags.Instance
|
||||
| Il2CppSystem.Reflection.BindingFlags.Static;
|
||||
|
||||
public static BindingFlags flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
|
||||
//reflection call
|
||||
public static object Call(object obj, string method, params object[] args)
|
||||
{
|
||||
var methodInfo = obj.GetType().GetMethod(method, flags);
|
||||
if (methodInfo != null)
|
||||
{
|
||||
return methodInfo.Invoke(obj, args);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// set value
|
||||
public static void SetValue<T>(T value, Type type, object obj, string field)
|
||||
{
|
||||
FieldInfo fieldInfo = type.GetField(field, flags);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
fieldInfo.SetValue(obj, value);
|
||||
}
|
||||
}
|
||||
|
||||
// get value
|
||||
public static object GetValue(Type type, object obj, string value)
|
||||
{
|
||||
FieldInfo fieldInfo = type.GetField(value, flags);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
return fieldInfo.GetValue(obj);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// inherit base values
|
||||
public static void InheritBaseValues(object _derived, object _base)
|
||||
{
|
||||
foreach (FieldInfo fi in _base.GetType().GetFields(flags))
|
||||
{
|
||||
try { _derived.GetType().GetField(fi.Name).SetValue(_derived, fi.GetValue(_base)); } catch { }
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user