mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-05 04:42:54 +08:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
82
README.md
82
README.md
@ -1,12 +1,23 @@
|
|||||||
|
# CppExplorer []()
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img align="center" src="https://i.imgur.com/1ZoZemW.png">
|
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[]()
|
<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>
|
||||||
|
|
||||||
An in-game explorer and a suite of debugging tools for [IL2CPP](https://docs.unity3d.com/Manual/IL2CPP.html) Unity games, using [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader).
|
<a href="../../releases/latest">
|
||||||
|
<img src="https://img.shields.io/github/release/sinai-dev/CppExplorer.svg" />
|
||||||
|
</a>
|
||||||
|
|
||||||
Most games running on Unity 2017 to 2019 should be supported. If you find that the GUI does not display properly and you get errors in the MelonLoader console about it, then this is likely due to a bug with Il2CppAssemblyUnhollower's unstripping. This bug is known by the developer of the tool and they will fix it as soon as they are able to.
|
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Known issue
|
||||||
|
Some games are experiencing `MissingMethodException`s or exceptions about failed unstripping, which prevent the CppExplorer menu from showing properly or at all. This is a bug with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower) and there isn't much I can do about it myself.
|
||||||
|
|
||||||
|
If you're familiar with C# and Unity, one possibility for now is making a fork of this repo and manually fixing all the broken methods to ones which aren't broken (if possible). There may be another overload of the same method which wasn't stripped or was unstripped successfully, which you can use instead.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Scene hierarchy explorer
|
* Scene hierarchy explorer
|
||||||
@ -27,32 +38,65 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
|
|||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
* Press F7 to show or hide the menu.
|
* 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.
|
||||||
|
|
||||||
### Help! I can't use the mouse!
|
### Scene Explorer
|
||||||
|
|
||||||
It is fairly common for games to override mouse control with their own mouse behaviour. Unfortunately, it's not feasible for CppExplorer to handle this due to how differently every game will go about it.
|
* 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.
|
||||||
|
|
||||||
In order to fix this problem, you can:
|
[](https://i.imgur.com/2b0q0jL.png)
|
||||||
* Use [VRCExplorerMouseControl](https://github.com/sinaioutlander/VRCExplorerMouseControl) (for VRChat)
|
|
||||||
* Use [HPExplorerMouseControl](https://github.com/sinaioutlander/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl) (for Hellpoint)
|
|
||||||
* In general, pressing Escape (to open a menu) will usually give you temporary control over the mouse.
|
|
||||||
* Create your own mini-plugin using one of the two plugins above as an example. Usually only 1 or 2 simple Harmony patches are needed to fix the problem.
|
|
||||||
|
|
||||||
## Images
|
### Inspectors
|
||||||
|
|
||||||
Scene explorer, and inspection of a MonoBehaviour object:
|
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
|
||||||
|
|
||||||
[](https://i.imgur.com/Yxizwcz.png)
|
<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.
|
||||||
|
|
||||||
Search feature:
|
### GameObject Inspector
|
||||||
|
|
||||||
[](https://i.imgur.com/F9ZfMvz.png)
|
* Allows you to see the children and components on a GameObject.
|
||||||
|
* Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
|
||||||
|
|
||||||
|
[](https://i.imgur.com/JTxqlx4.png)
|
||||||
|
|
||||||
C# REPL console:
|
### Reflection Inspector
|
||||||
|
|
||||||
[](https://i.imgur.com/14Dbtf8.png)
|
* The Reflection Inspector is used for all other supported objects.
|
||||||
|
* Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods.
|
||||||
|
* Can search and filter members for the ones you are interested in.
|
||||||
|
|
||||||
|
[](https://i.imgur.com/iq92m0l.png)
|
||||||
|
|
||||||
|
### 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
|
## Credits
|
||||||
|
|
||||||
|
34
src/CachedObjects/CacheDictionary.cs
Normal file
34
src/CachedObjects/CacheDictionary.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheDictionary : CacheObjectBase
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
//base.Init();
|
||||||
|
|
||||||
|
Value = "Unsupported";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue()
|
||||||
|
{
|
||||||
|
//base.UpdateValue();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
GUILayout.Label("<color=red>Dictionary (unsupported)</color>", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,25 +3,41 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public class CacheEnum : CacheObject
|
public class CacheEnum : CacheObjectBase
|
||||||
{
|
{
|
||||||
private readonly Type m_enumType;
|
public Type EnumType;
|
||||||
private readonly string[] m_names;
|
public string[] EnumNames;
|
||||||
|
|
||||||
public CacheEnum(object obj)
|
public override void Init()
|
||||||
{
|
{
|
||||||
m_enumType = obj.GetType();
|
try
|
||||||
m_names = Enum.GetNames(obj.GetType());
|
{
|
||||||
|
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)
|
public override void DrawValue(Rect window, float width)
|
||||||
{
|
{
|
||||||
if (MemberInfo != null)
|
if (CanWrite)
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||||
{
|
{
|
||||||
@ -35,34 +51,18 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.Label(Value.ToString(), null);
|
GUILayout.Label(Value.ToString(), null);// + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetValue()
|
|
||||||
{
|
|
||||||
if (MemberInfo == null)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Enum.Parse(m_enumType, Value.ToString()) is object enumValue && enumValue != null)
|
|
||||||
{
|
|
||||||
Value = enumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetValue(Value, MemberInfo, DeclaringInstance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetEnum(ref object value, int change)
|
public void SetEnum(ref object value, int change)
|
||||||
{
|
{
|
||||||
var names = m_names.ToList();
|
var names = EnumNames.ToList();
|
||||||
|
|
||||||
int newindex = names.IndexOf(value.ToString()) + change;
|
int newindex = names.IndexOf(value.ToString()) + change;
|
||||||
|
|
||||||
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
|
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
|
||||||
{
|
{
|
||||||
value = Enum.Parse(m_enumType, names[newindex]);
|
value = Enum.Parse(EnumType, names[newindex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,46 +8,16 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public class CacheGameObject : CacheObject
|
public class CacheGameObject : CacheObjectBase
|
||||||
{
|
{
|
||||||
private GameObject m_gameObject;
|
|
||||||
|
|
||||||
public CacheGameObject(object obj)
|
|
||||||
{
|
|
||||||
if (obj != null)
|
|
||||||
m_gameObject = GetGameObject(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
private GameObject GetGameObject(object obj)
|
|
||||||
{
|
|
||||||
if (obj is Il2CppSystem.Object ilObj)
|
|
||||||
{
|
|
||||||
var ilType = ilObj.GetIl2CppType();
|
|
||||||
|
|
||||||
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
|
|
||||||
{
|
|
||||||
return ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DrawValue(Rect window, float width)
|
public override void DrawValue(Rect window, float width)
|
||||||
{
|
{
|
||||||
UIHelpers.GameobjButton(m_gameObject, null, false, width);
|
UIHelpers.GameobjButton(Value, null, false, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetValue()
|
public override void UpdateValue()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("TODO");
|
base.UpdateValue();
|
||||||
}
|
|
||||||
|
|
||||||
public override void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
base.UpdateValue(obj);
|
|
||||||
|
|
||||||
m_gameObject = GetGameObject(Value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public class CacheIl2CppObject : CacheObject
|
|
||||||
{
|
|
||||||
public override void DrawValue(Rect window, float width)
|
|
||||||
{
|
|
||||||
var label = ValueType ?? Value.ToString();
|
|
||||||
if (!label.Contains(ValueType))
|
|
||||||
{
|
|
||||||
label += $" ({ValueType})";
|
|
||||||
}
|
|
||||||
if (Value is UnityEngine.Object unityObj)
|
|
||||||
{
|
|
||||||
label = unityObj.name + " | " + label;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(width) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(Value, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetValue()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("TODO");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,44 +3,247 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using MelonLoader;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public partial class CacheList : CacheObject
|
public class CacheList : CacheObjectBase
|
||||||
{
|
{
|
||||||
public bool IsExpanded { get; set; }
|
public bool IsExpanded { get; set; }
|
||||||
public int ArrayOffset { get; set; }
|
public PageHelper Pages = new PageHelper();
|
||||||
public Type EntryType { get; set; }
|
|
||||||
|
|
||||||
private IEnumerable m_enumerable;
|
public float WhiteSpace = 215f;
|
||||||
private CacheObject[] m_cachedEntries;
|
public float ButtonWidthOffset = 290f;
|
||||||
|
|
||||||
public CacheList(object obj)
|
private CacheObjectBase[] m_cachedEntries;
|
||||||
|
|
||||||
|
// Type of Entries in the Array
|
||||||
|
public Type EntryType
|
||||||
{
|
{
|
||||||
GetEnumerable(obj);
|
get => GetEntryType();
|
||||||
EntryType = m_enumerable.GetType().GetGenericArguments()[0];
|
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 void GetEnumerable(object obj)
|
private Type GetGenericTypeDef()
|
||||||
{
|
{
|
||||||
if (obj is IEnumerable isEnumerable)
|
if (m_genericTypeDef == null && Value != null)
|
||||||
{
|
{
|
||||||
m_enumerable = isEnumerable;
|
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
|
else
|
||||||
{
|
{
|
||||||
var listValueType = obj.GetType().GetGenericArguments()[0];
|
return ConvertIListToMono();
|
||||||
var listType = typeof(Il2CppSystem.Collections.Generic.List<>).MakeGenericType(new Type[] { listValueType });
|
|
||||||
var method = listType.GetMethod("ToArray");
|
|
||||||
m_enumerable = (IEnumerable)method.Invoke(obj, new object[0]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IList ConvertIListToMono()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.EntryType });
|
||||||
|
var list = (IList)Activator.CreateInstance(genericType);
|
||||||
|
|
||||||
|
for (int i = 0; ; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var itm = ItemProperty.GetValue(Value, new object[] { i });
|
||||||
|
list.Add(itm);
|
||||||
|
}
|
||||||
|
catch { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type GetEntryType()
|
||||||
|
{
|
||||||
|
if (m_entryType == null)
|
||||||
|
{
|
||||||
|
if (this.MemInfo != null)
|
||||||
|
{
|
||||||
|
Type memberType = null;
|
||||||
|
switch (this.MemInfo.MemberType)
|
||||||
|
{
|
||||||
|
case MemberTypes.Field:
|
||||||
|
memberType = (MemInfo as FieldInfo).FieldType;
|
||||||
|
break;
|
||||||
|
case MemberTypes.Property:
|
||||||
|
memberType = (MemInfo as PropertyInfo).PropertyType;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memberType != null && memberType.IsGenericType)
|
||||||
|
{
|
||||||
|
m_entryType = memberType.GetGenericArguments()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Value != null)
|
||||||
|
{
|
||||||
|
var type = Value.GetType();
|
||||||
|
if (type.IsGenericType)
|
||||||
|
{
|
||||||
|
m_entryType = type.GetGenericArguments()[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IList probably won't be able to get any EntryType.
|
||||||
|
if (m_entryType == null)
|
||||||
|
{
|
||||||
|
m_entryType = typeof(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_entryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue()
|
||||||
|
{
|
||||||
|
base.UpdateValue();
|
||||||
|
|
||||||
|
if (Value == null || Enumerable == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var enumerator = Enumerable.GetEnumerator();
|
||||||
|
if (enumerator == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = new List<CacheObjectBase>();
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
var obj = enumerator.Current;
|
||||||
|
|
||||||
|
if (obj != null && ReflectionHelpers.GetActualType(obj) is Type t)
|
||||||
|
{
|
||||||
|
if (obj is Il2CppSystem.Object iObj)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cast = iObj.Il2CppCast(t);
|
||||||
|
if (cast != null)
|
||||||
|
{
|
||||||
|
obj = cast;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetCacheObject(obj, t) is CacheObjectBase cached)
|
||||||
|
{
|
||||||
|
list.Add(cached);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list.Add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cachedEntries = list.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============= GUI Draw =============
|
||||||
|
|
||||||
public override void DrawValue(Rect window, float width)
|
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;
|
int count = m_cachedEntries.Length;
|
||||||
|
|
||||||
if (!IsExpanded)
|
if (!IsExpanded)
|
||||||
@ -60,94 +263,86 @@ namespace Explorer
|
|||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>";
|
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>";
|
||||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) }))
|
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
|
||||||
{
|
{
|
||||||
WindowManager.InspectObject(Value, out bool _);
|
WindowManager.InspectObject(Value, out bool _);
|
||||||
}
|
}
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
|
GUILayout.Space(5);
|
||||||
|
|
||||||
if (IsExpanded)
|
if (IsExpanded)
|
||||||
{
|
{
|
||||||
if (count > CppExplorer.ArrayLimit)
|
float whitespace = WhiteSpace;
|
||||||
|
if (whitespace > 0)
|
||||||
|
{
|
||||||
|
ClampLabelWidth(window, ref whitespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pages.Count = count;
|
||||||
|
|
||||||
|
if (count > Pages.PageLimit)
|
||||||
{
|
{
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
GUILayout.Space(190);
|
|
||||||
int maxOffset = (int)Mathf.Ceil(count / CppExplorer.ArrayLimit);
|
GUILayout.Space(whitespace);
|
||||||
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
|
||||||
|
//int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
|
||||||
|
Pages.CalculateMaxOffset();
|
||||||
|
|
||||||
|
//GUILayout.Label($"Page {PH.ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||||
|
Pages.CurrentPageLabel();
|
||||||
|
|
||||||
// prev/next page buttons
|
// prev/next page buttons
|
||||||
if (GUILayout.Button("< Prev", null))
|
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||||
{
|
{
|
||||||
if (ArrayOffset > 0) ArrayOffset--;
|
Pages.TurnPage(Turn.Left);
|
||||||
}
|
}
|
||||||
if (GUILayout.Button("Next >", null))
|
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||||
{
|
{
|
||||||
if (ArrayOffset < maxOffset) ArrayOffset++;
|
Pages.TurnPage(Turn.Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pages.DrawLimitInputArea();
|
||||||
|
|
||||||
|
GUILayout.Space(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
int offset = ArrayOffset * CppExplorer.ArrayLimit;
|
//int offset = ArrayOffset * ArrayLimit;
|
||||||
|
//if (offset >= count)
|
||||||
|
//{
|
||||||
|
// offset = 0;
|
||||||
|
// ArrayOffset = 0;
|
||||||
|
//}
|
||||||
|
int offset = Pages.CalculateOffsetIndex();
|
||||||
|
|
||||||
if (offset >= count) offset = 0;
|
for (int i = offset; i < offset + Pages.PageLimit && i < count; i++)
|
||||||
|
|
||||||
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
|
|
||||||
{
|
{
|
||||||
var entry = m_cachedEntries[i];
|
var entry = m_cachedEntries[i];
|
||||||
|
|
||||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
GUILayout.Space(190);
|
|
||||||
|
|
||||||
if (entry == null)
|
GUILayout.Space(whitespace);
|
||||||
|
|
||||||
|
if (entry == null || entry.Value == null)
|
||||||
{
|
{
|
||||||
GUILayout.Label("<i><color=grey>null</color></i>", null);
|
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(30) });
|
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||||
|
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||||
|
|
||||||
entry.DrawValue(window, window.width - 250);
|
entry.DrawValue(window, window.width - (whitespace + 85));
|
||||||
|
|
||||||
//var lbl = i + ": <color=cyan>" + obj.Value.ToString() + "</color>";
|
|
||||||
|
|
||||||
//if (EntryType.IsPrimitive || typeof(string).IsAssignableFrom(EntryType))
|
|
||||||
//{
|
|
||||||
// GUILayout.Label(lbl, null);
|
|
||||||
//}
|
|
||||||
//else
|
|
||||||
//{
|
|
||||||
// GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
// if (GUILayout.Button(lbl, null))
|
|
||||||
// {
|
|
||||||
// WindowManager.InspectObject(obj, out _);
|
|
||||||
// }
|
|
||||||
// GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetValue()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("TODO");
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
GetEnumerable(Value);
|
|
||||||
|
|
||||||
var list = new List<CacheObject>();
|
|
||||||
|
|
||||||
var enumerator = m_enumerable.GetEnumerator();
|
|
||||||
|
|
||||||
while (enumerator.MoveNext())
|
|
||||||
{
|
|
||||||
list.Add(GetCacheObject(enumerator.Current));
|
|
||||||
}
|
|
||||||
|
|
||||||
m_cachedEntries = list.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
223
src/CachedObjects/CacheMethod.cs
Normal file
223
src/CachedObjects/CacheMethod.cs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine;
|
||||||
|
using MelonLoader;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class CacheMethod : CacheObjectBase
|
||||||
|
{
|
||||||
|
private bool m_evaluated = false;
|
||||||
|
private CacheObjectBase m_cachedReturnValue;
|
||||||
|
|
||||||
|
private bool m_isEvaluating;
|
||||||
|
private ParameterInfo[] m_arguments;
|
||||||
|
private string[] m_argumentInput;
|
||||||
|
|
||||||
|
public bool HasParameters
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (m_hasParams == null)
|
||||||
|
{
|
||||||
|
m_hasParams = (MemInfo as MethodInfo).GetParameters().Length > 0;
|
||||||
|
}
|
||||||
|
return (bool)m_hasParams;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private bool? m_hasParams;
|
||||||
|
|
||||||
|
public static bool CanEvaluate(MethodInfo mi)
|
||||||
|
{
|
||||||
|
// generic type args not supported yet
|
||||||
|
if (mi.GetGenericArguments().Length > 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only primitive and string args supported
|
||||||
|
foreach (var param in mi.GetParameters())
|
||||||
|
{
|
||||||
|
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
base.Init();
|
||||||
|
|
||||||
|
var mi = MemInfo as MethodInfo;
|
||||||
|
|
||||||
|
m_arguments = mi.GetParameters();
|
||||||
|
m_argumentInput = new string[m_arguments.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void UpdateValue()
|
||||||
|
{
|
||||||
|
//base.UpdateValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void DrawValue(Rect window, float width)
|
||||||
|
{
|
||||||
|
GUILayout.BeginVertical(null);
|
||||||
|
|
||||||
|
string evaluateLabel = "<color=lime>Evaluate</color>";
|
||||||
|
if (HasParameters)
|
||||||
|
{
|
||||||
|
if (m_isEvaluating)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_arguments.Length; i++)
|
||||||
|
{
|
||||||
|
var name = m_arguments[i].Name;
|
||||||
|
var input = m_argumentInput[i];
|
||||||
|
var type = m_arguments[i].ParameterType.Name;
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||||
|
GUILayout.Label(i + ": <color=cyan>" + name + "</color> <color=yellow>(" + type + ")</color>", null);
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||||
|
{
|
||||||
|
Evaluate();
|
||||||
|
m_isEvaluating = false;
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||||
|
{
|
||||||
|
m_isEvaluating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||||
|
{
|
||||||
|
m_isEvaluating = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||||
|
{
|
||||||
|
Evaluate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
if (m_evaluated)
|
||||||
|
{
|
||||||
|
if (m_cachedReturnValue != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_cachedReturnValue.DrawValue(window, width);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Exception drawing m_cachedReturnValue!");
|
||||||
|
MelonLogger.Log(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueTypeName}</color>)", null);
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Evaluate()
|
||||||
|
{
|
||||||
|
var mi = MemInfo as MethodInfo;
|
||||||
|
|
||||||
|
object ret = null;
|
||||||
|
|
||||||
|
if (!HasParameters)
|
||||||
|
{
|
||||||
|
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
|
||||||
|
m_evaluated = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var arguments = new List<object>();
|
||||||
|
for (int i = 0; i < m_arguments.Length; i++)
|
||||||
|
{
|
||||||
|
var input = m_argumentInput[i];
|
||||||
|
var type = m_arguments[i].ParameterType;
|
||||||
|
|
||||||
|
if (type == typeof(string))
|
||||||
|
{
|
||||||
|
arguments.Add(input);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
|
||||||
|
{
|
||||||
|
arguments.Add(parsed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments.Count == m_arguments.Length)
|
||||||
|
{
|
||||||
|
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, arguments.ToArray());
|
||||||
|
m_evaluated = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MelonLogger.Log($"Did not invoke because {m_arguments.Length - arguments.Count} arguments could not be parsed!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != null)
|
||||||
|
{
|
||||||
|
m_cachedReturnValue = GetCacheObject(ret);
|
||||||
|
if (m_cachedReturnValue is CacheList cacheList)
|
||||||
|
{
|
||||||
|
cacheList.WhiteSpace = 0f;
|
||||||
|
cacheList.ButtonWidthOffset += 70f;
|
||||||
|
}
|
||||||
|
m_cachedReturnValue.UpdateValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_cachedReturnValue = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,194 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MelonLoader;
|
|
||||||
using UnhollowerBaseLib;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace Explorer
|
|
||||||
{
|
|
||||||
public abstract class CacheObject
|
|
||||||
{
|
|
||||||
public object Value;
|
|
||||||
public string ValueType;
|
|
||||||
|
|
||||||
// Reflection window only
|
|
||||||
public MemberInfo MemberInfo { get; set; }
|
|
||||||
public ReflectionWindow.MemberInfoType MemberInfoType { get; set; }
|
|
||||||
public Type DeclaringType { get; set; }
|
|
||||||
public object DeclaringInstance { get; set; }
|
|
||||||
|
|
||||||
public abstract void DrawValue(Rect window, float width);
|
|
||||||
public abstract void SetValue();
|
|
||||||
|
|
||||||
public static CacheObject GetCacheObject(object obj)
|
|
||||||
{
|
|
||||||
return GetCacheObject(obj, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CacheObject GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
|
|
||||||
{
|
|
||||||
CacheObject holder;
|
|
||||||
|
|
||||||
var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
|
|
||||||
|
|
||||||
if (obj is Il2CppSystem.Object || typeof(Il2CppSystem.Object).IsAssignableFrom(type))
|
|
||||||
{
|
|
||||||
var name = type.FullName;
|
|
||||||
if (name == "UnityEngine.GameObject" || name == "UnityEngine.Transform")
|
|
||||||
{
|
|
||||||
holder = new CacheGameObject(obj);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
holder = new CacheIl2CppObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (type.IsPrimitive || type == typeof(string))
|
|
||||||
{
|
|
||||||
holder = new CachePrimitive(obj);
|
|
||||||
}
|
|
||||||
else if (type.IsEnum)
|
|
||||||
{
|
|
||||||
holder = new CacheEnum(obj);
|
|
||||||
}
|
|
||||||
else if (typeof(System.Collections.IEnumerable).IsAssignableFrom(type) || ReflectionHelpers.IsList(type))
|
|
||||||
{
|
|
||||||
holder = new CacheList(obj);
|
|
||||||
}
|
|
||||||
else if (type.IsValueType)
|
|
||||||
{
|
|
||||||
holder = new CacheStruct(obj);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
holder = new CacheOther();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (holder == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memberInfo != null)
|
|
||||||
{
|
|
||||||
holder.MemberInfo = memberInfo;
|
|
||||||
holder.DeclaringType = memberInfo.DeclaringType;
|
|
||||||
|
|
||||||
if (declaringInstance is Il2CppSystem.Object ilInstance && ilInstance.GetType() != memberInfo.DeclaringType)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
holder.DeclaringInstance = ilInstance.Il2CppCast(holder.DeclaringType);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
holder.DeclaringInstance = declaringInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
holder.DeclaringInstance = declaringInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (memberInfo.MemberType == MemberTypes.Field)
|
|
||||||
{
|
|
||||||
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Field;
|
|
||||||
}
|
|
||||||
else if (memberInfo.MemberType == MemberTypes.Property)
|
|
||||||
{
|
|
||||||
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Property;
|
|
||||||
}
|
|
||||||
else if (memberInfo.MemberType == MemberTypes.Method)
|
|
||||||
{
|
|
||||||
holder.MemberInfoType = ReflectionWindow.MemberInfoType.Method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.Value = obj;
|
|
||||||
holder.ValueType = type.FullName;
|
|
||||||
|
|
||||||
return holder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Draw(Rect window, float labelWidth = 180f)
|
|
||||||
{
|
|
||||||
if (MemberInfo != null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<color=cyan>" + MemberInfo.Name + ":</color>", new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Space(labelWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Value == null)
|
|
||||||
{
|
|
||||||
GUILayout.Label("<i>null (" + this.ValueType + ")</i>", null);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DrawValue(window, window.width - labelWidth - 90);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
if (MemberInfo == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (MemberInfo.MemberType == MemberTypes.Field)
|
|
||||||
{
|
|
||||||
var fi = MemberInfo as FieldInfo;
|
|
||||||
Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
|
||||||
}
|
|
||||||
else if (MemberInfo.MemberType == MemberTypes.Property)
|
|
||||||
{
|
|
||||||
var pi = MemberInfo as PropertyInfo;
|
|
||||||
Value = pi.GetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch //(Exception e)
|
|
||||||
{
|
|
||||||
//MelonLogger.Log($"Error updating MemberInfo value | {e.GetType()}: {e.Message}\r\n{e.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetValue(object value, MemberInfo memberInfo, object declaringInstance)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (memberInfo.MemberType == MemberTypes.Field)
|
|
||||||
{
|
|
||||||
var fi = memberInfo as FieldInfo;
|
|
||||||
if (!(fi.IsLiteral && !fi.IsInitOnly))
|
|
||||||
{
|
|
||||||
fi.SetValue(fi.IsStatic ? null : declaringInstance, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (memberInfo.MemberType == MemberTypes.Property)
|
|
||||||
{
|
|
||||||
var pi = memberInfo as PropertyInfo;
|
|
||||||
if (pi.CanWrite)
|
|
||||||
{
|
|
||||||
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : declaringInstance, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
345
src/CachedObjects/CacheObjectBase.cs
Normal file
345
src/CachedObjects/CacheObjectBase.cs
Normal file
@ -0,0 +1,345 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public abstract class CacheObjectBase
|
||||||
|
{
|
||||||
|
public object Value;
|
||||||
|
public string ValueTypeName;
|
||||||
|
|
||||||
|
// Reflection Inspector only
|
||||||
|
public MemberInfo MemInfo { get; set; }
|
||||||
|
public Type DeclaringType { get; set; }
|
||||||
|
public object DeclaringInstance { get; set; }
|
||||||
|
public string ReflectionException { get; set; }
|
||||||
|
|
||||||
|
public int PropertyIndex { get; private set; }
|
||||||
|
private string m_propertyIndexInput = "0";
|
||||||
|
|
||||||
|
public string RichTextName => m_richTextName ?? GetRichTextName();
|
||||||
|
private string m_richTextName;
|
||||||
|
|
||||||
|
public bool CanWrite
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (MemInfo is FieldInfo fi)
|
||||||
|
return !(fi.IsLiteral && !fi.IsInitOnly);
|
||||||
|
else if (MemInfo is PropertyInfo pi)
|
||||||
|
return pi.CanWrite;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Abstract/Virtual Methods ===== //
|
||||||
|
|
||||||
|
public virtual void Init() { }
|
||||||
|
|
||||||
|
public abstract void DrawValue(Rect window, float width);
|
||||||
|
|
||||||
|
// ===== Static Methods ===== //
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get CacheObject from only an object instance
|
||||||
|
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
|
||||||
|
public static CacheObjectBase GetCacheObject(object obj)
|
||||||
|
{
|
||||||
|
return GetCacheObject(obj, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get CacheObject from an object instance and provide the value type
|
||||||
|
/// Calls GetCacheObjectImpl directly</summary>
|
||||||
|
public static CacheObjectBase GetCacheObject(object obj, Type valueType)
|
||||||
|
{
|
||||||
|
return GetCacheObjectImpl(obj, null, null, valueType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get CacheObject from only a MemberInfo and declaring instance
|
||||||
|
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (null, memberInfo, declaringInstance)</summary>
|
||||||
|
public static CacheObjectBase GetCacheObject(MemberInfo memberInfo, object declaringInstance)
|
||||||
|
{
|
||||||
|
return GetCacheObject(null, memberInfo, declaringInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get CacheObject from either an object or MemberInfo, and don't provide the type.
|
||||||
|
/// This gets the type and then calls GetCacheObjectImpl</summary>
|
||||||
|
public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
|
||||||
|
{
|
||||||
|
Type type = null;
|
||||||
|
|
||||||
|
if (obj != null)
|
||||||
|
{
|
||||||
|
type = ReflectionHelpers.GetActualType(obj);
|
||||||
|
}
|
||||||
|
else if (memberInfo != null)
|
||||||
|
{
|
||||||
|
if (memberInfo is FieldInfo fi)
|
||||||
|
{
|
||||||
|
type = fi.FieldType;
|
||||||
|
}
|
||||||
|
else if (memberInfo is PropertyInfo pi)
|
||||||
|
{
|
||||||
|
type = pi.PropertyType;
|
||||||
|
}
|
||||||
|
else if (memberInfo is MethodInfo mi)
|
||||||
|
{
|
||||||
|
type = mi.ReturnType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetCacheObjectImpl(obj, memberInfo, declaringInstance, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actual GetCacheObject implementation (private)
|
||||||
|
/// </summary>
|
||||||
|
private static CacheObjectBase GetCacheObjectImpl(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
|
||||||
|
{
|
||||||
|
CacheObjectBase holder;
|
||||||
|
|
||||||
|
if (memberInfo is MethodInfo mi)
|
||||||
|
{
|
||||||
|
if (CacheMethod.CanEvaluate(mi))
|
||||||
|
{
|
||||||
|
holder = new CacheMethod();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
|
||||||
|
{
|
||||||
|
holder = new CacheGameObject();
|
||||||
|
}
|
||||||
|
else if (valueType.IsPrimitive || valueType == typeof(string))
|
||||||
|
{
|
||||||
|
holder = new CachePrimitive();
|
||||||
|
}
|
||||||
|
else if (valueType.IsEnum)
|
||||||
|
{
|
||||||
|
holder = new CacheEnum();
|
||||||
|
}
|
||||||
|
else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType))
|
||||||
|
{
|
||||||
|
holder = new CacheList();
|
||||||
|
}
|
||||||
|
else if (ReflectionHelpers.IsDictionary(valueType))
|
||||||
|
{
|
||||||
|
holder = new CacheDictionary();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
holder = new CacheOther();
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.Value = obj;
|
||||||
|
holder.ValueTypeName = valueType.FullName;
|
||||||
|
|
||||||
|
if (memberInfo != null)
|
||||||
|
{
|
||||||
|
holder.MemInfo = memberInfo;
|
||||||
|
holder.DeclaringType = memberInfo.DeclaringType;
|
||||||
|
holder.DeclaringInstance = declaringInstance;
|
||||||
|
|
||||||
|
holder.UpdateValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.Init();
|
||||||
|
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== Instance Methods =========
|
||||||
|
|
||||||
|
public virtual void UpdateValue()
|
||||||
|
{
|
||||||
|
if (MemInfo == null || !string.IsNullOrEmpty(ReflectionException))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (MemInfo.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
var fi = MemInfo as FieldInfo;
|
||||||
|
Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
|
||||||
|
}
|
||||||
|
else if (MemInfo.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
var pi = MemInfo as PropertyInfo;
|
||||||
|
bool isStatic = pi.GetAccessors()[0].IsStatic;
|
||||||
|
var target = isStatic ? null : DeclaringInstance;
|
||||||
|
|
||||||
|
if (pi.GetIndexParameters().Length > 0)
|
||||||
|
{
|
||||||
|
var indexes = new object[] { PropertyIndex };
|
||||||
|
Value = pi.GetValue(target, indexes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Value = pi.GetValue(target, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ReflectionException = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
ReflectionException = ReflectionHelpers.ExceptionToString(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (MemInfo.MemberType == MemberTypes.Field)
|
||||||
|
{
|
||||||
|
var fi = MemInfo as FieldInfo;
|
||||||
|
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, Value);
|
||||||
|
}
|
||||||
|
else if (MemInfo.MemberType == MemberTypes.Property)
|
||||||
|
{
|
||||||
|
var pi = MemInfo as PropertyInfo;
|
||||||
|
|
||||||
|
if (pi.GetIndexParameters().Length > 0)
|
||||||
|
{
|
||||||
|
var indexes = new object[] { PropertyIndex };
|
||||||
|
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, indexes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========= Instance Gui Draw ==========
|
||||||
|
|
||||||
|
public const float MAX_LABEL_WIDTH = 400f;
|
||||||
|
|
||||||
|
public static void ClampLabelWidth(Rect window, ref float labelWidth)
|
||||||
|
{
|
||||||
|
float min = window.width * 0.37f;
|
||||||
|
if (min > MAX_LABEL_WIDTH) min = MAX_LABEL_WIDTH;
|
||||||
|
|
||||||
|
labelWidth = Mathf.Clamp(labelWidth, min, MAX_LABEL_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Draw(Rect window, float labelWidth = 215f)
|
||||||
|
{
|
||||||
|
if (labelWidth > 0)
|
||||||
|
{
|
||||||
|
ClampLabelWidth(window, ref labelWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MemInfo != null)
|
||||||
|
{
|
||||||
|
var name = RichTextName;
|
||||||
|
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
|
||||||
|
{
|
||||||
|
name += $"[{PropertyIndex}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label(name, new GUILayoutOption[] { GUILayout.Width(labelWidth) });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Space(labelWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(ReflectionException))
|
||||||
|
{
|
||||||
|
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
|
||||||
|
}
|
||||||
|
else if (Value == null && MemInfo?.MemberType != MemberTypes.Method)
|
||||||
|
{
|
||||||
|
GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
|
||||||
|
{
|
||||||
|
GUILayout.Label("index:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||||
|
|
||||||
|
m_propertyIndexInput = GUILayout.TextField(m_propertyIndexInput, new GUILayoutOption[] { GUILayout.Width(100) });
|
||||||
|
if (GUILayout.Button("Set", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||||
|
{
|
||||||
|
if (int.TryParse(m_propertyIndexInput, out int i))
|
||||||
|
{
|
||||||
|
PropertyIndex = i;
|
||||||
|
UpdateValue();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MelonLogger.Log($"Could not parse '{m_propertyIndexInput}' to an int!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// new line and space
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUILayout.Space(labelWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawValue(window, window.width - labelWidth - 90);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRichTextName()
|
||||||
|
{
|
||||||
|
string memberColor = "";
|
||||||
|
switch (MemInfo.MemberType)
|
||||||
|
{
|
||||||
|
case MemberTypes.Field:
|
||||||
|
memberColor = "#c266ff"; break;
|
||||||
|
case MemberTypes.Property:
|
||||||
|
memberColor = "#72a6a6"; break;
|
||||||
|
case MemberTypes.Method:
|
||||||
|
memberColor = "#ff8000"; break;
|
||||||
|
};
|
||||||
|
|
||||||
|
m_richTextName = $"<color=#2df7b2>{MemInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemInfo.Name}</color>";
|
||||||
|
|
||||||
|
if (MemInfo is MethodInfo mi)
|
||||||
|
{
|
||||||
|
m_richTextName += "(";
|
||||||
|
var _params = "";
|
||||||
|
foreach (var param in mi.GetParameters())
|
||||||
|
{
|
||||||
|
if (_params != "") _params += ", ";
|
||||||
|
|
||||||
|
_params += $"<color=#a6e9e9>{param.Name}</color>";
|
||||||
|
}
|
||||||
|
m_richTextName += _params;
|
||||||
|
m_richTextName += ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_richTextName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +1,60 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Reflection;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public class CacheOther : CacheObject
|
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)
|
public override void DrawValue(Rect window, float width)
|
||||||
{
|
{
|
||||||
string label;
|
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
|
||||||
if (Value is UnityEngine.Object uObj)
|
|
||||||
{
|
|
||||||
label = uObj.name;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
label = Value.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
string typeLabel = Value.GetType().FullName;
|
if (!label.Contains(ValueTypeName))
|
||||||
|
|
||||||
if (!label.Contains(typeLabel))
|
|
||||||
{
|
{
|
||||||
label += $" ({typeLabel})";
|
label += $" ({ValueTypeName})";
|
||||||
|
}
|
||||||
|
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
|
||||||
|
{
|
||||||
|
label = unityObj.name + " | " + label;
|
||||||
}
|
}
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 230) }))
|
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||||
{
|
{
|
||||||
WindowManager.InspectObject(Value, out bool _);
|
WindowManager.InspectObject(Value, out bool _);
|
||||||
}
|
}
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetValue()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void UpdateValue(object obj)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,105 +1,181 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Reflection;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public class CachePrimitive : CacheObject
|
public class CachePrimitive : CacheObjectBase
|
||||||
{
|
{
|
||||||
public enum PrimitiveType
|
public enum PrimitiveTypes
|
||||||
{
|
{
|
||||||
Bool,
|
Bool,
|
||||||
Double,
|
Double,
|
||||||
Float,
|
Float,
|
||||||
Int,
|
Int,
|
||||||
String
|
String,
|
||||||
|
Char
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly PrimitiveType m_primitiveType;
|
private string m_valueToString;
|
||||||
|
|
||||||
public CachePrimitive(object obj)
|
public PrimitiveTypes PrimitiveType;
|
||||||
|
|
||||||
|
public MethodInfo ParseMethod
|
||||||
{
|
{
|
||||||
if (obj is bool)
|
get
|
||||||
{
|
{
|
||||||
m_primitiveType = PrimitiveType.Bool;
|
if (m_parseMethod == null)
|
||||||
|
{
|
||||||
|
m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) });
|
||||||
|
}
|
||||||
|
return m_parseMethod;
|
||||||
}
|
}
|
||||||
else if (obj is double)
|
}
|
||||||
|
|
||||||
|
private MethodInfo m_parseMethod;
|
||||||
|
|
||||||
|
public override void Init()
|
||||||
|
{
|
||||||
|
if (Value == null)
|
||||||
{
|
{
|
||||||
m_primitiveType = PrimitiveType.Double;
|
// this must mean it is a string. No other primitive type should be nullable.
|
||||||
|
PrimitiveType = PrimitiveTypes.String;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (obj is float)
|
|
||||||
|
m_valueToString = Value.ToString();
|
||||||
|
var type = Value.GetType();
|
||||||
|
|
||||||
|
if (type == typeof(bool))
|
||||||
{
|
{
|
||||||
m_primitiveType = PrimitiveType.Float;
|
PrimitiveType = PrimitiveTypes.Bool;
|
||||||
}
|
}
|
||||||
else if (obj is int)
|
else if (type == typeof(double))
|
||||||
{
|
{
|
||||||
m_primitiveType = PrimitiveType.Int;
|
PrimitiveType = PrimitiveTypes.Double;
|
||||||
}
|
}
|
||||||
else if (obj is string)
|
else if (type == typeof(float))
|
||||||
{
|
{
|
||||||
m_primitiveType = PrimitiveType.String;
|
PrimitiveType = PrimitiveTypes.Float;
|
||||||
}
|
}
|
||||||
|
else if (IsInteger(type))
|
||||||
|
{
|
||||||
|
PrimitiveType = PrimitiveTypes.Int;
|
||||||
|
}
|
||||||
|
else if (type == typeof(char))
|
||||||
|
{
|
||||||
|
PrimitiveType = PrimitiveTypes.Char;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrimitiveType = PrimitiveTypes.String;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsInteger(Type type)
|
||||||
|
{
|
||||||
|
// For our purposes, all types of int can be treated the same, including IntPtr.
|
||||||
|
return _integerTypes.Contains(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly HashSet<Type> _integerTypes = new HashSet<Type>
|
||||||
|
{
|
||||||
|
typeof(int),
|
||||||
|
typeof(uint),
|
||||||
|
typeof(short),
|
||||||
|
typeof(ushort),
|
||||||
|
typeof(long),
|
||||||
|
typeof(ulong),
|
||||||
|
typeof(byte),
|
||||||
|
typeof(sbyte),
|
||||||
|
typeof(IntPtr)
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void UpdateValue()
|
||||||
|
{
|
||||||
|
base.UpdateValue();
|
||||||
|
|
||||||
|
m_valueToString = Value?.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DrawValue(Rect window, float width)
|
public override void DrawValue(Rect window, float width)
|
||||||
{
|
{
|
||||||
if (m_primitiveType == PrimitiveType.Bool && Value is bool b)
|
if (PrimitiveType == PrimitiveTypes.Bool)
|
||||||
{
|
{
|
||||||
var color = "<color=" + (b ? "lime>" : "red>");
|
var b = (bool)Value;
|
||||||
Value = GUILayout.Toggle((bool)Value, color + Value.ToString() + "</color>", null);
|
var color = $"<color={(b ? "lime>" : "red>")}";
|
||||||
|
var label = $"{color}{b}</color>";
|
||||||
|
|
||||||
if (b != (bool)Value)
|
if (CanWrite)
|
||||||
{
|
{
|
||||||
SetValue();
|
b = GUILayout.Toggle(b, label, null);
|
||||||
|
if (b != (bool)Value)
|
||||||
|
{
|
||||||
|
SetValueFromInput(b.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label(label, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var toString = Value.ToString();
|
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||||
if (toString.Length > 37)
|
|
||||||
|
int dynSize = 25 + (m_valueToString.Length * 15);
|
||||||
|
var maxwidth = window.width - 300f;
|
||||||
|
if (CanWrite) maxwidth -= 60;
|
||||||
|
|
||||||
|
if (dynSize > maxwidth)
|
||||||
{
|
{
|
||||||
Value = GUILayout.TextArea(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
|
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Value = GUILayout.TextField(toString, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 260) });
|
m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MemberInfo != null)
|
if (CanWrite)
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
|
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||||
{
|
{
|
||||||
SetValue();
|
SetValueFromInput(m_valueToString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GUILayout.Space(5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetValue()
|
public void SetValueFromInput(string value)
|
||||||
{
|
{
|
||||||
if (MemberInfo == null)
|
if (MemInfo == null)
|
||||||
{
|
{
|
||||||
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m_primitiveType)
|
if (PrimitiveType == PrimitiveTypes.String)
|
||||||
{
|
{
|
||||||
case PrimitiveType.Bool:
|
Value = value;
|
||||||
SetValue(bool.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
|
||||||
case PrimitiveType.Double:
|
|
||||||
SetValue(double.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
|
||||||
case PrimitiveType.Float:
|
|
||||||
SetValue(float.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
|
||||||
case PrimitiveType.Int:
|
|
||||||
SetValue(int.Parse(Value.ToString()), MemberInfo, DeclaringInstance); return;
|
|
||||||
case PrimitiveType.String:
|
|
||||||
SetValue(Value.ToString(), MemberInfo, DeclaringInstance); return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var val = ParseMethod.Invoke(null, new object[] { value });
|
||||||
|
Value = val;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
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 CacheStruct : CacheObject
|
|
||||||
{
|
|
||||||
public MethodInfo ToStringMethod { get; private set; }
|
|
||||||
private static readonly MethodInfo m_defaultToString = typeof(object).GetMethod("ToString");
|
|
||||||
|
|
||||||
public CacheStruct(object obj)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var methods = obj.GetType().GetMethods(ReflectionHelpers.CommonFlags).Where(x => x.Name == "ToString");
|
|
||||||
var enumerator = methods.GetEnumerator();
|
|
||||||
while (enumerator.MoveNext())
|
|
||||||
{
|
|
||||||
ToStringMethod = enumerator.Current;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
ToStringMethod = m_defaultToString;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void DrawValue(Rect window, float width)
|
|
||||||
{
|
|
||||||
string label;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
label = (string)ToStringMethod.Invoke(Value, null);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
label = Value.ToString();
|
|
||||||
}
|
|
||||||
string typeLabel = Value.GetType().FullName;
|
|
||||||
|
|
||||||
if (!label.Contains(typeLabel))
|
|
||||||
{
|
|
||||||
label += $" ({typeLabel})";
|
|
||||||
}
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
|
||||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(window.width - 230) }))
|
|
||||||
{
|
|
||||||
WindowManager.InspectObject(Value, out bool _);
|
|
||||||
}
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void SetValue()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException("TODO");
|
|
||||||
}
|
|
||||||
|
|
||||||
//public override void UpdateValue(object obj)
|
|
||||||
//{
|
|
||||||
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
using System.Reflection;
|
||||||
|
using Harmony;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public class CppExplorer : MelonMod
|
public class CppExplorer : MelonMod
|
||||||
{
|
{
|
||||||
// consts
|
public const string GUID = "com.sinai.cppexplorer";
|
||||||
|
public const string VERSION = "1.5.3";
|
||||||
public const string ID = "com.sinai.cppexplorer";
|
|
||||||
public const string VERSION = "1.4.0";
|
|
||||||
public const string AUTHOR = "Sinai";
|
public const string AUTHOR = "Sinai";
|
||||||
|
|
||||||
public const string NAME = "CppExplorer"
|
public const string NAME = "CppExplorer"
|
||||||
@ -21,27 +21,52 @@ namespace Explorer
|
|||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
// fields
|
public static CppExplorer Instance { get; private set; }
|
||||||
|
|
||||||
public static CppExplorer Instance;
|
public static bool ShowMenu
|
||||||
|
{
|
||||||
|
get => m_showMenu;
|
||||||
|
set => SetShowMenu(value);
|
||||||
|
}
|
||||||
|
private static bool m_showMenu;
|
||||||
|
|
||||||
// props
|
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 ShowMenu { get; set; } = false;
|
public static bool ShouldForceMouse => ShowMenu && ForceUnlockMouse;
|
||||||
public static int ArrayLimit { get; set; } = 20;
|
|
||||||
|
|
||||||
// methods
|
private static void SetShowMenu(bool show)
|
||||||
|
{
|
||||||
|
m_showMenu = show;
|
||||||
|
UpdateCursorControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== MonoBehaviour methods ==========
|
||||||
|
|
||||||
public override void OnApplicationStart()
|
public override void OnApplicationStart()
|
||||||
{
|
{
|
||||||
base.OnApplicationStart();
|
|
||||||
|
|
||||||
Instance = this;
|
Instance = this;
|
||||||
|
|
||||||
new MainMenu();
|
new MainMenu();
|
||||||
new WindowManager();
|
new WindowManager();
|
||||||
|
|
||||||
ShowMenu = true;
|
// Get current cursor state and enable cursor
|
||||||
|
m_lastLockMode = Cursor.lockState;
|
||||||
|
m_lastVisibleState = Cursor.visible;
|
||||||
|
|
||||||
|
// 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)
|
public override void OnLevelWasLoaded(int level)
|
||||||
@ -52,6 +77,7 @@ namespace Explorer
|
|||||||
|
|
||||||
public override void OnUpdate()
|
public override void OnUpdate()
|
||||||
{
|
{
|
||||||
|
// Check main toggle key input
|
||||||
if (Input.GetKeyDown(KeyCode.F7))
|
if (Input.GetKeyDown(KeyCode.F7))
|
||||||
{
|
{
|
||||||
ShowMenu = !ShowMenu;
|
ShowMenu = !ShowMenu;
|
||||||
@ -59,27 +85,117 @@ namespace Explorer
|
|||||||
|
|
||||||
if (ShowMenu)
|
if (ShowMenu)
|
||||||
{
|
{
|
||||||
if (!Cursor.visible)
|
// Check Force-Unlock input
|
||||||
|
if (Input.GetKeyDown(KeyCode.LeftAlt))
|
||||||
{
|
{
|
||||||
Cursor.visible = true;
|
ForceUnlockMouse = !ForceUnlockMouse;
|
||||||
Cursor.lockState = CursorLockMode.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MainMenu.Instance.Update();
|
MainMenu.Instance.Update();
|
||||||
WindowManager.Instance.Update();
|
WindowManager.Instance.Update();
|
||||||
|
|
||||||
InspectUnderMouse.Update();
|
InspectUnderMouse.Update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnGUI()
|
public override void OnGUI()
|
||||||
{
|
{
|
||||||
base.OnGUI();
|
if (!ShowMenu) return;
|
||||||
|
|
||||||
MainMenu.Instance.OnGUI();
|
MainMenu.Instance.OnGUI();
|
||||||
WindowManager.Instance.OnGUI();
|
WindowManager.Instance.OnGUI();
|
||||||
|
|
||||||
InspectUnderMouse.OnGUI();
|
InspectUnderMouse.OnGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========== Cursor control ===========
|
||||||
|
|
||||||
|
private static void SetForceUnlock(bool unlock)
|
||||||
|
{
|
||||||
|
m_forceUnlock = unlock;
|
||||||
|
UpdateCursorControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateCursorControl()
|
||||||
|
{
|
||||||
|
m_currentlySettingCursor = true;
|
||||||
|
if (ShouldForceMouse)
|
||||||
|
{
|
||||||
|
Cursor.lockState = CursorLockMode.None;
|
||||||
|
Cursor.visible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Cursor.lockState = m_lastLockMode;
|
||||||
|
Cursor.visible = m_lastVisibleState;
|
||||||
|
}
|
||||||
|
m_currentlySettingCursor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
public static void Prefix(ref CursorLockMode value)
|
||||||
|
{
|
||||||
|
if (!m_currentlySettingCursor)
|
||||||
|
{
|
||||||
|
m_lastLockMode = value;
|
||||||
|
|
||||||
|
if (ShouldForceMouse)
|
||||||
|
{
|
||||||
|
value = CursorLockMode.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Setter)]
|
||||||
|
public class Cursor_set_visible
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
public static void Prefix(ref bool value)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,6 @@
|
|||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
|
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Il2CppSystem.Core">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="mcs">
|
<Reference Include="mcs">
|
||||||
<HintPath>..\lib\mcs.dll</HintPath>
|
<HintPath>..\lib\mcs.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
@ -64,10 +60,6 @@
|
|||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="UnhollowerRuntimeLib">
|
|
||||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerRuntimeLib.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<!-- Unity 2019 build (InputLegacyModule.dll) -->
|
<!-- Unity 2019 build (InputLegacyModule.dll) -->
|
||||||
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
|
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
|
||||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||||
@ -97,10 +89,6 @@
|
|||||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="UnityEngine.UIElementsModule" Condition="'$(Configuration)'=='Debug'">
|
|
||||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
<!-- Unity 2018 build (InputModule.dll) -->
|
<!-- Unity 2018 build (InputModule.dll) -->
|
||||||
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
|
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||||
@ -130,27 +118,26 @@
|
|||||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="UnityEngine.UIElementsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
|
||||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UIElementsModule.dll</HintPath>
|
|
||||||
<Private>False</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="CachedObjects\CacheDictionary.cs" />
|
||||||
<Compile Include="CachedObjects\CacheEnum.cs" />
|
<Compile Include="CachedObjects\CacheEnum.cs" />
|
||||||
<Compile Include="CachedObjects\CacheGameObject.cs" />
|
<Compile Include="CachedObjects\CacheGameObject.cs" />
|
||||||
<Compile Include="CachedObjects\CacheList.cs" />
|
<Compile Include="CachedObjects\CacheList.cs" />
|
||||||
<Compile Include="CachedObjects\CachePrimitive.cs" />
|
<Compile Include="CachedObjects\CachePrimitive.cs" />
|
||||||
<Compile Include="CachedObjects\CacheIl2CppObject.cs" />
|
|
||||||
<Compile Include="CachedObjects\CacheStruct.cs" />
|
|
||||||
<Compile Include="CachedObjects\CacheOther.cs" />
|
<Compile Include="CachedObjects\CacheOther.cs" />
|
||||||
|
<Compile Include="CachedObjects\CacheMethod.cs" />
|
||||||
<Compile Include="CppExplorer.cs" />
|
<Compile Include="CppExplorer.cs" />
|
||||||
<Compile Include="Extensions\ReflectionExtensions.cs" />
|
<Compile Include="Extensions\ReflectionExtensions.cs" />
|
||||||
<Compile Include="Extensions\UnityExtensions.cs" />
|
<Compile Include="Extensions\UnityExtensions.cs" />
|
||||||
|
<Compile Include="Helpers\PageHelper.cs" />
|
||||||
<Compile Include="Helpers\ReflectionHelpers.cs" />
|
<Compile Include="Helpers\ReflectionHelpers.cs" />
|
||||||
<Compile Include="Helpers\UIHelpers.cs" />
|
<Compile Include="Helpers\UIHelpers.cs" />
|
||||||
<Compile Include="Helpers\UnityHelpers.cs" />
|
<Compile Include="Helpers\UnityHelpers.cs" />
|
||||||
<Compile Include="MainMenu\InspectUnderMouse.cs" />
|
<Compile Include="MainMenu\InspectUnderMouse.cs" />
|
||||||
<Compile Include="CachedObjects\CacheObject.cs" />
|
<Compile Include="CachedObjects\CacheObjectBase.cs" />
|
||||||
|
<Compile Include="Windows\ResizeDrag.cs" />
|
||||||
|
<Compile Include="Windows\TabViewWindow.cs" />
|
||||||
<Compile Include="Windows\UIWindow.cs" />
|
<Compile Include="Windows\UIWindow.cs" />
|
||||||
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
||||||
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
||||||
|
88
src/Helpers/PageHelper.cs
Normal file
88
src/Helpers/PageHelper.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public enum Turn
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PageHelper
|
||||||
|
{
|
||||||
|
public int PageOffset { get; set; }
|
||||||
|
public int PageLimit { get; set; } = 20;
|
||||||
|
public int Count { get; set; }
|
||||||
|
public int MaxOffset { get; set; } = -1;
|
||||||
|
|
||||||
|
public int CalculateMaxOffset()
|
||||||
|
{
|
||||||
|
return MaxOffset = (int)Mathf.Ceil((float)(Count / (decimal)PageLimit)) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CurrentPageLabel()
|
||||||
|
{
|
||||||
|
var orig = GUI.skin.label.alignment;
|
||||||
|
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
|
GUILayout.Label($"Page {PageOffset + 1}/{MaxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||||
|
|
||||||
|
GUI.skin.label.alignment = orig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TurnPage(Turn direction)
|
||||||
|
{
|
||||||
|
var _ = Vector2.zero;
|
||||||
|
TurnPage(direction, ref _);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void TurnPage(Turn direction, ref Vector2 scroll)
|
||||||
|
{
|
||||||
|
if (direction == Turn.Left)
|
||||||
|
{
|
||||||
|
if (PageOffset > 0)
|
||||||
|
{
|
||||||
|
PageOffset--;
|
||||||
|
scroll = Vector2.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (PageOffset < MaxOffset)
|
||||||
|
{
|
||||||
|
PageOffset++;
|
||||||
|
scroll = Vector2.zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateOffsetIndex()
|
||||||
|
{
|
||||||
|
int offset = PageOffset * PageLimit;
|
||||||
|
|
||||||
|
if (offset >= Count)
|
||||||
|
{
|
||||||
|
offset = 0;
|
||||||
|
PageOffset = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawLimitInputArea()
|
||||||
|
{
|
||||||
|
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||||
|
var limit = this.PageLimit.ToString();
|
||||||
|
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
|
||||||
|
if (limit != PageLimit.ToString() && int.TryParse(limit, out int i))
|
||||||
|
{
|
||||||
|
PageLimit = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,33 +8,97 @@ using UnhollowerBaseLib;
|
|||||||
using UnhollowerRuntimeLib;
|
using UnhollowerRuntimeLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using BF = System.Reflection.BindingFlags;
|
using BF = System.Reflection.BindingFlags;
|
||||||
using ILBF = Il2CppSystem.Reflection.BindingFlags;
|
using MelonLoader;
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public class ReflectionHelpers
|
public class ReflectionHelpers
|
||||||
{
|
{
|
||||||
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
||||||
public static ILBF CommonFlags_IL = ILBF.Public | ILBF.NonPublic | ILBF.Instance | ILBF.Static;
|
|
||||||
|
|
||||||
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
|
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
|
||||||
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>();
|
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>();
|
||||||
public static Il2CppSystem.Type ObjectType => Il2CppType.Of<UnityEngine.Object>();
|
public static Il2CppSystem.Type ObjectType => Il2CppType.Of<UnityEngine.Object>();
|
||||||
public static Il2CppSystem.Type ComponentType => Il2CppType.Of<Component>();
|
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");
|
private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
|
||||||
|
|
||||||
public static object Il2CppCast(object obj, Type castTo)
|
public static object Il2CppCast(object obj, Type castTo)
|
||||||
{
|
{
|
||||||
var generic = m_tryCastMethodInfo.MakeGenericMethod(castTo);
|
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) return obj;
|
||||||
return generic.Invoke(obj, null);
|
|
||||||
|
return m_tryCastMethodInfo
|
||||||
|
.MakeGenericMethod(castTo)
|
||||||
|
.Invoke(obj, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ExceptionToString(Exception e)
|
||||||
|
{
|
||||||
|
if (IsFailedGeneric(e))
|
||||||
|
{
|
||||||
|
return "Unable to initialize this type.";
|
||||||
|
}
|
||||||
|
else if (IsObjectCollected(e))
|
||||||
|
{
|
||||||
|
return "Garbage collected in Il2Cpp.";
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.GetType() + ", " + e.Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsFailedGeneric(Exception e)
|
||||||
|
{
|
||||||
|
return IsExceptionOfType(e, typeof(TargetInvocationException)) && IsExceptionOfType(e, typeof(TypeLoadException));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsObjectCollected(Exception e)
|
||||||
|
{
|
||||||
|
return IsExceptionOfType(e, typeof(ObjectCollectedException));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsExceptionOfType(Exception e, Type t, bool strict = true, bool checkInner = true)
|
||||||
|
{
|
||||||
|
bool isType;
|
||||||
|
|
||||||
|
if (strict)
|
||||||
|
isType = e.GetType() == t;
|
||||||
|
else
|
||||||
|
isType = t.IsAssignableFrom(e.GetType());
|
||||||
|
|
||||||
|
if (isType) return true;
|
||||||
|
|
||||||
|
if (e.InnerException != null && checkInner)
|
||||||
|
return IsExceptionOfType(e.InnerException, t, strict);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsArray(Type t)
|
||||||
|
{
|
||||||
|
return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsList(Type t)
|
public static bool IsList(Type t)
|
||||||
|
{
|
||||||
|
if (t.IsGenericType)
|
||||||
|
{
|
||||||
|
var generic = t.GetGenericTypeDefinition();
|
||||||
|
|
||||||
|
return generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||||
|
|| generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.IList<>));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return t.IsAssignableFrom(typeof(Il2CppSystem.Collections.IList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDictionary(Type t)
|
||||||
{
|
{
|
||||||
return t.IsGenericType
|
return t.IsGenericType
|
||||||
&& t.GetGenericTypeDefinition() is Type typeDef
|
&& t.GetGenericTypeDefinition() is Type typeDef
|
||||||
&& (typeDef.IsAssignableFrom(typeof(List<>)) || typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>)));
|
&& typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type GetTypeByName(string typeName)
|
public static Type GetTypeByName(string typeName)
|
||||||
@ -54,51 +118,36 @@ namespace Explorer
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type GetActualType(object m_object)
|
public static Type GetActualType(object obj)
|
||||||
{
|
{
|
||||||
if (m_object == null) return null;
|
if (obj == null) return null;
|
||||||
|
|
||||||
if (m_object is Il2CppSystem.Object ilObject)
|
if (obj is Il2CppSystem.Object ilObject)
|
||||||
{
|
{
|
||||||
var iltype = ilObject.GetIl2CppType();
|
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
|
||||||
return Type.GetType(iltype.AssemblyQualifiedName);
|
|
||||||
}
|
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
|
||||||
else
|
{
|
||||||
{
|
return t;
|
||||||
return m_object.GetType();
|
}
|
||||||
|
|
||||||
|
return ilObject.GetType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return obj.GetType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type[] GetAllBaseTypes(object m_object)
|
public static Type[] GetAllBaseTypes(object obj)
|
||||||
{
|
{
|
||||||
var list = new List<Type>();
|
var list = new List<Type>();
|
||||||
|
|
||||||
if (m_object is Il2CppSystem.Object ilObject)
|
var type = GetActualType(obj);
|
||||||
{
|
list.Add(type);
|
||||||
var ilType = ilObject.GetIl2CppType();
|
|
||||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilTypeToManaged)
|
|
||||||
{
|
|
||||||
list.Add(ilTypeToManaged);
|
|
||||||
|
|
||||||
while (ilType.BaseType != null)
|
while (type.BaseType != null)
|
||||||
{
|
|
||||||
ilType = ilType.BaseType;
|
|
||||||
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilBaseTypeToManaged)
|
|
||||||
{
|
|
||||||
list.Add(ilBaseTypeToManaged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var type = m_object.GetType();
|
type = type.BaseType;
|
||||||
list.Add(type);
|
list.Add(type);
|
||||||
while (type.BaseType != null)
|
|
||||||
{
|
|
||||||
type = type.BaseType;
|
|
||||||
list.Add(type);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.ToArray();
|
return list.ToArray();
|
||||||
|
@ -10,6 +10,35 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
public class UIHelpers
|
public class UIHelpers
|
||||||
{
|
{
|
||||||
|
private static bool ScrollUnstrippingFailed = false;
|
||||||
|
|
||||||
|
public static Vector2 BeginScrollView(Vector2 scroll) => BeginScrollView(scroll, null);
|
||||||
|
|
||||||
|
public static Vector2 BeginScrollView(Vector2 scroll, GUIStyle style, params GUILayoutOption[] layoutOptions)
|
||||||
|
{
|
||||||
|
if (ScrollUnstrippingFailed) return scroll;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (style != null)
|
||||||
|
return GUILayout.BeginScrollView(scroll, style, layoutOptions);
|
||||||
|
else
|
||||||
|
return GUILayout.BeginScrollView(scroll, layoutOptions);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ScrollUnstrippingFailed = true;
|
||||||
|
return scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void EndScrollView()
|
||||||
|
{
|
||||||
|
if (ScrollUnstrippingFailed) return;
|
||||||
|
|
||||||
|
GUILayout.EndScrollView();
|
||||||
|
}
|
||||||
|
|
||||||
// helper for "Instantiate" button on UnityEngine.Objects
|
// helper for "Instantiate" button on UnityEngine.Objects
|
||||||
public static void InstantiateButton(Object obj, float width = 100)
|
public static void InstantiateButton(Object obj, float width = 100)
|
||||||
{
|
{
|
||||||
@ -22,8 +51,10 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper for drawing a styled button for a GameObject or Transform
|
// 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)
|
public static void GameobjButton(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||||
{
|
{
|
||||||
|
var obj = (_obj as GameObject) ?? (_obj as Transform).gameObject;
|
||||||
|
|
||||||
bool children = obj.transform.childCount > 0;
|
bool children = obj.transform.childCount > 0;
|
||||||
|
|
||||||
string label = children ? "[" + obj.transform.childCount + " children] " : "";
|
string label = children ? "[" + obj.transform.childCount + " children] " : "";
|
||||||
@ -49,11 +80,13 @@ namespace Explorer
|
|||||||
color = Color.red;
|
color = Color.red;
|
||||||
}
|
}
|
||||||
|
|
||||||
FastGameobjButton(obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
|
FastGameobjButton(_obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void FastGameobjButton(GameObject obj, Color activeColor, string label, bool enabled, Action<GameObject> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
public static void FastGameobjButton(object _obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||||
{
|
{
|
||||||
|
var obj = _obj as GameObject ?? (_obj as Transform).gameObject;
|
||||||
|
|
||||||
if (!obj)
|
if (!obj)
|
||||||
{
|
{
|
||||||
GUILayout.Label("<i><color=red>null</color></i>", null);
|
GUILayout.Label("<i><color=red>null</color></i>", null);
|
||||||
@ -79,11 +112,11 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
if (specialInspectMethod != null)
|
if (specialInspectMethod != null)
|
||||||
{
|
{
|
||||||
specialInspectMethod(obj);
|
specialInspectMethod(obj.transform);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WindowManager.InspectObject(obj, out bool _);
|
WindowManager.InspectObject(_obj, out bool _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,13 +127,18 @@ namespace Explorer
|
|||||||
|
|
||||||
if (showSmallInspectBtn)
|
if (showSmallInspectBtn)
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("Inspect", null))
|
SmallInspectButton(_obj);
|
||||||
{
|
|
||||||
WindowManager.InspectObject(obj, out bool _);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SmallInspectButton(object obj)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("Inspect", null))
|
||||||
|
{
|
||||||
|
WindowManager.InspectObject(obj, out bool _);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,12 +33,14 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void HorizontalLine(Color color)
|
public static void HorizontalLine(Color _color, bool small = false)
|
||||||
{
|
{
|
||||||
var c = GUI.color;
|
var orig = GUI.color;
|
||||||
GUI.color = color;
|
|
||||||
GUILayout.Box(GUIContent.none, HorizontalBar, null);
|
GUI.color = _color;
|
||||||
GUI.color = c;
|
GUILayout.Box(GUIContent.none, !small ? HorizontalBar : HorizontalBarSmall, null);
|
||||||
|
|
||||||
|
GUI.color = orig;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GUISkin _customSkin;
|
private static GUISkin _customSkin;
|
||||||
@ -46,8 +48,6 @@ namespace Explorer
|
|||||||
public static Texture2D m_nofocusTex;
|
public static Texture2D m_nofocusTex;
|
||||||
public static Texture2D m_focusTex;
|
public static Texture2D m_focusTex;
|
||||||
|
|
||||||
private static GUIStyle _horizBarStyle;
|
|
||||||
|
|
||||||
private static GUIStyle HorizontalBar
|
private static GUIStyle HorizontalBar
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -63,14 +63,32 @@ namespace Explorer
|
|||||||
return _horizBarStyle;
|
return _horizBarStyle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private static GUIStyle _horizBarStyle;
|
||||||
|
|
||||||
|
private static GUIStyle HorizontalBarSmall
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_horizBarSmallStyle == null)
|
||||||
|
{
|
||||||
|
_horizBarSmallStyle = new GUIStyle();
|
||||||
|
_horizBarSmallStyle.normal.background = Texture2D.whiteTexture;
|
||||||
|
_horizBarSmallStyle.margin = new RectOffset(0, 0, 2, 2);
|
||||||
|
_horizBarSmallStyle.fixedHeight = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _horizBarSmallStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static GUIStyle _horizBarSmallStyle;
|
||||||
|
|
||||||
private static GUISkin CreateWindowSkin()
|
private static GUISkin CreateWindowSkin()
|
||||||
{
|
{
|
||||||
var newSkin = Object.Instantiate(GUI.skin);
|
var newSkin = Object.Instantiate(GUI.skin);
|
||||||
Object.DontDestroyOnLoad(newSkin);
|
Object.DontDestroyOnLoad(newSkin);
|
||||||
|
|
||||||
m_nofocusTex = MakeTex(550, 700, new Color(0.1f, 0.1f, 0.1f, 0.7f));
|
m_nofocusTex = MakeTex(1, 1, new Color(0.1f, 0.1f, 0.1f, 0.7f));
|
||||||
m_focusTex = MakeTex(550, 700, new Color(0.3f, 0.3f, 0.3f, 1f));
|
m_focusTex = MakeTex(1, 1, new Color(0.3f, 0.3f, 0.3f, 1f));
|
||||||
|
|
||||||
newSkin.window.normal.background = m_nofocusTex;
|
newSkin.window.normal.background = m_nofocusTex;
|
||||||
newSkin.window.onNormal.background = m_focusTex;
|
newSkin.window.onNormal.background = m_focusTex;
|
||||||
|
@ -51,15 +51,12 @@ namespace Explorer
|
|||||||
|
|
||||||
public void OnGUI()
|
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, CppExplorer.NAME);
|
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, CppExplorer.NAME);
|
||||||
|
|
||||||
GUI.skin = origSkin;
|
GUI.skin = origSkin;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainWindow(int id)
|
private void MainWindow(int id)
|
||||||
@ -77,30 +74,20 @@ namespace Explorer
|
|||||||
MainHeader();
|
MainHeader();
|
||||||
|
|
||||||
var page = Pages[m_currentPage];
|
var page = Pages[m_currentPage];
|
||||||
page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView);
|
|
||||||
page.DrawWindow();
|
|
||||||
GUILayout.EndScrollView();
|
|
||||||
|
|
||||||
MainRect = WindowManager.ResizeWindow(MainRect, MainWindowID);
|
page.scroll = UIHelpers.BeginScrollView(page.scroll);
|
||||||
|
|
||||||
|
page.DrawWindow();
|
||||||
|
|
||||||
|
UIHelpers.EndScrollView();
|
||||||
|
|
||||||
|
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);
|
||||||
|
|
||||||
GUILayout.EndArea();
|
GUILayout.EndArea();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainHeader()
|
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;
|
|
||||||
}
|
|
||||||
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
for (int i = 0; i < Pages.Count; i++)
|
for (int i = 0; i < Pages.Count; i++)
|
||||||
{
|
{
|
||||||
@ -116,6 +103,17 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
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);
|
GUILayout.Space(10);
|
||||||
GUI.color = Color.white;
|
GUI.color = Color.white;
|
||||||
}
|
}
|
||||||
|
@ -86,11 +86,11 @@ MelonLogger.Log(""hello world"");";
|
|||||||
if (!UsingDirectives.Contains(asm))
|
if (!UsingDirectives.Contains(asm))
|
||||||
{
|
{
|
||||||
UsingDirectives.Add(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;
|
object ret = VoidType.Value;
|
||||||
|
|
||||||
@ -98,11 +98,19 @@ MelonLogger.Log(""hello world"");";
|
|||||||
|
|
||||||
try
|
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)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
MelonLogger.LogWarning(e.ToString());
|
if (!suppressWarning)
|
||||||
|
{
|
||||||
|
MelonLogger.LogWarning(e.GetType() + ", " + e.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@ -113,8 +121,10 @@ MelonLogger.Log(""hello world"");";
|
|||||||
{
|
{
|
||||||
GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", null);
|
GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", null);
|
||||||
|
|
||||||
GUILayout.Label("Method:", null);
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(300) });
|
|
||||||
|
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))
|
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
|
||||||
{
|
{
|
||||||
@ -139,12 +149,9 @@ MelonLogger.Log(""hello world"");";
|
|||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.Label("<b>Using directives:</b>", null);
|
GUILayout.Label("<b>Using directives:</b>", null);
|
||||||
foreach (var asm in UsingDirectives)
|
|
||||||
{
|
|
||||||
GUILayout.Label(AsmToUsing(asm, true), null);
|
|
||||||
}
|
|
||||||
GUILayout.BeginHorizontal(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) });
|
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||||
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||||
{
|
{
|
||||||
@ -155,6 +162,11 @@ MelonLogger.Log(""hello world"");";
|
|||||||
ResetConsole();
|
ResetConsole();
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
foreach (var asm in UsingDirectives)
|
||||||
|
{
|
||||||
|
GUILayout.Label(AsmToUsing(asm, true), null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update() { }
|
public override void Update() { }
|
||||||
|
@ -14,9 +14,13 @@ namespace Explorer
|
|||||||
|
|
||||||
public override string Name { get => "Scene Explorer"; set => base.Name = value; }
|
public override string Name { get => "Scene Explorer"; set => base.Name = value; }
|
||||||
|
|
||||||
// ----- Holders for GUI elements ----- //
|
public PageHelper Pages = new PageHelper();
|
||||||
|
|
||||||
private string m_currentScene = "";
|
private float m_timeOfLastUpdate = -1f;
|
||||||
|
|
||||||
|
// ----- Holders for GUI elements ----- //
|
||||||
|
|
||||||
|
private string m_currentScene = "";
|
||||||
|
|
||||||
// gameobject list
|
// gameobject list
|
||||||
private Transform m_currentTransform;
|
private Transform m_currentTransform;
|
||||||
@ -37,190 +41,82 @@ namespace Explorer
|
|||||||
public void OnSceneChange()
|
public void OnSceneChange()
|
||||||
{
|
{
|
||||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||||
|
SetTransformTarget(null);
|
||||||
m_currentTransform = null;
|
|
||||||
CancelSearch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//public void CheckOffset(ref int offset, int childCount)
|
||||||
|
//{
|
||||||
|
// if (offset >= childCount)
|
||||||
|
// {
|
||||||
|
// offset = 0;
|
||||||
|
// m_pageOffset = 0;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
{
|
{
|
||||||
if (!m_searching)
|
if (m_searching) return;
|
||||||
|
|
||||||
|
if (Time.time - m_timeOfLastUpdate < 1f) return;
|
||||||
|
m_timeOfLastUpdate = Time.time;
|
||||||
|
|
||||||
|
m_objectList = new List<GameObjectCache>();
|
||||||
|
|
||||||
|
var allTransforms = new List<Transform>();
|
||||||
|
|
||||||
|
// get current list of all transforms (either scene root or our current transform children)
|
||||||
|
if (m_currentTransform)
|
||||||
{
|
{
|
||||||
m_objectList = new List<GameObjectCache>();
|
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||||
if (m_currentTransform)
|
|
||||||
{
|
{
|
||||||
var endAppend = new List<GameObjectCache>();
|
allTransforms.Add(m_currentTransform.GetChild(i));
|
||||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
|
||||||
{
|
|
||||||
var child = m_currentTransform.GetChild(i);
|
|
||||||
|
|
||||||
if (child)
|
|
||||||
{
|
|
||||||
if (child.childCount > 0)
|
|
||||||
m_objectList.Add(new GameObjectCache(child.gameObject));
|
|
||||||
else
|
|
||||||
endAppend.Add(new GameObjectCache(child.gameObject));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_objectList.AddRange(endAppend);
|
|
||||||
endAppend = null;
|
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var scene = SceneManager.GetSceneByName(m_currentScene);
|
||||||
|
var rootObjects = scene.GetRootGameObjects();
|
||||||
|
|
||||||
|
foreach (var obj in rootObjects)
|
||||||
{
|
{
|
||||||
var scene = SceneManager.GetSceneByName(m_currentScene);
|
allTransforms.Add(obj.transform);
|
||||||
var rootObjects = scene.GetRootGameObjects();
|
|
||||||
|
|
||||||
// add objects with children first
|
|
||||||
foreach (var obj in rootObjects.Where(x => x.transform.childCount > 0))
|
|
||||||
{
|
|
||||||
m_objectList.Add(new GameObjectCache(obj));
|
|
||||||
}
|
|
||||||
foreach (var obj in rootObjects.Where(x => x.transform.childCount == 0))
|
|
||||||
{
|
|
||||||
m_objectList.Add(new GameObjectCache(obj));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pages.Count = allTransforms.Count;
|
||||||
|
|
||||||
|
int offset = Pages.CalculateOffsetIndex();
|
||||||
|
|
||||||
|
// sort by childcount
|
||||||
|
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||||
|
|
||||||
|
for (int i = offset; i < offset + Pages.PageLimit && i < Pages.Count; i++)
|
||||||
|
{
|
||||||
|
var child = allTransforms[i];
|
||||||
|
m_objectList.Add(new GameObjectCache(child.gameObject));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------- GUI Draw Functions --------- //
|
public void SetTransformTarget(Transform t)
|
||||||
|
|
||||||
public override void DrawWindow()
|
|
||||||
{
|
{
|
||||||
try
|
m_currentTransform = t;
|
||||||
{
|
|
||||||
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)
|
if (m_searching)
|
||||||
{
|
CancelSearch();
|
||||||
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();
|
m_timeOfLastUpdate = -1f;
|
||||||
|
Update();
|
||||||
// ----- 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(m_currentTransform.GetGameObjectPath(), 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);
|
|
||||||
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
UIHelpers.FastGameobjButton(obj.RefGameObject, obj.EnabledColor, obj.Label, obj.RefGameObject.activeSelf, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
m_currentTransform = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// -------- Actual Methods (not drawing GUI) ---------- //
|
|
||||||
|
|
||||||
public void SetTransformTarget(GameObject obj)
|
|
||||||
{
|
|
||||||
m_currentTransform = obj.transform;
|
|
||||||
CancelSearch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TraverseUp()
|
public void TraverseUp()
|
||||||
{
|
{
|
||||||
if (m_currentTransform.parent != null)
|
if (m_currentTransform.parent != null)
|
||||||
{
|
{
|
||||||
m_currentTransform = m_currentTransform.parent;
|
SetTransformTarget(m_currentTransform.parent);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_currentTransform = null;
|
SetTransformTarget(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +124,7 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
m_searchResults = SearchSceneObjects(m_searchInput);
|
||||||
m_searching = true;
|
m_searching = true;
|
||||||
|
Pages.Count = m_searchResults.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CancelSearch()
|
public void CancelSearch()
|
||||||
@ -250,6 +147,215 @@ namespace Explorer
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------- GUI Draw Function --------- //
|
||||||
|
|
||||||
|
public override void DrawWindow()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DrawHeaderArea();
|
||||||
|
|
||||||
|
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||||
|
|
||||||
|
DrawPageButtons();
|
||||||
|
|
||||||
|
if (!m_searching)
|
||||||
|
{
|
||||||
|
DrawGameObjectList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawSearchResultsList();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message);
|
||||||
|
MelonLogger.Log(e.StackTrace);
|
||||||
|
m_currentTransform = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeaderArea()
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
// Current Scene label
|
||||||
|
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
|
||||||
|
var scenes = SceneManager.GetAllScenes().ToList();
|
||||||
|
|
||||||
|
if (scenes.Count > 1)
|
||||||
|
{
|
||||||
|
int changeWanted = 0;
|
||||||
|
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||||
|
{
|
||||||
|
changeWanted = -1;
|
||||||
|
}
|
||||||
|
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||||
|
{
|
||||||
|
changeWanted = 1;
|
||||||
|
}
|
||||||
|
if (changeWanted != 0)
|
||||||
|
{
|
||||||
|
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||||
|
index += changeWanted;
|
||||||
|
if (index > scenes.Count - 1)
|
||||||
|
{
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
else if (index < 0)
|
||||||
|
{
|
||||||
|
index = scenes.Count - 1;
|
||||||
|
}
|
||||||
|
m_currentScene = scenes[index].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
// ----- GameObject Search -----
|
||||||
|
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
||||||
|
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||||
|
m_searchInput = GUILayout.TextField(m_searchInput, null);
|
||||||
|
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
Search();
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.Space(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPageButtons()
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
Pages.DrawLimitInputArea();
|
||||||
|
|
||||||
|
if (Pages.Count > Pages.PageLimit)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Pages.CurrentPageLabel();
|
||||||
|
|
||||||
|
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGameObjectList()
|
||||||
|
{
|
||||||
|
if (m_currentTransform != null)
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||||
|
{
|
||||||
|
TraverseUp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label("<color=cyan>" + m_currentTransform.GetGameObjectPath() + "</color>",
|
||||||
|
new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 187f) });
|
||||||
|
}
|
||||||
|
|
||||||
|
UIHelpers.SmallInspectButton(m_currentTransform);
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label("Scene Root GameObjects:", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_objectList.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var obj in m_objectList)
|
||||||
|
{
|
||||||
|
if (!obj.RefGameObject)
|
||||||
|
{
|
||||||
|
string label = "<color=red><i>null";
|
||||||
|
|
||||||
|
if (obj.RefGameObject != null)
|
||||||
|
{
|
||||||
|
label += " (Destroyed)";
|
||||||
|
}
|
||||||
|
|
||||||
|
label += "</i></color>";
|
||||||
|
GUILayout.Label(label, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UIHelpers.FastGameobjButton(obj.RefGameObject,
|
||||||
|
obj.EnabledColor,
|
||||||
|
obj.Label,
|
||||||
|
obj.RefGameObject.activeSelf,
|
||||||
|
SetTransformTarget,
|
||||||
|
true,
|
||||||
|
MainMenu.MainRect.width - 170);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawSearchResultsList()
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("<- Cancel Search", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||||
|
{
|
||||||
|
CancelSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.Label("Search Results:", null);
|
||||||
|
|
||||||
|
if (m_searchResults.Count > 0)
|
||||||
|
{
|
||||||
|
int offset = Pages.CalculateOffsetIndex();
|
||||||
|
|
||||||
|
for (int i = offset; i < offset + Pages.PageLimit && i < m_searchResults.Count; i++)
|
||||||
|
{
|
||||||
|
var obj = m_searchResults[i];
|
||||||
|
|
||||||
|
if (obj.RefGameObject)
|
||||||
|
{
|
||||||
|
UIHelpers.FastGameobjButton(obj.RefGameObject,
|
||||||
|
obj.EnabledColor,
|
||||||
|
obj.Label,
|
||||||
|
obj.RefGameObject.activeSelf,
|
||||||
|
SetTransformTarget,
|
||||||
|
true,
|
||||||
|
MainMenu.MainRect.width - 170);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label("<i><color=red>Null or destroyed!</color></i>", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------- Mini GameObjectCache class ---------- //
|
||||||
|
|
||||||
public class GameObjectCache
|
public class GameObjectCache
|
||||||
{
|
{
|
||||||
public GameObject RefGameObject;
|
public GameObject RefGameObject;
|
||||||
|
@ -5,11 +5,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using UnityEngine.SceneManagement;
|
|
||||||
using Object = UnityEngine.Object;
|
|
||||||
using UnhollowerRuntimeLib;
|
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using UnhollowerBaseLib;
|
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
@ -21,12 +17,12 @@ namespace Explorer
|
|||||||
|
|
||||||
private string m_searchInput = "";
|
private string m_searchInput = "";
|
||||||
private string m_typeInput = "";
|
private string m_typeInput = "";
|
||||||
private int m_limit = 20;
|
|
||||||
private int m_pageOffset = 0;
|
|
||||||
//private List<object> m_searchResults = new List<object>();
|
|
||||||
private Vector2 resultsScroll = Vector2.zero;
|
private Vector2 resultsScroll = Vector2.zero;
|
||||||
|
|
||||||
private List<CacheObject> m_searchResults = new List<CacheObject>();
|
public PageHelper Pages = new PageHelper();
|
||||||
|
|
||||||
|
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
|
||||||
|
|
||||||
public SceneFilter SceneMode = SceneFilter.Any;
|
public SceneFilter SceneMode = SceneFilter.Any;
|
||||||
public TypeFilter TypeMode = TypeFilter.Object;
|
public TypeFilter TypeMode = TypeFilter.Object;
|
||||||
@ -55,7 +51,7 @@ namespace Explorer
|
|||||||
public void OnSceneChange()
|
public void OnSceneChange()
|
||||||
{
|
{
|
||||||
m_searchResults.Clear();
|
m_searchResults.Clear();
|
||||||
m_pageOffset = 0;
|
Pages.PageOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update()
|
public override void Update()
|
||||||
@ -64,7 +60,7 @@ namespace Explorer
|
|||||||
|
|
||||||
private void CacheResults(IEnumerable results)
|
private void CacheResults(IEnumerable results)
|
||||||
{
|
{
|
||||||
m_searchResults = new List<CacheObject>();
|
m_searchResults = new List<CacheObjectBase>();
|
||||||
|
|
||||||
foreach (var obj in results)
|
foreach (var obj in results)
|
||||||
{
|
{
|
||||||
@ -75,9 +71,12 @@ namespace Explorer
|
|||||||
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
|
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
var cache = CacheObject.GetCacheObject(toCache);
|
var cache = CacheObjectBase.GetCacheObject(toCache);
|
||||||
m_searchResults.Add(cache);
|
m_searchResults.Add(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pages.Count = m_searchResults.Count;
|
||||||
|
Pages.PageOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void DrawWindow()
|
public override void DrawWindow()
|
||||||
@ -91,7 +90,6 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
//m_searchResults = GetInstanceClassScanner().ToList();
|
//m_searchResults = GetInstanceClassScanner().ToList();
|
||||||
CacheResults(GetInstanceClassScanner());
|
CacheResults(GetInstanceClassScanner());
|
||||||
m_pageOffset = 0;
|
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
@ -106,36 +104,45 @@ namespace Explorer
|
|||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
|
|
||||||
int count = m_searchResults.Count;
|
int count = m_searchResults.Count;
|
||||||
|
Pages.CalculateMaxOffset();
|
||||||
|
|
||||||
if (count > this.m_limit)
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
Pages.DrawLimitInputArea();
|
||||||
|
|
||||||
|
if (count > Pages.PageLimit)
|
||||||
{
|
{
|
||||||
// prev/next page buttons
|
// prev/next page buttons
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
int maxOffset = (int)Mathf.Ceil(count / this.m_limit);
|
|
||||||
if (GUILayout.Button("< Prev", null))
|
|
||||||
{
|
|
||||||
if (m_pageOffset > 0) m_pageOffset--;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
if (Pages.Count > Pages.PageLimit)
|
||||||
|
|
||||||
if (GUILayout.Button("Next >", null))
|
|
||||||
{
|
{
|
||||||
if (m_pageOffset < maxOffset) m_pageOffset++;
|
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 = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView);
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
resultsScroll = UIHelpers.BeginScrollView(resultsScroll);
|
||||||
|
|
||||||
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
||||||
|
|
||||||
if (m_searchResults.Count > 0)
|
if (m_searchResults.Count > 0)
|
||||||
{
|
{
|
||||||
int offset = m_pageOffset * this.m_limit;
|
//int offset = m_pageOffset * this.m_limit;
|
||||||
if (offset >= count) m_pageOffset = 0;
|
//if (offset >= count) m_pageOffset = 0;
|
||||||
|
int offset = Pages.CalculateOffsetIndex();
|
||||||
|
|
||||||
for (int i = offset; i < offset + CppExplorer.ArrayLimit && i < count; i++)
|
for (int i = offset; i < offset + Pages.PageLimit && i < count; i++)
|
||||||
{
|
{
|
||||||
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||||
//m_searchResults[i].DrawValue(MainMenu.MainRect);
|
//m_searchResults[i].DrawValue(MainMenu.MainRect);
|
||||||
@ -146,7 +153,7 @@ namespace Explorer
|
|||||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
UIHelpers.EndScrollView();
|
||||||
GUILayout.EndVertical();
|
GUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@ -169,15 +176,15 @@ namespace Explorer
|
|||||||
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||||
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
||||||
|
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
//GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||||
GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
|
//GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||||
var resultinput = m_limit.ToString();
|
//var resultinput = m_limit.ToString();
|
||||||
resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
|
//resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
|
||||||
if (int.TryParse(resultinput, out int _i) && _i > 0)
|
//if (int.TryParse(resultinput, out int _i) && _i > 0)
|
||||||
{
|
//{
|
||||||
m_limit = _i;
|
// m_limit = _i;
|
||||||
}
|
//}
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
//GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
@ -256,7 +263,7 @@ namespace Explorer
|
|||||||
|
|
||||||
private void Search()
|
private void Search()
|
||||||
{
|
{
|
||||||
m_pageOffset = 0;
|
Pages.PageOffset = 0;
|
||||||
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,6 +277,7 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
var findType = ReflectionHelpers.GetTypeByName(_type);
|
var findType = ReflectionHelpers.GetTypeByName(_type);
|
||||||
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||||
|
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@ -299,8 +307,13 @@ namespace Explorer
|
|||||||
|
|
||||||
var allObjectsOfType = Resources.FindObjectsOfTypeAll(searchType);
|
var allObjectsOfType = Resources.FindObjectsOfTypeAll(searchType);
|
||||||
|
|
||||||
|
//MelonLogger.Log("Found count: " + allObjectsOfType.Length);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
foreach (var obj in allObjectsOfType)
|
foreach (var obj in allObjectsOfType)
|
||||||
{
|
{
|
||||||
|
if (i >= 2000) break;
|
||||||
|
|
||||||
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
|
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@ -322,6 +335,8 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
matches.Add(obj);
|
matches.Add(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
|
@ -11,7 +11,9 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
public class GameObjectWindow : UIWindow
|
public class GameObjectWindow : UIWindow
|
||||||
{
|
{
|
||||||
public override string Name { get => "GameObject Inspector"; set => Name = value; }
|
public override string Title => WindowManager.TabView
|
||||||
|
? $"<color=cyan>[G]</color> {m_object.name}"
|
||||||
|
: $"GameObject Inspector ({m_object.name})";
|
||||||
|
|
||||||
public GameObject m_object;
|
public GameObject m_object;
|
||||||
|
|
||||||
@ -19,20 +21,22 @@ namespace Explorer
|
|||||||
private string m_name;
|
private string m_name;
|
||||||
private string m_scene;
|
private string m_scene;
|
||||||
|
|
||||||
private Vector2 m_transformScroll = Vector2.zero;
|
|
||||||
private Transform[] m_children;
|
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 Vector2 m_compScroll = Vector2.zero;
|
||||||
//private Component[] m_components;
|
private PageHelper CompPages = new PageHelper();
|
||||||
|
|
||||||
private float m_translateAmount = 0.3f;
|
private float m_translateAmount = 0.3f;
|
||||||
private float m_rotateAmount = 50f;
|
private float m_rotateAmount = 50f;
|
||||||
private float m_scaleAmount = 0.1f;
|
private float m_scaleAmount = 0.1f;
|
||||||
|
|
||||||
List<Component> m_cachedDestroyList = new List<Component>();
|
private readonly List<Component> m_cachedDestroyList = new List<Component>();
|
||||||
//private string m_addComponentInput = "";
|
//private string m_addComponentInput = "";
|
||||||
|
|
||||||
private string m_setParentInput = "";
|
private string m_setParentInput = "Enter a GameObject name or path";
|
||||||
|
|
||||||
public bool GetObjectAsGameObject()
|
public bool GetObjectAsGameObject()
|
||||||
{
|
{
|
||||||
@ -68,30 +72,60 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_name = m_object.name;
|
m_name = m_object.name;
|
||||||
m_scene = m_object.scene == null ? "null" : m_object.scene.name;
|
m_scene = string.IsNullOrEmpty(m_object.scene.name)
|
||||||
|
? "None"
|
||||||
|
: m_object.scene.name;
|
||||||
|
|
||||||
//var listComps = new Il2CppSystem.Collections.Generic.List<Component>();
|
Update();
|
||||||
//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()
|
public override void Update()
|
||||||
{
|
{
|
||||||
if (!m_object && !GetObjectAsGameObject())
|
try
|
||||||
{
|
{
|
||||||
MelonLogger.Log("Object is null! Destroying window...");
|
if (!m_object && !GetObjectAsGameObject())
|
||||||
DestroyWindow();
|
{
|
||||||
|
throw new Exception("Object is null!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = new List<Transform>();
|
||||||
|
for (int i = 0; i < m_object.transform.childCount; i++)
|
||||||
|
{
|
||||||
|
list.Add(m_object.transform.GetChild(i));
|
||||||
|
}
|
||||||
|
list.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||||
|
m_children = list.ToArray();
|
||||||
|
|
||||||
|
ChildPages.Count = m_children.Length;
|
||||||
|
|
||||||
|
var list2 = new List<Component>();
|
||||||
|
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
|
||||||
|
{
|
||||||
|
var ilType = comp.GetIl2CppType();
|
||||||
|
if (ilType == ReflectionHelpers.TransformType)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list2.Add(comp);
|
||||||
|
}
|
||||||
|
m_components = list2.ToArray();
|
||||||
|
|
||||||
|
CompPages.Count = m_components.Length;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DestroyOnException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InspectGameObject(GameObject obj)
|
private void DestroyOnException(Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
|
||||||
|
DestroyWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InspectGameObject(Transform obj)
|
||||||
{
|
{
|
||||||
var window = WindowManager.InspectObject(obj, out bool created);
|
var window = WindowManager.InspectObject(obj, out bool created);
|
||||||
|
|
||||||
@ -125,89 +159,127 @@ namespace Explorer
|
|||||||
|
|
||||||
public override void WindowFunction(int windowID)
|
public override void WindowFunction(int windowID)
|
||||||
{
|
{
|
||||||
Header();
|
try
|
||||||
|
|
||||||
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 == UnityHelpers.ActiveSceneName)
|
|
||||||
{
|
{
|
||||||
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) }))
|
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
|
||||||
|
|
||||||
|
if (!WindowManager.TabView)
|
||||||
{
|
{
|
||||||
ScenePage.Instance.SetTransformTarget(m_object);
|
Header();
|
||||||
MainMenu.SetCurrentPage(0);
|
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
|
||||||
|
}
|
||||||
|
|
||||||
|
scroll = UIHelpers.BeginScrollView(scroll);
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
|
||||||
|
if (m_scene == UnityHelpers.ActiveSceneName)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||||
|
{
|
||||||
|
ScenePage.Instance.SetTransformTarget(m_object.transform);
|
||||||
|
MainMenu.SetCurrentPage(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("Reflection Inspect", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||||
|
{
|
||||||
|
WindowManager.InspectObject(Target, out _, true);
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||||
|
string pathlabel = m_object.transform.GetGameObjectPath();
|
||||||
|
if (m_object.transform.parent != null)
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||||
|
{
|
||||||
|
InspectGameObject(m_object.transform.parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GUILayout.TextArea(pathlabel, null);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||||
|
GUILayout.TextArea(m_name, null);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
// --- Horizontal Columns section ---
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
|
||||||
|
TransformList(rect);
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
|
||||||
|
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
|
||||||
|
ComponentList(rect);
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal(); // end horiz columns
|
||||||
|
|
||||||
|
GameObjectControls();
|
||||||
|
|
||||||
|
UIHelpers.EndScrollView();
|
||||||
|
|
||||||
|
if (!WindowManager.TabView)
|
||||||
|
{
|
||||||
|
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
|
||||||
|
|
||||||
|
GUILayout.EndArea();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
catch (Exception e)
|
||||||
|
|
||||||
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) }))
|
DestroyOnException(e);
|
||||||
{
|
|
||||||
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()
|
private void TransformList(Rect m_rect)
|
||||||
{
|
{
|
||||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||||
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView);
|
m_transformScroll = UIHelpers.BeginScrollView(m_transformScroll);
|
||||||
|
|
||||||
|
GUILayout.Label("<b><size=15>Children</size></b>", null);
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
ChildPages.DrawLimitInputArea();
|
||||||
|
|
||||||
|
if (ChildPages.Count > ChildPages.PageLimit)
|
||||||
|
{
|
||||||
|
ChildPages.CalculateMaxOffset();
|
||||||
|
|
||||||
|
ChildPages.CurrentPageLabel();
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
ChildPages.TurnPage(Turn.Left, ref this.m_transformScroll);
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
ChildPages.TurnPage(Turn.Right, ref this.m_transformScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.Label("<b>Children:</b>", null);
|
|
||||||
if (m_children != null && m_children.Length > 0)
|
if (m_children != null && m_children.Length > 0)
|
||||||
{
|
{
|
||||||
foreach (var obj in m_children.Where(x => x.childCount > 0))
|
int start = ChildPages.CalculateOffsetIndex();
|
||||||
|
|
||||||
|
for (int j = start; (j < start + ChildPages.PageLimit && j < ChildPages.Count); j++)
|
||||||
{
|
{
|
||||||
|
var obj = m_children[j];
|
||||||
|
|
||||||
if (!obj)
|
if (!obj)
|
||||||
{
|
{
|
||||||
GUILayout.Label("null", null);
|
GUILayout.Label("null", null);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
|
||||||
}
|
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
|
||||||
foreach (var obj in m_children.Where(x => x.childCount == 0))
|
|
||||||
{
|
|
||||||
if (!obj)
|
|
||||||
{
|
|
||||||
GUILayout.Label("null", null);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, this.m_rect.width / 2 - 60);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -215,53 +287,78 @@ namespace Explorer
|
|||||||
GUILayout.Label("<i>None</i>", null);
|
GUILayout.Label("<i>None</i>", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
UIHelpers.EndScrollView();
|
||||||
GUILayout.EndVertical();
|
GUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ComponentList(Rect m_rect)
|
||||||
private void ComponentList()
|
|
||||||
{
|
{
|
||||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Height(250) });
|
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||||
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView);
|
m_compScroll = UIHelpers.BeginScrollView(m_compScroll);
|
||||||
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
CompPages.DrawLimitInputArea();
|
||||||
|
|
||||||
|
if (CompPages.Count > CompPages.PageLimit)
|
||||||
|
{
|
||||||
|
CompPages.CalculateMaxOffset();
|
||||||
|
|
||||||
|
CompPages.CurrentPageLabel();
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
CompPages.TurnPage(Turn.Left, ref this.m_compScroll);
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
|
{
|
||||||
|
CompPages.TurnPage(Turn.Right, ref this.m_compScroll);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
if (m_cachedDestroyList.Count > 0)
|
if (m_cachedDestroyList.Count > 0)
|
||||||
{
|
{
|
||||||
m_cachedDestroyList.Clear();
|
m_cachedDestroyList.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
var m_components = new Il2CppSystem.Collections.Generic.List<Component>();
|
if (m_components != null)
|
||||||
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();
|
int start = CompPages.CalculateOffsetIndex();
|
||||||
if (ilType == ilTypeOfTransform)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
for (int j = start; (j < start + CompPages.PageLimit && j < CompPages.Count); j++)
|
||||||
if (ilTypeOfBehaviour.IsAssignableFrom(ilType))
|
|
||||||
{
|
{
|
||||||
BehaviourEnabledBtn(component.TryCast<Behaviour>());
|
var component = m_components[j];
|
||||||
|
|
||||||
|
if (!component) continue;
|
||||||
|
|
||||||
|
var ilType = component.GetIl2CppType();
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
|
||||||
|
{
|
||||||
|
BehaviourEnabledBtn(component.TryCast<Behaviour>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.Space(26);
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
|
||||||
|
{
|
||||||
|
ReflectObject(component);
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||||
|
{
|
||||||
|
m_cachedDestroyList.Add(component);
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) }))
|
|
||||||
{
|
|
||||||
ReflectObject(component);
|
|
||||||
}
|
|
||||||
if (GUILayout.Button("<color=red>-</color>", new GUILayoutOption[] { GUILayout.Width(20) }))
|
|
||||||
{
|
|
||||||
m_cachedDestroyList.Add(component);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
if (m_cachedDestroyList.Count > 0)
|
if (m_cachedDestroyList.Count > 0)
|
||||||
{
|
{
|
||||||
@ -272,25 +369,7 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
UIHelpers.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();
|
GUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
@ -320,7 +399,7 @@ namespace Explorer
|
|||||||
|
|
||||||
private void GameObjectControls()
|
private void GameObjectControls()
|
||||||
{
|
{
|
||||||
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(530) });
|
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
|
||||||
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
|
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
@ -331,15 +410,16 @@ namespace Explorer
|
|||||||
|
|
||||||
UIHelpers.InstantiateButton(m_object, 100);
|
UIHelpers.InstantiateButton(m_object, 100);
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
if (GUILayout.Button("Set DontDestroyOnLoad", new GUILayoutOption[] { GUILayout.Width(170) }))
|
||||||
|
{
|
||||||
|
GameObject.DontDestroyOnLoad(m_object);
|
||||||
|
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
if (GUILayout.Button("Remove from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
|
m_setParentInput = GUILayout.TextField(m_setParentInput, null);
|
||||||
{
|
|
||||||
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 (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
{
|
{
|
||||||
if (GameObject.Find(m_setParentInput) is GameObject newparent)
|
if (GameObject.Find(m_setParentInput) is GameObject newparent)
|
||||||
@ -351,6 +431,11 @@ namespace Explorer
|
|||||||
MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'");
|
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.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||||
@ -362,7 +447,7 @@ namespace Explorer
|
|||||||
|
|
||||||
GUILayout.EndVertical();
|
GUILayout.EndVertical();
|
||||||
|
|
||||||
if (GUILayout.Button("<color=red><b>Destroy</b></color>", null))
|
if (GUILayout.Button("<color=red><b>Destroy</b></color>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||||
{
|
{
|
||||||
GameObject.Destroy(m_object);
|
GameObject.Destroy(m_object);
|
||||||
DestroyWindow();
|
DestroyWindow();
|
||||||
|
@ -5,7 +5,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using MelonLoader;
|
using MelonLoader;
|
||||||
using Mono.CSharp;
|
|
||||||
using UnhollowerBaseLib;
|
using UnhollowerBaseLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@ -13,49 +12,64 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
public class ReflectionWindow : UIWindow
|
public class ReflectionWindow : UIWindow
|
||||||
{
|
{
|
||||||
public override string Name { get => "Object Reflection"; set => Name = value; }
|
public override string Title => WindowManager.TabView
|
||||||
|
? $"<color=cyan>[R]</color> {TargetType.Name}"
|
||||||
|
: $"Reflection Inspector ({TargetType.Name})";
|
||||||
|
|
||||||
public Type ObjectType;
|
public Type TargetType;
|
||||||
|
|
||||||
private CacheObject[] m_cachedMembers;
|
private CacheObjectBase[] m_allCachedMembers;
|
||||||
private CacheObject[] m_cachedMemberFiltered;
|
private CacheObjectBase[] m_cachedMembersFiltered;
|
||||||
private int m_pageOffset;
|
|
||||||
|
public PageHelper Pages = new PageHelper();
|
||||||
|
//private int m_pageOffset;
|
||||||
|
//private int m_limitPerPage = 20;
|
||||||
|
|
||||||
private bool m_autoUpdate = false;
|
private bool m_autoUpdate = false;
|
||||||
private string m_search = "";
|
private string m_search = "";
|
||||||
public MemberInfoType m_filter = MemberInfoType.Property;
|
public MemberTypes m_filter = MemberTypes.Property;
|
||||||
|
private bool m_hideFailedReflection = false;
|
||||||
|
|
||||||
public enum MemberInfoType
|
// some extra caching
|
||||||
{
|
private UnityEngine.Object m_uObj;
|
||||||
Field,
|
private Component m_component;
|
||||||
Property,
|
|
||||||
Method,
|
|
||||||
All
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Init()
|
public override void Init()
|
||||||
{
|
{
|
||||||
var type = ReflectionHelpers.GetActualType(Target);
|
var type = ReflectionHelpers.GetActualType(Target);
|
||||||
if (type == null)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Could not get underlying type for object. ToString(): " + Target.ToString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectType = type;
|
TargetType = type;
|
||||||
|
|
||||||
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
||||||
|
|
||||||
CacheMembers(types);
|
CacheMembers(types);
|
||||||
|
|
||||||
m_filter = MemberInfoType.All;
|
if (Target is Il2CppSystem.Object ilObject)
|
||||||
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
{
|
||||||
UpdateValues();
|
var unityObj = ilObject.TryCast<UnityEngine.Object>();
|
||||||
m_filter = MemberInfoType.Property;
|
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()
|
public override void Update()
|
||||||
{
|
{
|
||||||
m_cachedMemberFiltered = m_cachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||||
|
|
||||||
if (m_autoUpdate)
|
if (m_autoUpdate)
|
||||||
{
|
{
|
||||||
@ -65,94 +79,106 @@ namespace Explorer
|
|||||||
|
|
||||||
private void UpdateValues()
|
private void UpdateValues()
|
||||||
{
|
{
|
||||||
UpdateMembers();
|
foreach (var member in m_cachedMembersFiltered)
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateMembers()
|
|
||||||
{
|
|
||||||
foreach (var member in m_cachedMemberFiltered)
|
|
||||||
{
|
{
|
||||||
member.UpdateValue(Target);
|
member.UpdateValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldProcessMember(CacheObject holder)
|
private bool ShouldProcessMember(CacheObjectBase holder)
|
||||||
{
|
{
|
||||||
if (m_filter != MemberInfoType.All && m_filter != holder.MemberInfoType) return false;
|
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType) return false;
|
||||||
|
|
||||||
if (m_search == "" || holder.MemberInfo == null) return true;
|
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
|
||||||
|
|
||||||
return holder.MemberInfo.Name
|
if (m_search == "" || holder.MemInfo == null) return true;
|
||||||
.ToLower()
|
|
||||||
.Contains(m_search.ToLower());
|
var name = holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name;
|
||||||
|
|
||||||
|
return name.ToLower().Contains(m_search.ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CacheMembers(Type[] types, List<string> names = null)
|
private void CacheMembers(Type[] types)
|
||||||
{
|
{
|
||||||
if (names == null)
|
var list = new List<CacheObjectBase>();
|
||||||
{
|
|
||||||
names = new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
var list = new List<CacheObject>();
|
var names = new List<string>();
|
||||||
|
|
||||||
foreach (var type in types)
|
foreach (var declaringType in types)
|
||||||
{
|
{
|
||||||
MemberInfo[] infos;
|
MemberInfo[] infos;
|
||||||
|
string exception = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
infos = type.GetMembers(ReflectionHelpers.CommonFlags);
|
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
MelonLogger.Log("Exception getting members for type: " + type.Name);
|
MelonLogger.Log($"Exception getting members for type: {declaringType.FullName}");
|
||||||
continue;
|
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)
|
foreach (var member in infos)
|
||||||
{
|
{
|
||||||
try
|
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method)
|
||||||
{
|
{
|
||||||
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property)
|
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")
|
||||||
{
|
{
|
||||||
if (member.Name == "Il2CppType") continue;
|
continue;
|
||||||
|
}
|
||||||
if (names.Contains(member.Name)) continue;
|
|
||||||
names.Add(member.Name);
|
if (member is MethodInfo mi)
|
||||||
|
{
|
||||||
object value = null;
|
name += " (";
|
||||||
object target = Target;
|
foreach (var param in mi.GetParameters())
|
||||||
|
{
|
||||||
if (target is Il2CppSystem.Object ilObject)
|
name += param.ParameterType.Name + ", ";
|
||||||
{
|
}
|
||||||
if (member.DeclaringType == typeof(Il2CppObjectBase)) continue;
|
name += ")";
|
||||||
|
}
|
||||||
target = ilObject.Il2CppCast(member.DeclaringType);
|
if (names.Contains(name))
|
||||||
}
|
{
|
||||||
|
continue;
|
||||||
if (member is FieldInfo)
|
}
|
||||||
{
|
|
||||||
value = (member as FieldInfo).GetValue(target);
|
try
|
||||||
}
|
{
|
||||||
else if (member is PropertyInfo)
|
var cached = CacheObjectBase.GetCacheObject(member, target);
|
||||||
{
|
if (cached != null)
|
||||||
value = (member as PropertyInfo).GetValue(target);
|
{
|
||||||
}
|
names.Add(name);
|
||||||
|
list.Add(cached);
|
||||||
list.Add(CacheObject.GetCacheObject(value, member, Target));
|
cached.ReflectionException = exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
MelonLogger.LogWarning($"Exception caching member {name}!");
|
||||||
|
MelonLogger.Log(e.ToString());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Exception caching member " + member.Name + "!");
|
|
||||||
MelonLogger.Log(e.GetType() + ", " + e.Message);
|
|
||||||
MelonLogger.Log(e.StackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_cachedMembers = list.ToArray();
|
m_allCachedMembers = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========== GUI DRAW =========== //
|
// =========== GUI DRAW =========== //
|
||||||
@ -161,40 +187,42 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Header();
|
// ====== HEADER ======
|
||||||
|
|
||||||
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
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.BeginHorizontal(null);
|
||||||
GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.Name + "</color>", null);
|
GUILayout.Label("<b>Type:</b> <color=cyan>" + TargetType.FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(245f) });
|
||||||
|
if (m_uObj)
|
||||||
bool unityObj = Target is UnityEngine.Object;
|
|
||||||
|
|
||||||
if (unityObj)
|
|
||||||
{
|
{
|
||||||
GUILayout.Label("Name: " + (Target as UnityEngine.Object).name, null);
|
GUILayout.Label("Name: " + m_uObj.name, null);
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
if (unityObj)
|
if (m_uObj)
|
||||||
{
|
{
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||||
|
UIHelpers.InstantiateButton(m_uObj);
|
||||||
UIHelpers.InstantiateButton((UnityEngine.Object)Target);
|
if (m_component && m_component.gameObject is GameObject obj)
|
||||||
|
|
||||||
if (Target is Component comp && comp.gameObject is GameObject obj)
|
|
||||||
{
|
{
|
||||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||||
GUILayout.Label("GameObject:", null);
|
GUILayout.Label("GameObject:", new GUILayoutOption[] { GUILayout.Width(135) });
|
||||||
if (GUILayout.Button("<color=#00FF00>" + obj.name + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(m_rect.width - 350) }))
|
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 _);
|
WindowManager.InspectObject(obj, out bool _);
|
||||||
}
|
}
|
||||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,9 +235,10 @@ namespace Explorer
|
|||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
|
||||||
FilterToggle(MemberInfoType.All, "All");
|
FilterToggle(MemberTypes.All, "All");
|
||||||
FilterToggle(MemberInfoType.Property, "Properties");
|
FilterToggle(MemberTypes.Property, "Properties");
|
||||||
FilterToggle(MemberInfoType.Field, "Fields");
|
FilterToggle(MemberTypes.Field, "Fields");
|
||||||
|
FilterToggle(MemberTypes.Method, "Methods");
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(null);
|
GUILayout.BeginHorizontal(null);
|
||||||
@ -220,105 +249,98 @@ namespace Explorer
|
|||||||
}
|
}
|
||||||
GUI.color = m_autoUpdate ? Color.green : Color.red;
|
GUI.color = m_autoUpdate ? Color.green : Color.red;
|
||||||
m_autoUpdate = GUILayout.Toggle(m_autoUpdate, "Auto-update?", new GUILayoutOption[] { GUILayout.Width(100) });
|
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;
|
GUI.color = Color.white;
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
GUILayout.Space(10);
|
GUILayout.Space(10);
|
||||||
|
|
||||||
int count = m_cachedMemberFiltered.Length;
|
Pages.Count = m_cachedMembersFiltered.Length;
|
||||||
|
|
||||||
if (count > 20)
|
// prev/next page buttons
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
|
||||||
|
Pages.DrawLimitInputArea();
|
||||||
|
|
||||||
|
if (Pages.Count > Pages.PageLimit)
|
||||||
{
|
{
|
||||||
// prev/next page buttons
|
Pages.CalculateMaxOffset();
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
int maxOffset = (int)Mathf.Ceil(count / 20);
|
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
if (GUILayout.Button("< Prev", null))
|
|
||||||
{
|
{
|
||||||
if (m_pageOffset > 0) m_pageOffset--;
|
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
Pages.CurrentPageLabel();
|
||||||
|
|
||||||
if (GUILayout.Button("Next >", null))
|
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||||
{
|
{
|
||||||
if (m_pageOffset < maxOffset) m_pageOffset++;
|
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||||
}
|
}
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView);
|
// ====== BODY ======
|
||||||
|
|
||||||
|
scroll = UIHelpers.BeginScrollView(scroll);
|
||||||
|
|
||||||
GUILayout.Space(10);
|
GUILayout.Space(10);
|
||||||
|
|
||||||
DrawMembers(this.m_cachedMemberFiltered);
|
UIStyles.HorizontalLine(Color.grey);
|
||||||
|
|
||||||
GUILayout.EndScrollView();
|
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||||
|
|
||||||
m_rect = WindowManager.ResizeWindow(m_rect, windowID);
|
var members = this.m_cachedMembersFiltered;
|
||||||
|
int start = Pages.CalculateOffsetIndex();
|
||||||
|
|
||||||
GUILayout.EndArea();
|
for (int j = start; (j < start + Pages.PageLimit && j < members.Length); j++)
|
||||||
|
{
|
||||||
|
var holder = members[j];
|
||||||
|
|
||||||
|
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
||||||
|
try
|
||||||
|
{
|
||||||
|
holder.Draw(rect, 180f);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
// if not last element
|
||||||
|
if (!(j == (start + Pages.PageLimit - 1) || j == (members.Length - 1)))
|
||||||
|
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
UIHelpers.EndScrollView();
|
||||||
|
|
||||||
|
if (!WindowManager.TabView)
|
||||||
|
{
|
||||||
|
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
|
||||||
|
|
||||||
|
GUILayout.EndArea();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Il2CppException e)
|
||||||
|
{
|
||||||
|
if (!e.Message.Contains("in a group with only"))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
MelonLogger.LogWarning("Exception on window draw. Message: " + e.Message);
|
MelonLogger.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message);
|
||||||
DestroyWindow();
|
DestroyWindow();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMembers(CacheObject[] members)
|
private void FilterToggle(MemberTypes mode, string label)
|
||||||
{
|
|
||||||
// todo pre-cache list based on current search, otherwise this doesnt work.
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
DrawMembersInternal("Properties", MemberInfoType.Property, members, ref i);
|
|
||||||
DrawMembersInternal("Fields", MemberInfoType.Field, members, ref i);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawMembersInternal(string title, MemberInfoType filter, CacheObject[] members, ref int index)
|
|
||||||
{
|
|
||||||
if (m_filter != filter && m_filter != MemberInfoType.All)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIStyles.HorizontalLine(Color.grey);
|
|
||||||
|
|
||||||
GUILayout.Label($"<size=18><b><color=gold>{title}</color></b></size>", null);
|
|
||||||
|
|
||||||
int offset = (m_pageOffset * 20) + index;
|
|
||||||
|
|
||||||
if (offset >= m_cachedMemberFiltered.Length)
|
|
||||||
{
|
|
||||||
m_pageOffset = 0;
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int j = offset; j < offset + 20 && j < members.Length; j++)
|
|
||||||
{
|
|
||||||
var holder = members[j];
|
|
||||||
|
|
||||||
if (holder.MemberInfoType != filter || !ShouldProcessMember(holder)) continue;
|
|
||||||
|
|
||||||
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
|
|
||||||
try
|
|
||||||
{
|
|
||||||
holder.Draw(this.m_rect, 180f);
|
|
||||||
}
|
|
||||||
catch // (Exception e)
|
|
||||||
{
|
|
||||||
//MelonLogger.Log("Exception drawing member " + holder.MemberInfo.Name);
|
|
||||||
//MelonLogger.Log(e.GetType() + ", " + e.Message);
|
|
||||||
//MelonLogger.Log(e.StackTrace);
|
|
||||||
}
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (index >= 20) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FilterToggle(MemberInfoType mode, string label)
|
|
||||||
{
|
{
|
||||||
if (m_filter == mode)
|
if (m_filter == mode)
|
||||||
{
|
{
|
||||||
@ -331,6 +353,8 @@ namespace Explorer
|
|||||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||||
{
|
{
|
||||||
m_filter = mode;
|
m_filter = mode;
|
||||||
|
Pages.PageOffset = 0;
|
||||||
|
scroll = Vector2.zero;
|
||||||
}
|
}
|
||||||
GUI.color = Color.white;
|
GUI.color = Color.white;
|
||||||
}
|
}
|
||||||
|
106
src/Windows/ResizeDrag.cs
Normal file
106
src/Windows/ResizeDrag.cs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using UnityEngine;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnhollowerBaseLib;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class ResizeDrag
|
||||||
|
{
|
||||||
|
private static bool RESIZE_FAILED = false;
|
||||||
|
|
||||||
|
private static readonly GUIContent gcDrag = new GUIContent("<-- Drag to resize -->");
|
||||||
|
private static bool isResizing = false;
|
||||||
|
private static Rect m_currentResize;
|
||||||
|
private static int m_currentWindow;
|
||||||
|
|
||||||
|
public static Rect ResizeWindow(Rect _rect, int ID)
|
||||||
|
{
|
||||||
|
if (!RESIZE_FAILED)
|
||||||
|
{
|
||||||
|
var origRect = _rect;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
||||||
|
|
||||||
|
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||||
|
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
|
||||||
|
|
||||||
|
var r = GUILayoutUtility.GetLastRect();
|
||||||
|
|
||||||
|
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
|
||||||
|
|
||||||
|
if (r.Contains(mouse) && Input.GetMouseButtonDown(0))
|
||||||
|
{
|
||||||
|
isResizing = true;
|
||||||
|
m_currentWindow = ID;
|
||||||
|
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
|
||||||
|
}
|
||||||
|
else if (!Input.GetMouseButton(0))
|
||||||
|
{
|
||||||
|
isResizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isResizing && ID == m_currentWindow)
|
||||||
|
{
|
||||||
|
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
|
||||||
|
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
|
||||||
|
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
|
||||||
|
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
catch (Il2CppException e) when (e.Message.StartsWith("System.ArgumentException"))
|
||||||
|
{
|
||||||
|
// suppress
|
||||||
|
return origRect;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
RESIZE_FAILED = true;
|
||||||
|
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
|
||||||
|
return origRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
||||||
|
|
||||||
|
GUILayout.Label("Resize window:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||||
|
|
||||||
|
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||||
|
GUILayout.Label("<color=cyan>Width:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
|
||||||
|
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||||
|
{
|
||||||
|
_rect.width -= 5f;
|
||||||
|
}
|
||||||
|
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||||
|
{
|
||||||
|
_rect.width += 5f;
|
||||||
|
}
|
||||||
|
GUILayout.Label("<color=cyan>Height:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
|
||||||
|
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||||
|
{
|
||||||
|
_rect.height -= 5f;
|
||||||
|
}
|
||||||
|
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
|
||||||
|
{
|
||||||
|
_rect.height += 5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
116
src/Windows/TabViewWindow.cs
Normal file
116
src/Windows/TabViewWindow.cs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using MelonLoader;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Explorer
|
||||||
|
{
|
||||||
|
public class TabViewWindow : UIWindow
|
||||||
|
{
|
||||||
|
public override string Title => $"Tabs ({WindowManager.Windows.Count})";
|
||||||
|
|
||||||
|
public static TabViewWindow Instance => m_instance ?? (m_instance = new TabViewWindow());
|
||||||
|
private static TabViewWindow m_instance;
|
||||||
|
|
||||||
|
private UIWindow m_targetWindow;
|
||||||
|
public int TargetTabID = 0;
|
||||||
|
|
||||||
|
public override bool IsTabViewWindow => true;
|
||||||
|
|
||||||
|
public TabViewWindow()
|
||||||
|
{
|
||||||
|
m_rect = new Rect(570, 0, 550, 700);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Init() { }
|
||||||
|
public override void Update()
|
||||||
|
{
|
||||||
|
while (TargetTabID >= WindowManager.Windows.Count)
|
||||||
|
{
|
||||||
|
TargetTabID--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TargetTabID == -1 && WindowManager.Windows.Count > 0)
|
||||||
|
{
|
||||||
|
TargetTabID = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TargetTabID >= 0)
|
||||||
|
{
|
||||||
|
m_targetWindow = WindowManager.Windows[TargetTabID];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_targetWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_targetWindow?.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WindowFunction(int windowID)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20));
|
||||||
|
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red>Close All</color>"))
|
||||||
|
{
|
||||||
|
foreach (var window in WindowManager.Windows)
|
||||||
|
{
|
||||||
|
window.DestroyWindow();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
|
||||||
|
|
||||||
|
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||||
|
int tabPerRow = Mathf.FloorToInt((float)((decimal)m_rect.width / 238));
|
||||||
|
int rowCount = 0;
|
||||||
|
for (int i = 0; i < WindowManager.Windows.Count; i++)
|
||||||
|
{
|
||||||
|
if (rowCount >= tabPerRow)
|
||||||
|
{
|
||||||
|
rowCount = 0;
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.BeginHorizontal(null);
|
||||||
|
}
|
||||||
|
rowCount++;
|
||||||
|
|
||||||
|
bool focused = i == TargetTabID;
|
||||||
|
string color = focused ? "<color=lime>" : "<color=orange>";
|
||||||
|
GUI.color = focused ? Color.green : Color.white;
|
||||||
|
|
||||||
|
var window = WindowManager.Windows[i];
|
||||||
|
if (GUILayout.Button(color + window.Title + "</color>", new GUILayoutOption[] { GUILayout.Width(200) }))
|
||||||
|
{
|
||||||
|
TargetTabID = i;
|
||||||
|
}
|
||||||
|
if (GUILayout.Button("<color=red><b>X</b></color>", new GUILayoutOption[] { GUILayout.Width(22) }))
|
||||||
|
{
|
||||||
|
window.DestroyWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GUI.color = Color.white;
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||||
|
|
||||||
|
m_targetWindow.WindowFunction(m_targetWindow.windowID);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_rect = ResizeDrag.ResizeWindow(m_rect, windowID);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
GUILayout.EndArea();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,14 +7,12 @@ using MelonLoader;
|
|||||||
using UnhollowerBaseLib;
|
using UnhollowerBaseLib;
|
||||||
using UnhollowerRuntimeLib;
|
using UnhollowerRuntimeLib;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
|
|
||||||
namespace Explorer
|
namespace Explorer
|
||||||
{
|
{
|
||||||
public abstract class UIWindow
|
public abstract class UIWindow
|
||||||
{
|
{
|
||||||
public abstract string Name { get; set; }
|
public abstract string Title { get; }
|
||||||
|
|
||||||
public object Target;
|
public object Target;
|
||||||
|
|
||||||
@ -23,6 +21,8 @@ namespace Explorer
|
|||||||
|
|
||||||
public Vector2 scroll = Vector2.zero;
|
public Vector2 scroll = Vector2.zero;
|
||||||
|
|
||||||
|
public virtual bool IsTabViewWindow => false;
|
||||||
|
|
||||||
public abstract void Init();
|
public abstract void Init();
|
||||||
public abstract void WindowFunction(int windowID);
|
public abstract void WindowFunction(int windowID);
|
||||||
public abstract void Update();
|
public abstract void Update();
|
||||||
@ -44,15 +44,7 @@ namespace Explorer
|
|||||||
|
|
||||||
public void DestroyWindow()
|
public void DestroyWindow()
|
||||||
{
|
{
|
||||||
try
|
WindowManager.DestroyWindow(this);
|
||||||
{
|
|
||||||
WindowManager.Windows.Remove(this);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
MelonLogger.Log("Exception removing Window from WindowManager.Windows list!");
|
|
||||||
MelonLogger.Log($"{e.GetType()} : {e.Message}\r\n{e.StackTrace}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnGUI()
|
public void OnGUI()
|
||||||
@ -62,7 +54,7 @@ namespace Explorer
|
|||||||
var origSkin = GUI.skin;
|
var origSkin = GUI.skin;
|
||||||
|
|
||||||
GUI.skin = UIStyles.WindowSkin;
|
GUI.skin = UIStyles.WindowSkin;
|
||||||
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Name);
|
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Title);
|
||||||
|
|
||||||
GUI.skin = origSkin;
|
GUI.skin = origSkin;
|
||||||
}
|
}
|
||||||
@ -70,12 +62,15 @@ namespace Explorer
|
|||||||
|
|
||||||
public void Header()
|
public void Header()
|
||||||
{
|
{
|
||||||
GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20));
|
if (!WindowManager.TabView)
|
||||||
|
|
||||||
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red><b>X</b></color>"))
|
|
||||||
{
|
{
|
||||||
DestroyWindow();
|
GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20));
|
||||||
return;
|
|
||||||
|
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red><b>X</b></color>"))
|
||||||
|
{
|
||||||
|
DestroyWindow();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,33 +16,160 @@ namespace Explorer
|
|||||||
{
|
{
|
||||||
public static WindowManager Instance;
|
public static WindowManager Instance;
|
||||||
|
|
||||||
|
public static bool TabView = true;
|
||||||
|
|
||||||
public static List<UIWindow> Windows = new List<UIWindow>();
|
public static List<UIWindow> Windows = new List<UIWindow>();
|
||||||
public static int CurrentWindowID { get; set; } = 500000;
|
public static int CurrentWindowID { get; set; } = 500000;
|
||||||
private static Rect m_lastWindowRect;
|
private static Rect m_lastWindowRect;
|
||||||
|
|
||||||
|
private static readonly List<UIWindow> m_windowsToDestroy = new List<UIWindow>();
|
||||||
|
|
||||||
public WindowManager()
|
public WindowManager()
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void DestroyWindow(UIWindow window)
|
||||||
|
{
|
||||||
|
m_windowsToDestroy.Add(window);
|
||||||
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
foreach (var window in Windows)
|
if (m_windowsToDestroy.Count > 0)
|
||||||
{
|
{
|
||||||
window.Update();
|
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()
|
public void OnGUI()
|
||||||
{
|
{
|
||||||
foreach (var window in Windows)
|
if (TabView)
|
||||||
{
|
{
|
||||||
window.OnGUI();
|
if (Windows.Count > 0)
|
||||||
|
{
|
||||||
|
TabViewWindow.Instance.OnGUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var window in Windows)
|
||||||
|
{
|
||||||
|
window.OnGUI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========= Public Helpers =========
|
// ========= Public Helpers =========
|
||||||
|
|
||||||
|
public static UIWindow InspectObject(object obj, out bool createdNew, bool forceReflection = false)
|
||||||
|
{
|
||||||
|
createdNew = false;
|
||||||
|
|
||||||
|
if (Input.GetKey(KeyCode.LeftShift))
|
||||||
|
{
|
||||||
|
forceReflection = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Il2CppSystem.Object iObj = null;
|
||||||
|
if (obj is Il2CppSystem.Object isObj)
|
||||||
|
{
|
||||||
|
iObj = isObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!forceReflection)
|
||||||
|
{
|
||||||
|
foreach (var window in Windows)
|
||||||
|
{
|
||||||
|
bool equals = ReferenceEquals(obj, window.Target);
|
||||||
|
|
||||||
|
if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
|
||||||
|
{
|
||||||
|
if (iCurrent.GetIl2CppType() != iTarget.GetIl2CppType())
|
||||||
|
{
|
||||||
|
if (iCurrent is Transform transform)
|
||||||
|
{
|
||||||
|
iCurrent = transform.gameObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
equals = iCurrent.Pointer == iTarget.Pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (equals)
|
||||||
|
{
|
||||||
|
FocusWindow(window);
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createdNew = true;
|
||||||
|
if (!forceReflection && (obj is GameObject || obj is Transform))
|
||||||
|
{
|
||||||
|
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return InspectReflection(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FocusWindow(UIWindow window)
|
||||||
|
{
|
||||||
|
if (!TabView)
|
||||||
|
{
|
||||||
|
GUI.BringWindowToFront(window.windowID);
|
||||||
|
GUI.FocusWindow(window.windowID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TabViewWindow.Instance.TargetTabID = Windows.IndexOf(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UIWindow InspectGameObject(GameObject obj)
|
||||||
|
{
|
||||||
|
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
|
||||||
|
FocusWindow(new_window);
|
||||||
|
|
||||||
|
return new_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UIWindow InspectReflection(object obj)
|
||||||
|
{
|
||||||
|
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
|
||||||
|
FocusWindow(new_window);
|
||||||
|
|
||||||
|
return new_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Misc Helpers ===
|
||||||
|
|
||||||
public static bool IsMouseInWindow
|
public static bool IsMouseInWindow
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -97,93 +224,5 @@ namespace Explorer
|
|||||||
|
|
||||||
return rect;
|
return rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UIWindow InspectObject(object obj, out bool createdNew)
|
|
||||||
{
|
|
||||||
createdNew = false;
|
|
||||||
|
|
||||||
foreach (var window in Windows)
|
|
||||||
{
|
|
||||||
if (obj == window.Target)
|
|
||||||
{
|
|
||||||
GUI.BringWindowToFront(window.windowID);
|
|
||||||
GUI.FocusWindow(window.windowID);
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createdNew = true;
|
|
||||||
if (obj is GameObject || obj is Transform)
|
|
||||||
{
|
|
||||||
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return InspectReflection(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static UIWindow InspectGameObject(GameObject obj)
|
|
||||||
{
|
|
||||||
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
|
|
||||||
GUI.FocusWindow(new_window.windowID);
|
|
||||||
|
|
||||||
return new_window;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UIWindow InspectReflection(object obj)
|
|
||||||
{
|
|
||||||
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
|
|
||||||
GUI.FocusWindow(new_window.windowID);
|
|
||||||
|
|
||||||
return new_window;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============= Resize Window Helper ============
|
|
||||||
|
|
||||||
// static readonly GUIContent gcDrag = new GUIContent("<->", "drag to resize");
|
|
||||||
private static readonly GUIContent gcDrag = new GUIContent("<->");
|
|
||||||
private static bool isResizing = false;
|
|
||||||
private static Rect m_currentResize;
|
|
||||||
private static int m_currentWindow;
|
|
||||||
|
|
||||||
public static Rect ResizeWindow(Rect _rect, int ID)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
GUILayout.BeginHorizontal(null);
|
|
||||||
GUILayout.Space(_rect.width - 35);
|
|
||||||
|
|
||||||
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Width(25), GUILayout.Height(25) });
|
|
||||||
|
|
||||||
var r = GUILayoutUtility.GetLastRect();
|
|
||||||
|
|
||||||
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
|
|
||||||
|
|
||||||
if (r.Contains(mouse) && Input.GetMouseButtonDown(0))
|
|
||||||
{
|
|
||||||
isResizing = true;
|
|
||||||
m_currentWindow = ID;
|
|
||||||
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
|
|
||||||
}
|
|
||||||
else if (!Input.GetMouseButton(0))
|
|
||||||
{
|
|
||||||
isResizing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isResizing && ID == m_currentWindow)
|
|
||||||
{
|
|
||||||
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
|
|
||||||
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
|
|
||||||
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
|
|
||||||
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
return _rect;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user