Compare commits

...

29 Commits
1.5.1 ... 1.6.4

Author SHA1 Message Date
c8a3aecdf4 Update README.md 2020-09-08 17:09:23 +10:00
33c2378f41 Update README.md 2020-09-08 17:09:12 +10:00
38aafa7e5b 1.6.4
* Fix for games which do not load InputModule on startup. CppExplorer will now try to load the module itself.
* Cleanups
2020-09-08 17:07:10 +10:00
4bb0811b2c 1.6.3
* Merged the two builds into one, there is now only one release. Using Reflection for UnityEngine.Input
* A few small fixes and cleanups
2020-09-08 06:21:45 +10:00
4aefe1c5a3 a few tidy ups 2020-09-08 04:33:27 +10:00
c228d29707 Update CppExplorer.cs 2020-09-07 20:28:43 +10:00
56d1507aff 1.6.2
* Fix for a crash that can occur when inspecting unsupported Dictionaries
* Added a scroll bar to the REPL console input area, fixes the issue of the code just being cut off when it goes too long.
2020-09-07 20:28:33 +10:00
72d31eaa64 1.6.1
* Fix for when inspected object gets destroyed
* Fix for displaying Dictionaries/Lists nested inside a Dictionary
* Cleanups
2020-09-07 17:05:37 +10:00
4e8b84b67e Update CppExplorer.cs 2020-09-07 03:26:10 +10:00
5b94e31a12 1.6.0
* Fix for failed unstrip with RectOffset(int, int, int, int) ctor
* Cleanups
2020-09-07 03:25:43 +10:00
692a37635e 1.5.9
* Added beta support for Dictionaries. Should work fine for simple dictionaries, may be janky or broken for more complex ones (eg. Dicts nested inside a Dict).
* Fixed a bug with Lists of primitive values.
2020-09-06 21:33:09 +10:00
9cb1cea025 Update README.md 2020-09-06 17:48:40 +10:00
e13f198815 1.5.8
* Fixed a bug where the Page Helper would not update the total page count after changing the limit per page
* Cleaned up the "Find Instances" helper, it will now filter out all types in the `System`, `Mono`, `Il2CppSystem` and `Iced` namespaces.
* Improved the Find Instances helper so that it will avoid exceptions and get more results.
* Enums now display their value type name
* Changed the Scroll View unstrip so that it is less hard-coded for different unity versions and more dynamic.
2020-09-06 16:55:39 +10:00
9a059c1056 Update ScenePage.cs 2020-09-06 03:19:39 +10:00
ffb6cad8c2 1.5.7
* More fixes for failed unstripping, should fix most issues in Audica and other games
* If `GetRootSceneObjects` fails and we fall back to the manual implementation, the auto-update for root scene objects will be disabled. Instead, there will be a button to press to update the list.
* Transforms are now listed on the Components list in the GameObject inspector
* Various cleanups
2020-09-06 03:19:21 +10:00
d0a4863139 Update ScenePage.cs 2020-09-05 23:18:58 +10:00
bb8837d58c 1.5.6 hotfix
* Fix for setting CacheColor value
* Cleanup
2020-09-05 23:10:50 +10:00
a236b272c1 Update README.md 2020-09-05 20:41:48 +10:00
18de1eaf1c 1.5.6
Cleanup
2020-09-05 20:38:46 +10:00
b1264c6912 1.5.6
* Added a fallback method for GetRootSceneObjects for games where this fails.
* Fixed an issue where the `new Rect(Rect source)` constructor was failing in some games, using the normal ctor now.
* Added special support for `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Color` and `Rect` structs in the reflection inspector to allow for easier editing.
* Several improvements to GameObject Inspector, such as position/rotation freezing, local/global context, and an improved way to edit the transform values.
2020-09-05 20:27:00 +10:00
9836566e55 tidy up 2020-09-05 01:30:50 +10:00
d20461fa0e 1.5.5
* Fix for GetRootSceneObjects
* Tidy ups
2020-09-04 23:49:43 +10:00
72ec34090d 1.5.4 cleanup 2020-09-04 21:51:38 +10:00
883a8705c3 Update README.md 2020-09-04 21:42:09 +10:00
6adaaf5500 1.5.4
* Implemented manual unstripping for ScrollView and Resize, should now work on any Unity 2018 or 2019 game.
* Fixed a bug with page view on the Scene Explorer
* Back-end cleanups
2020-09-04 21:36:40 +10:00
5de771389e 1.5.3
* Added exception handling for scrollview when unstripping fails
* Added some better logging in some places
2020-09-04 01:27:44 +10:00
51cfbe524e Update README.md 2020-09-03 20:59:54 +10:00
217b93ef4f 1.5.2
* Added ability to force Reflection Inspector for GameObjects and Transforms if you hold Left Shift while clicking the Inspect button
* Fixed a bug causing duplicate windows to open when you inspect Transforms, the current active window will now be focused. Note: does not apply if you hold Left Shift for forced reflection.
2020-09-03 20:58:04 +10:00
42156e1160 1.5.2
* Added page view to GameObject Children/Component lists
* Made a generic Page Handler helper class, replaced all page view implementations with the helper (no real change for users but should make things easier to maintain in the future, and they were basically all copy+pastes).
2020-09-03 19:48:50 +10:00
42 changed files with 2754 additions and 845 deletions

View File

@ -1,4 +1,4 @@
# CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.7.1-green.svg)]() # CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.7.1-green.svg)](https://github.com/HerpDerpinstine/MelonLoader)
<p align="center"> <p align="center">
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png"> <img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
@ -14,18 +14,9 @@
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" /> <img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
</p> </p>
### Known issue ### Known issues
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. * CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
If you're familiar with C# and Unity, one possibility for now is making a fork of this repo and manually fixing all the broken methods to ones which aren't broken (if possible). There may be another overload of the same method which wasn't stripped or was unstripped successfully, which you can use instead.
## Features
* Scene hierarchy explorer
* Search loaded assets with filters
* Traverse and manipulate GameObjects
* Generic Reflection inspector
* C# REPL Console
* Inspect-under-mouse
## How to install ## How to install
@ -40,48 +31,52 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
* 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, most of it is pretty self-explanatory. * Simply browse through the scene, search for objects, etc, most of it is pretty self-explanatory.
### Features
[![](img.png)](img.png)
<i>An overview of the different CppExplorer menus.</i>
* Scene hierarchy explorer
* Search loaded assets with filters
* Traverse and manipulate GameObjects
* Generic Reflection inspector
* C# REPL Console
* Inspect-under-mouse
### Scene Explorer ### Scene Explorer
* A simple menu which allows you to traverse the Transform heirarchy of the scene. * 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. * Click on a GameObject to set it as the current path, or <b>Inspect</b> it to send it to an Inspector Window.
[![](https://i.imgur.com/2b0q0jL.png)](https://i.imgur.com/2b0q0jL.png)
### Inspectors ### Inspectors
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>. CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
<b>Tip:</b> when in Tab View, GameObjects are denoted by a [G] prefix, and Reflection objects are denoted by a [R] prefix. <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.
### GameObject Inspector ### GameObject Inspector
* Allows you to see the children and components on a GameObject. * 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. * Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
[![](https://i.imgur.com/JTxqlx4.png)](https://i.imgur.com/JTxqlx4.png)
### Reflection Inspector ### Reflection Inspector
* The Reflection Inspector is used for all other supported objects. * 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. * 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. * Can search and filter members for the ones you are interested in.
[![](https://i.imgur.com/iq92m0l.png)](https://i.imgur.com/iq92m0l.png)
### Object Search ### Object Search
* You can search for an `UnityEngine.Object` with the Object Search feature. * You can search for an `UnityEngine.Object` with the Object Search feature.
* Filter by name, type, etc. * Filter by name, type, etc.
* For GameObjects and Transforms you can filter which scene they are found in too. * For GameObjects and Transforms you can filter which scene they are found in too.
[![](https://i.imgur.com/lK2RthM.png)](https://i.imgur.com/lK2RthM.png)
### C# REPL console ### C# REPL console
* A simple C# REPL console, allows you to execute a method body on the fly. * A simple C# REPL console, allows you to execute a method body on the fly.
[![](https://i.imgur.com/5U4D1a8.png)](https://i.imgur.com/5U4D1a8.png)
### Inspect-under-mouse ### Inspect-under-mouse
* Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse. * Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse.

BIN
img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

View File

@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheGameObject : CacheObjectBase
{
private GameObject GameObj
{
get
{
if (m_gameObject == null)
{
if (Value is Il2CppSystem.Object ilObj)
{
var ilType = ilObj.GetIl2CppType();
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
{
m_gameObject = ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
}
}
}
return m_gameObject;
}
}
private GameObject m_gameObject;
public override void DrawValue(Rect window, float width)
{
UIHelpers.GameobjButton(GameObj, null, false, width);
}
public override void UpdateValue()
{
base.UpdateValue();
}
}
}

View File

@ -14,16 +14,16 @@ namespace Explorer
{ {
public object Value; public object Value;
public string ValueTypeName; public string ValueTypeName;
public Type ValueType;
// Reflection Inspector only
public MemberInfo MemInfo { get; set; } public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; } public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; } public object DeclaringInstance { get; set; }
public string ReflectionException { get; set; }
public int PropertyIndex { get; private set; } public int PropertyIndex { get; private set; }
private string m_propertyIndexInput = "0"; private string m_propertyIndexInput = "0";
public string ReflectionException { get; set; }
public string RichTextName => m_richTextName ?? GetRichTextName(); public string RichTextName => m_richTextName ?? GetRichTextName();
private string m_richTextName; private string m_richTextName;
@ -79,11 +79,7 @@ namespace Explorer
{ {
Type type = null; Type type = null;
if (obj != null) if (memberInfo != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
else if (memberInfo != null)
{ {
if (memberInfo is FieldInfo fi) if (memberInfo is FieldInfo fi)
{ {
@ -98,6 +94,10 @@ namespace Explorer
type = mi.ReturnType; type = mi.ReturnType;
} }
} }
else if (obj != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
if (type == null) if (type == null)
{ {
@ -114,6 +114,11 @@ namespace Explorer
{ {
CacheObjectBase holder; CacheObjectBase holder;
// This is pretty ugly, could probably make a cleaner implementation.
// However, the only cleaner ways I can think of are slower and probably not worth it.
// Note: the order is somewhat important.
if (memberInfo is MethodInfo mi) if (memberInfo is MethodInfo mi)
{ {
if (CacheMethod.CanEvaluate(mi)) if (CacheMethod.CanEvaluate(mi))
@ -137,20 +142,38 @@ namespace Explorer
{ {
holder = new CacheEnum(); holder = new CacheEnum();
} }
else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType)) else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
{ {
holder = new CacheList(); holder = new CacheVector();
} }
else if (valueType == typeof(Quaternion))
{
holder = new CacheQuaternion();
}
else if (valueType == typeof(Color))
{
holder = new CacheColor();
}
else if (valueType == typeof(Rect))
{
holder = new CacheRect();
}
// must check this before IsEnumerable
else if (ReflectionHelpers.IsDictionary(valueType)) else if (ReflectionHelpers.IsDictionary(valueType))
{ {
holder = new CacheDictionary(); holder = new CacheDictionary();
} }
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType))
{
holder = new CacheList();
}
else else
{ {
holder = new CacheOther(); holder = new CacheOther();
} }
holder.Value = obj; holder.Value = obj;
holder.ValueType = valueType;
holder.ValueTypeName = valueType.FullName; holder.ValueTypeName = valueType.FullName;
if (memberInfo != null) if (memberInfo != null)

View File

@ -0,0 +1,299 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using System.Reflection;
using UnhollowerBaseLib;
namespace Explorer
{
public class CacheDictionary : CacheObjectBase, IExpandHeight
{
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public float ButtonWidthOffset { get; set; } = 290f;
public PageHelper Pages = new PageHelper();
private CacheObjectBase[] m_cachedKeys;
private CacheObjectBase[] m_cachedValues;
public Type TypeOfKeys
{
get
{
if (m_keysType == null) GetGenericArguments();
return m_keysType;
}
}
private Type m_keysType;
public Type TypeOfValues
{
get
{
if (m_valuesType == null) GetGenericArguments();
return m_valuesType;
}
}
private Type m_valuesType;
public IDictionary IDict
{
get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono();
set => m_iDictionary = value;
}
private IDictionary m_iDictionary;
// ========== Methods ==========
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
private IDictionary Il2CppDictionaryToMono()
{
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
// get keys and values
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
var values = ValueType.GetProperty("Values").GetValue(Value);
// create lists to hold them
var keyList = new List<object>();
var valueList = new List<object>();
// store entries with reflection
EnumerateWithReflection(keys, keyList);
EnumerateWithReflection(values, valueList);
// make actual mono dictionary
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// finally iterate into dictionary
for (int i = 0; i < keyList.Count; i++)
{
dict.Add(keyList[i], valueList[i]);
}
return dict;
}
private void EnumerateWithReflection(object collection, List<object> list)
{
// invoke GetEnumerator
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
// get the type of it
var enumeratorType = enumerator.GetType();
// reflect MoveNext and Current
var moveNext = enumeratorType.GetMethod("MoveNext");
var current = enumeratorType.GetProperty("Current");
// iterate
while ((bool)moveNext.Invoke(enumerator, null))
{
list.Add(current.GetValue(enumerator));
}
}
private void GetGenericArguments()
{
if (this.MemInfo != null)
{
Type memberType = null;
switch (this.MemInfo.MemberType)
{
case MemberTypes.Field:
memberType = (MemInfo as FieldInfo).FieldType;
break;
case MemberTypes.Property:
memberType = (MemInfo as PropertyInfo).PropertyType;
break;
}
if (memberType != null && memberType.IsGenericType)
{
m_keysType = memberType.GetGenericArguments()[0];
m_valuesType = memberType.GetGenericArguments()[1];
}
}
else if (Value != null)
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_keysType = type.GetGenericArguments()[0];
m_valuesType = type.GetGenericArguments()[1];
}
}
}
public override void UpdateValue()
{
// first make sure we won't run into a TypeInitializationException.
if (!EnsureDictionaryIsSupported())
{
ReflectionException = "Dictionary Type not supported with Reflection!";
return;
}
base.UpdateValue();
// reset
IDict = null;
if (Value == null || IDict == null)
{
return;
}
var keys = new List<CacheObjectBase>();
foreach (var key in IDict.Keys)
{
var cache = GetCacheObject(key, TypeOfKeys);
cache.UpdateValue();
keys.Add(cache);
}
var values = new List<CacheObjectBase>();
foreach (var val in IDict.Values)
{
var cache = GetCacheObject(val, TypeOfValues);
cache.UpdateValue();
values.Add(cache);
}
m_cachedKeys = keys.ToArray();
m_cachedValues = values.ToArray();
}
private bool EnsureDictionaryIsSupported()
{
try
{
return Check(TypeOfKeys) && Check(TypeOfValues);
bool Check(Type type)
{
var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(type)
.GetField("NativeClassPtr")
.GetValue(null);
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
}
}
catch
{
return false;
}
}
// ============= GUI Draw =============
public override void DrawValue(Rect window, float width)
{
if (m_cachedKeys == null || m_cachedValues == null)
{
GUILayout.Label("Cached keys or values is null!", null);
return;
}
int count = m_cachedKeys.Length;
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = $"<color=yellow>[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUILayout.Space(5);
if (IsExpanded)
{
float whitespace = WhiteSpace;
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
Pages.CurrentPageLabel();
// prev/next page buttons
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Left);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Right);
}
Pages.DrawLimitInputArea();
GUILayout.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{
var key = m_cachedKeys[i];
var val = m_cachedValues[i];
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
//GUILayout.Space(whitespace);
if (key == null || val == null)
{
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
}
else
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
key.DrawValue(window, (window.width / 2) - 30f);
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
val.DrawValue(window, (window.width / 2) - 30f);
}
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
}
}
}

View File

@ -8,27 +8,16 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
public class CacheDictionary : CacheObjectBase public class CacheGameObject : CacheObjectBase
{ {
public override void DrawValue(Rect window, float width)
public override void Init()
{ {
//base.Init(); UIHelpers.GOButton(Value, null, false, width);
Value = "Unsupported";
} }
public override void UpdateValue() public override void UpdateValue()
{ {
//base.UpdateValue(); base.UpdateValue();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label("<color=red>Dictionary (unsupported)</color>", null);
} }
} }
} }

View File

@ -8,14 +8,13 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
public class CacheList : CacheObjectBase public class CacheList : CacheObjectBase, IExpandHeight
{ {
public bool IsExpanded { get; set; } public bool IsExpanded { get; set; }
public int ArrayOffset { get; set; } public float WhiteSpace { get; set; } = 215f;
public int ArrayLimit { get; set; } = 20; public float ButtonWidthOffset { get; set; } = 290f;
public float WhiteSpace = 215f; public PageHelper Pages = new PageHelper();
public float ButtonWidthOffset = 290f;
private CacheObjectBase[] m_cachedEntries; private CacheObjectBase[] m_cachedEntries;
@ -53,6 +52,7 @@ namespace Explorer
{ {
get => GetItemProperty(); get => GetItemProperty();
} }
private PropertyInfo m_itemProperty; private PropertyInfo m_itemProperty;
// ========== Methods ========== // ========== Methods ==========
@ -219,6 +219,7 @@ namespace Explorer
if (GetCacheObject(obj, t) is CacheObjectBase cached) if (GetCacheObject(obj, t) is CacheObjectBase cached)
{ {
cached.UpdateValue();
list.Add(cached); list.Add(cached);
} }
else else
@ -230,18 +231,6 @@ namespace Explorer
{ {
list.Add(null); list.Add(null);
} }
//var type = ReflectionHelpers.GetActualType(obj);
//if (obj is Il2CppSystem.Object iObj)
//{
// obj = iObj.Il2CppCast(type);
//}
//var cached = GetCacheObject(obj, null, null, type);
//cached.UpdateValue();
//list.Add(cached);
} }
m_cachedEntries = list.ToArray(); m_cachedEntries = list.ToArray();
@ -275,7 +264,7 @@ 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.FullName + "</color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) })) if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
{ {
WindowManager.InspectObject(Value, out bool _); WindowManager.InspectObject(Value, out bool _);
@ -287,50 +276,40 @@ namespace Explorer
if (IsExpanded) if (IsExpanded)
{ {
float whitespace = WhiteSpace; float whitespace = WhiteSpace;
if (whitespace > 0) if (whitespace > 0)
{ {
ClampLabelWidth(window, ref whitespace); ClampLabelWidth(window, ref whitespace);
} }
if (count > ArrayLimit) Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
{ {
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace); GUILayout.Space(whitespace);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1; Pages.CurrentPageLabel();
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
// prev/next page buttons // prev/next page buttons
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) })) if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{ {
if (ArrayOffset > 0) ArrayOffset--; Pages.TurnPage(Turn.Left);
} }
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) })) if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{ {
if (ArrayOffset < maxOffset) ArrayOffset++; Pages.TurnPage(Turn.Right);
}
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.ArrayLimit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != ArrayLimit.ToString() && int.TryParse(limit, out int i))
{
ArrayLimit = i;
} }
Pages.DrawLimitInputArea();
GUILayout.Space(5); GUILayout.Space(5);
} }
int offset = ArrayOffset * ArrayLimit; int offset = Pages.CalculateOffsetIndex();
if (offset >= count) for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{
offset = 0;
ArrayOffset = 0;
}
for (int i = offset; i < offset + ArrayLimit && i < count; i++)
{ {
var entry = m_cachedEntries[i]; var entry = m_cachedEntries[i];

View File

@ -18,28 +18,17 @@ namespace Explorer
private ParameterInfo[] m_arguments; private ParameterInfo[] m_arguments;
private string[] m_argumentInput; private string[] m_argumentInput;
public bool HasParameters public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
{
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) public static bool CanEvaluate(MethodInfo mi)
{ {
// generic type args not supported yet // TODO generic args
if (mi.GetGenericArguments().Length > 0) if (mi.GetGenericArguments().Length > 0)
{ {
return false; return false;
} }
// only primitive and string args supported // primitive and string args supported
foreach (var param in mi.GetParameters()) foreach (var param in mi.GetParameters())
{ {
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string)) if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
@ -66,6 +55,83 @@ namespace Explorer
//base.UpdateValue(); //base.UpdateValue();
} }
private void Evaluate()
{
var mi = MemInfo as MethodInfo;
object ret = null;
if (!HasParameters)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
m_evaluated = true;
}
else
{
var parsedArgs = new List<object>();
for (int i = 0; i < m_arguments.Length; i++)
{
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType;
if (type == typeof(string))
{
parsedArgs.Add(input);
}
else
{
try
{
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
{
parsedArgs.Add(parsed);
}
else
{
// try add a null arg i guess
parsedArgs.Add(null);
}
}
catch
{
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
break;
}
}
}
try
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, parsedArgs.ToArray());
m_evaluated = true;
}
catch (Exception e)
{
MelonLogger.Log($"Exception evaluating: {e.GetType()}, {e.Message}");
}
}
if (ret != null)
{
m_cachedReturnValue = GetCacheObject(ret);
if (m_cachedReturnValue is IExpandHeight expander)
{
expander.WhiteSpace = 0f;
expander.ButtonWidthOffset += 70f;
}
m_cachedReturnValue.UpdateValue();
}
else
{
m_cachedReturnValue = null;
}
}
// ==== GUI DRAW ====
public override void DrawValue(Rect window, float width) public override void DrawValue(Rect window, float width)
{ {
GUILayout.BeginVertical(null); GUILayout.BeginVertical(null);
@ -123,17 +189,9 @@ namespace Explorer
if (m_evaluated) if (m_evaluated)
{ {
if (m_cachedReturnValue != null) if (m_cachedReturnValue != null)
{
try
{ {
m_cachedReturnValue.DrawValue(window, width); m_cachedReturnValue.DrawValue(window, width);
} }
catch (Exception e)
{
MelonLogger.Log("Exception drawing m_cachedReturnValue!");
MelonLogger.Log(e.ToString());
}
}
else else
{ {
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null); GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
@ -147,77 +205,5 @@ namespace Explorer
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
private void Evaluate()
{
var mi = MemInfo as MethodInfo;
object ret = null;
if (!HasParameters)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
m_evaluated = true;
}
else
{
var arguments = new List<object>();
for (int i = 0; i < m_arguments.Length; i++)
{
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType;
if (type == typeof(string))
{
arguments.Add(input);
}
else
{
try
{
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
{
arguments.Add(parsed);
}
else
{
throw new Exception();
}
}
catch
{
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
break;
}
}
}
if (arguments.Count == m_arguments.Length)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, arguments.ToArray());
m_evaluated = true;
}
else
{
MelonLogger.Log($"Did not invoke because {m_arguments.Length - arguments.Count} arguments could not be parsed!");
}
}
if (ret != null)
{
m_cachedReturnValue = GetCacheObject(ret);
if (m_cachedReturnValue is CacheList cacheList)
{
cacheList.WhiteSpace = 0f;
cacheList.ButtonWidthOffset += 70f;
}
m_cachedReturnValue.UpdateValue();
}
else
{
m_cachedReturnValue = null;
}
}
} }
} }

View File

@ -50,7 +50,7 @@ namespace Explorer
} }
GUI.skin.button.alignment = TextAnchor.MiddleLeft; GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width) })) if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width - 15) }))
{ {
WindowManager.InspectObject(Value, out bool _); WindowManager.InspectObject(Value, out bool _);
} }

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheColor : CacheObjectBase
{
private string r = "0";
private string g = "0";
private string b = "0";
private string a = "0";
public override void UpdateValue()
{
base.UpdateValue();
var color = (Color)Value;
r = color.r.ToString();
g = color.g.ToString();
b = color.b.ToString();
a = color.a.ToString();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Color</color>: {((Color)Value).ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
r = GUILayout.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
g = GUILayout.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
b = GUILayout.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
a = GUILayout.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(r, out float fR)
&& float.TryParse(g, out float fG)
&& float.TryParse(b, out float fB)
&& float.TryParse(a, out float fA))
{
Value = new Color(fR, fB, fG, fA);
SetValue();
}
}
}
}

View File

@ -51,7 +51,7 @@ namespace Explorer
} }
} }
GUILayout.Label(Value.ToString(), null);// + "<color=yellow><i> (" + ValueType + ")</i></color>", null); GUILayout.Label(Value.ToString() + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
} }
public void SetEnum(ref object value, int change) public void SetEnum(ref object value, int change)

View File

@ -8,90 +8,36 @@ namespace Explorer
{ {
public class CachePrimitive : CacheObjectBase public class CachePrimitive : CacheObjectBase
{ {
public enum PrimitiveTypes private bool m_isBool;
{ private bool m_isString;
Bool,
Double,
Float,
Int,
String,
Char
}
private string m_valueToString; private string m_valueToString;
public PrimitiveTypes PrimitiveType; public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
public MethodInfo ParseMethod
{
get
{
if (m_parseMethod == null)
{
m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) });
}
return m_parseMethod;
}
}
private MethodInfo m_parseMethod; private MethodInfo m_parseMethod;
public override void Init() public override void Init()
{ {
if (Value == null) if (ValueType == null)
{ {
// this must mean it is a string. No other primitive type should be nullable. ValueType = Value?.GetType();
PrimitiveType = PrimitiveTypes.String;
return;
}
m_valueToString = Value.ToString(); // has to be a string at this point
var type = Value.GetType(); if (ValueType == null)
if (type == typeof(bool))
{ {
PrimitiveType = PrimitiveTypes.Bool; ValueType = typeof(string);
}
else if (type == typeof(double))
{
PrimitiveType = PrimitiveTypes.Double;
}
else if (type == typeof(float))
{
PrimitiveType = PrimitiveTypes.Float;
}
else if (IsInteger(type))
{
PrimitiveType = PrimitiveTypes.Int;
}
else if (type == typeof(char))
{
PrimitiveType = PrimitiveTypes.Char;
}
else
{
PrimitiveType = PrimitiveTypes.String;
} }
} }
private static bool IsInteger(Type type) if (ValueType == typeof(string))
{ {
// For our purposes, all types of int can be treated the same, including IntPtr. m_isString = true;
return _integerTypes.Contains(type);
} }
else if (ValueType == typeof(bool))
private static readonly HashSet<Type> _integerTypes = new HashSet<Type>
{ {
typeof(int), m_isBool = true;
typeof(uint), }
typeof(short), }
typeof(ushort),
typeof(long),
typeof(ulong),
typeof(byte),
typeof(sbyte),
typeof(IntPtr)
};
public override void UpdateValue() public override void UpdateValue()
{ {
@ -102,11 +48,10 @@ namespace Explorer
public override void DrawValue(Rect window, float width) public override void DrawValue(Rect window, float width)
{ {
if (PrimitiveType == PrimitiveTypes.Bool) if (m_isBool)
{ {
var b = (bool)Value; var b = (bool)Value;
var color = $"<color={(b ? "lime>" : "red>")}"; var label = $"<color={(b ? "lime" : "red")}>{b}</color>";
var label = $"{color}{b}</color>";
if (CanWrite) if (CanWrite)
{ {
@ -123,7 +68,8 @@ namespace Explorer
} }
else else
{ {
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) }); // using ValueType.Name instead of ValueTypeName, because we only want the short name.
GUILayout.Label("<color=yellow><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
int dynSize = 25 + (m_valueToString.Length * 15); int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f; var maxwidth = window.width - 300f;
@ -150,7 +96,7 @@ namespace Explorer
} }
} }
public void SetValueFromInput(string value) public void SetValueFromInput(string valueString)
{ {
if (MemInfo == null) if (MemInfo == null)
{ {
@ -158,16 +104,15 @@ namespace Explorer
return; return;
} }
if (PrimitiveType == PrimitiveTypes.String) if (m_isString)
{ {
Value = value; Value = valueString;
} }
else else
{ {
try try
{ {
var val = ParseMethod.Invoke(null, new object[] { value }); Value = ParseMethod.Invoke(null, new object[] { valueString });
Value = val;
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheQuaternion : CacheObjectBase
{
private string x = "0";
private string y = "0";
private string z = "0";
public override void UpdateValue()
{
base.UpdateValue();
var euler = ((Quaternion)Value).eulerAngles;
x = euler.x.ToString();
y = euler.y.ToString();
z = euler.z.ToString();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(x, out float fX)
&& float.TryParse(y, out float fY)
&& float.TryParse(z, out float fZ))
{
Value = Quaternion.Euler(new Vector3(fX, fY, fZ));
SetValue();
}
}
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheRect : CacheObjectBase
{
private string x = "0";
private string y = "0";
private string w = "0";
private string h = "0";
public override void UpdateValue()
{
base.UpdateValue();
var rect = (Rect)Value;
x = rect.x.ToString();
y = rect.y.ToString();
w = rect.width.ToString();
h = rect.height.ToString();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
h = GUILayout.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(x, out float fX)
&& float.TryParse(y, out float fY)
&& float.TryParse(w, out float fW)
&& float.TryParse(h, out float fH))
{
Value = new Rect(fX, fY, fW, fH);
SetValue();
}
}
}
}

View File

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public class CacheVector : CacheObjectBase
{
public int VectorSize = 2;
private string x = "0";
private string y = "0";
private string z = "0";
private string w = "0";
private MethodInfo m_toStringMethod;
public override void Init()
{
if (Value is Vector2)
{
VectorSize = 2;
}
else if (Value is Vector3)
{
VectorSize = 3;
}
else
{
VectorSize = 4;
}
m_toStringMethod = Value.GetType().GetMethod("ToString", new Type[0]);
}
public override void UpdateValue()
{
base.UpdateValue();
if (Value is Vector2 vec2)
{
x = vec2.x.ToString();
y = vec2.y.ToString();
}
else if (Value is Vector3 vec3)
{
x = vec3.x.ToString();
y = vec3.y.ToString();
z = vec3.z.ToString();
}
else if (Value is Vector4 vec4)
{
x = vec4.x.ToString();
y = vec4.y.ToString();
z = vec4.z.ToString();
w = vec4.w.ToString();
}
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
// always draw x and y
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
if (VectorSize > 2)
{
// draw z
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
}
if (VectorSize > 3)
{
// draw w
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
}
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(x, out float fX)
&& float.TryParse(y, out float fY)
&& float.TryParse(z, out float fZ)
&& float.TryParse(w, out float fW))
{
object vector = null;
switch (VectorSize)
{
case 2: vector = new Vector2(fX, fY); break;
case 3: vector = new Vector3(fX, fY, fZ); break;
case 4: vector = new Vector4(fX, fY, fZ, fW); break;
}
if (vector != null)
{
Value = vector;
SetValue();
}
}
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -11,15 +12,10 @@ namespace Explorer
{ {
public class CppExplorer : MelonMod public class CppExplorer : MelonMod
{ {
public const string GUID = "com.sinai.cppexplorer"; public const string NAME = "CppExplorer";
public const string VERSION = "1.5.1"; public const string VERSION = "1.6.4";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.cppexplorer";
public const string NAME = "CppExplorer"
#if Release_Unity2018
+ " (Unity 2018)"
#endif
;
public static CppExplorer Instance { get; private set; } public static CppExplorer Instance { get; private set; }
@ -54,6 +50,8 @@ namespace Explorer
{ {
Instance = this; Instance = this;
InputHelper.CheckInput();
new MainMenu(); new MainMenu();
new WindowManager(); new WindowManager();
@ -78,7 +76,7 @@ namespace Explorer
public override void OnUpdate() public override void OnUpdate()
{ {
// Check main toggle key input // Check main toggle key input
if (Input.GetKeyDown(KeyCode.F7)) if (InputHelper.GetKeyDown(KeyCode.F7))
{ {
ShowMenu = !ShowMenu; ShowMenu = !ShowMenu;
} }
@ -86,7 +84,7 @@ namespace Explorer
if (ShowMenu) if (ShowMenu)
{ {
// Check Force-Unlock input // Check Force-Unlock input
if (Input.GetKeyDown(KeyCode.LeftAlt)) if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
{ {
ForceUnlockMouse = !ForceUnlockMouse; ForceUnlockMouse = !ForceUnlockMouse;
} }
@ -99,6 +97,8 @@ namespace Explorer
public override void OnGUI() public override void OnGUI()
{ {
if (!ShowMenu) return;
MainMenu.Instance.OnGUI(); MainMenu.Instance.OnGUI();
WindowManager.Instance.OnGUI(); WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI(); InspectUnderMouse.OnGUI();

View File

@ -2,7 +2,7 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid> <ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
@ -12,26 +12,12 @@
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<AssemblyName>CppExplorer</AssemblyName> <AssemblyName>CppExplorer</AssemblyName>
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>..\Release\</OutputPath> <OutputPath>..\Release\</OutputPath>
<DefineConstants>DEBUG</DefineConstants> <DefineConstants />
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_Unity2018|AnyCPU' ">
<AssemblyName>CppExplorer_Unity2018</AssemblyName>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Release\</OutputPath>
<DefineConstants>Release_Unity2018</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@ -44,7 +30,7 @@
</Reference> </Reference>
<Reference Include="mcs"> <Reference Include="mcs">
<HintPath>..\lib\mcs.dll</HintPath> <HintPath>..\lib\mcs.dll</HintPath>
<Private>False</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="MelonLoader.ModHandler"> <Reference Include="MelonLoader.ModHandler">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath> <HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
@ -60,86 +46,59 @@
<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>
<!-- Replace these references with ones from your game (..\MelonLoader\ folder) -->
<!-- Unity 2019 build (InputLegacyModule.dll) --> <Reference Include="UnityEngine">
<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>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Debug'"> <Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath> <HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Debug'"> <Reference Include="UnityEngine.IMGUIModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath> <HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Debug'"> <Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath> <HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Debug'"> <Reference Include="UnityEngine.TextRenderingModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath> <HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Debug'"> <Reference Include="UnityEngine.UI">
<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>
<!-- Unity 2018 build (InputModule.dll) -->
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CachedObjects\CacheDictionary.cs" /> <Compile Include="Helpers\IExpandHeight.cs" />
<Compile Include="CachedObjects\CacheEnum.cs" /> <Compile Include="CachedObjects\Struct\CacheColor.cs" />
<Compile Include="CachedObjects\CacheGameObject.cs" /> <Compile Include="CachedObjects\Object\CacheDictionary.cs" />
<Compile Include="CachedObjects\CacheList.cs" /> <Compile Include="CachedObjects\Struct\CacheEnum.cs" />
<Compile Include="CachedObjects\CachePrimitive.cs" /> <Compile Include="CachedObjects\Object\CacheGameObject.cs" />
<Compile Include="CachedObjects\CacheOther.cs" /> <Compile Include="CachedObjects\Object\CacheList.cs" />
<Compile Include="CachedObjects\CacheMethod.cs" /> <Compile Include="CachedObjects\Struct\CachePrimitive.cs" />
<Compile Include="CachedObjects\Other\CacheOther.cs" />
<Compile Include="CachedObjects\Other\CacheMethod.cs" />
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
<Compile Include="CppExplorer.cs" /> <Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" /> <Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="Helpers\InputHelper.cs" />
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
<Compile Include="UnstripFixes\ScrollViewStateUnstrip.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\CacheObjectBase.cs" /> <Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="UnstripFixes\SliderHandlerUnstrip.cs" />
<Compile Include="UnstripFixes\UnstripExtensions.cs" />
<Compile Include="Windows\ResizeDrag.cs" /> <Compile Include="Windows\ResizeDrag.cs" />
<Compile Include="Windows\TabViewWindow.cs" /> <Compile Include="Windows\TabViewWindow.cs" />
<Compile Include="Windows\UIWindow.cs" /> <Compile Include="Windows\UIWindow.cs" />

View File

@ -7,14 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppExplorer", "CppExplorer.
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU
Release_Unity2018|Any CPU = Release_Unity2018|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.ActiveCfg = Release_Unity2018|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.Build.0 = Release_Unity2018|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -3,14 +3,38 @@ 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;
namespace Explorer namespace Explorer
{ {
public static class ReflectionExtensions public static class ReflectionExtensions
{ {
/// <summary>
/// Extension to allow for easy, non-generic Il2Cpp casting.
/// The extension is on System.Object, but only Il2Cpp objects would be a valid target.
/// </summary>
public static object Il2CppCast(this object obj, Type castTo) public static object Il2CppCast(this object obj, Type castTo)
{ {
return ReflectionHelpers.Il2CppCast(obj, castTo); return ReflectionHelpers.Il2CppCast(obj, castTo);
} }
/// <summary>
/// Extension to safely try to get all Types from an Assembly, with a fallback for ReflectionTypeLoadException.
/// </summary>
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
{
try
{
return asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null);
}
catch
{
return Enumerable.Empty<Type>();
}
}
} }
} }

View File

@ -3,6 +3,7 @@ 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 UnhollowerBaseLib;
using UnityEngine; using UnityEngine;
namespace Explorer namespace Explorer

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Explorer
{
interface IExpandHeight
{
bool IsExpanded { get; set; }
float WhiteSpace { get; set; }
float ButtonWidthOffset { get; set; }
}
}

107
src/Helpers/InputHelper.cs Normal file
View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
using MelonLoader;
namespace Explorer
{
/// <summary>
/// Version-agnostic UnityEngine Input module using Reflection.
/// </summary>
public static class InputHelper
{
public static void CheckInput()
{
if (Input == null)
{
MelonLogger.Log("UnityEngine.Input is null, trying to load manually....");
if ((TryLoad("UnityEngine.InputLegacyModule.dll") || TryLoad("UnityEngine.CoreModule.dll")) && Input != null)
{
MelonLogger.Log("Ok!");
}
else
{
MelonLogger.Log("Could not load Input module!");
}
bool TryLoad(string module)
{
var path = $@"MelonLoader\Managed\{module}";
if (!File.Exists(path)) return false;
try
{
Assembly.Load(File.ReadAllBytes(path));
return true;
}
catch (Exception e)
{
MelonLogger.Log(e.GetType() + ", " + e.Message);
return false;
}
}
}
}
public static Type Input => _input ?? (_input = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
private static Type _input;
private static PropertyInfo MousePosInfo => _mousePosition ?? (_mousePosition = Input?.GetProperty("mousePosition"));
private static PropertyInfo _mousePosition;
private static MethodInfo GetKeyInfo => _getKey ?? (_getKey = Input?.GetMethod("GetKey", new Type[] { typeof(KeyCode) }));
private static MethodInfo _getKey;
private static MethodInfo GetKeyDownInfo => _getKeyDown ?? (_getKeyDown = Input?.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) }));
private static MethodInfo _getKeyDown;
private static MethodInfo GetMouseButtonInfo => _getMouseButton ?? (_getMouseButton = Input?.GetMethod("GetMouseButton", new Type[] { typeof(int) }));
private static MethodInfo _getMouseButton;
private static MethodInfo GetMouseButtonDownInfo => _getMouseButtonDown ?? (_getMouseButtonDown = Input?.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) }));
private static MethodInfo _getMouseButtonDown;
#pragma warning disable IDE1006 // Camel-case property (Unity style)
public static Vector3 mousePosition
{
get
{
if (Input == null) return Vector3.zero;
return (Vector3)MousePosInfo.GetValue(null);
}
}
#pragma warning restore IDE1006
public static bool GetKeyDown(KeyCode key)
{
if (Input == null) return false;
return (bool)GetKeyDownInfo.Invoke(null, new object[] { key });
}
public static bool GetKey(KeyCode key)
{
if (Input == null) return false;
return (bool)GetKeyInfo.Invoke(null, new object[] { key });
}
/// <param name="btn">1 = left, 2 = middle, 3 = right, etc</param>
public static bool GetMouseButtonDown(int btn)
{
if (Input == null) return false;
return (bool)GetMouseButtonDownInfo.Invoke(null, new object[] { btn });
}
/// <param name="btn">1 = left, 2 = middle, 3 = right, etc</param>
public static bool GetMouseButton(int btn)
{
if (Input == null) return false;
return (bool)GetMouseButtonInfo.Invoke(null, new object[] { btn });
}
}
}

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

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

View File

@ -1,14 +1,13 @@
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.Threading.Tasks;
using System.Reflection; using System.Reflection;
using UnhollowerBaseLib; using UnhollowerBaseLib;
using UnhollowerRuntimeLib; using UnhollowerRuntimeLib;
using UnityEngine; using UnityEngine;
using BF = System.Reflection.BindingFlags; using BF = System.Reflection.BindingFlags;
using MelonLoader; using ILType = Il2CppSystem.Type;
namespace Explorer namespace Explorer
{ {
@ -16,11 +15,11 @@ namespace Explorer
{ {
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 Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>(); public static ILType GameObjectType => Il2CppType.Of<GameObject>();
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>(); public static ILType TransformType => Il2CppType.Of<Transform>();
public static Il2CppSystem.Type ObjectType => Il2CppType.Of<UnityEngine.Object>(); public static ILType ObjectType => Il2CppType.Of<UnityEngine.Object>();
public static Il2CppSystem.Type ComponentType => Il2CppType.Of<Component>(); public static ILType ComponentType => Il2CppType.Of<Component>();
public static Il2CppSystem.Type BehaviourType => Il2CppType.Of<Behaviour>(); public static ILType BehaviourType => Il2CppType.Of<Behaviour>();
private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast"); private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
@ -33,6 +32,94 @@ namespace Explorer
.Invoke(obj, null); .Invoke(obj, null);
} }
public static bool IsEnumerable(Type t)
{
return typeof(IEnumerable).IsAssignableFrom(t);
}
// Only Il2Cpp List needs this check. C# List is IEnumerable.
public static bool IsCppList(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
}
}
public static bool IsDictionary(Type t)
{
if (typeof(IDictionary).IsAssignableFrom(t))
{
return true;
}
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t);
}
}
public static Type GetTypeByName(string fullName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
{
if (type.FullName == fullName)
{
return type;
}
}
}
return null;
}
public static Type GetActualType(object obj)
{
if (obj == null) return null;
if (obj is Il2CppSystem.Object ilObject)
{
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
{
return t;
}
return ilObject.GetType();
}
return obj.GetType();
}
public static Type[] GetAllBaseTypes(object obj)
{
var list = new List<Type>();
var type = GetActualType(obj);
list.Add(type);
while (type.BaseType != null)
{
type = type.BaseType;
list.Add(type);
}
return list.ToArray();
}
public static string ExceptionToString(Exception e) public static string ExceptionToString(Exception e)
{ {
if (IsFailedGeneric(e)) if (IsFailedGeneric(e))
@ -73,84 +160,5 @@ namespace Explorer
else else
return false; return false;
} }
public static bool IsArray(Type t)
{
return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
}
public static bool IsList(Type t)
{
if (t.IsGenericType)
{
var generic = t.GetGenericTypeDefinition();
return generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))
|| generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.IList<>));
}
else
{
return t.IsAssignableFrom(typeof(Il2CppSystem.Collections.IList));
}
}
public static bool IsDictionary(Type t)
{
return t.IsGenericType
&& t.GetGenericTypeDefinition() is Type typeDef
&& typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>));
}
public static Type GetTypeByName(string typeName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
if (asm.GetType(typeName) is Type type)
{
return type;
}
}
catch { }
}
return null;
}
public static Type GetActualType(object obj)
{
if (obj == null) return null;
if (obj is Il2CppSystem.Object ilObject)
{
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
{
return t;
}
return ilObject.GetType();
}
return obj.GetType();
}
public static Type[] GetAllBaseTypes(object obj)
{
var list = new List<Type>();
var type = GetActualType(obj);
list.Add(type);
while (type.BaseType != null)
{
type = type.BaseType;
list.Add(type);
}
return list.ToArray();
}
} }
} }

View File

@ -3,6 +3,8 @@ 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 UnhollowerRuntimeLib;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@ -22,11 +24,13 @@ 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<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380) public static void GOButton(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{ {
bool children = obj.transform.childCount > 0; var obj = (_obj as GameObject) ?? (_obj as Transform).gameObject;
string label = children ? "[" + obj.transform.childCount + " children] " : ""; bool hasChild = obj.transform.childCount > 0;
string label = hasChild ? $"[{obj.transform.childCount} children] " : "";
label += obj.name; label += obj.name;
bool enabled = obj.activeSelf; bool enabled = obj.activeSelf;
@ -49,11 +53,13 @@ namespace Explorer
color = Color.red; color = Color.red;
} }
FastGameobjButton(obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width); GOButton_Impl(_obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
} }
public static void FastGameobjButton(GameObject obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380) public static void GOButton_Impl(object _obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{ {
var obj = _obj as GameObject ?? (_obj as Transform).gameObject;
if (!obj) if (!obj)
{ {
GUILayout.Label("<i><color=red>null</color></i>", null); GUILayout.Label("<i><color=red>null</color></i>", null);
@ -83,7 +89,7 @@ namespace Explorer
} }
else else
{ {
WindowManager.InspectObject(obj, out bool _); WindowManager.InspectObject(_obj, out bool _);
} }
} }
@ -94,7 +100,7 @@ namespace Explorer
if (showSmallInspectBtn) if (showSmallInspectBtn)
{ {
SmallInspectButton(obj); SmallInspectButton(_obj);
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();

View File

@ -56,7 +56,10 @@ namespace Explorer
{ {
_horizBarStyle = new GUIStyle(); _horizBarStyle = new GUIStyle();
_horizBarStyle.normal.background = Texture2D.whiteTexture; _horizBarStyle.normal.background = Texture2D.whiteTexture;
_horizBarStyle.margin = new RectOffset(0, 0, 4, 4); var rectOffset = new RectOffset();
rectOffset.top = 4;
rectOffset.bottom = 4;
_horizBarStyle.margin = rectOffset;
_horizBarStyle.fixedHeight = 2; _horizBarStyle.fixedHeight = 2;
} }
@ -73,7 +76,10 @@ namespace Explorer
{ {
_horizBarSmallStyle = new GUIStyle(); _horizBarSmallStyle = new GUIStyle();
_horizBarSmallStyle.normal.background = Texture2D.whiteTexture; _horizBarSmallStyle.normal.background = Texture2D.whiteTexture;
_horizBarSmallStyle.margin = new RectOffset(0, 0, 2, 2); var rectOffset = new RectOffset();
rectOffset.top = 2;
rectOffset.bottom = 2;
_horizBarSmallStyle.margin = rectOffset;
_horizBarSmallStyle.fixedHeight = 1; _horizBarSmallStyle.fixedHeight = 1;
} }

View File

@ -15,7 +15,7 @@ namespace Explorer
{ {
get get
{ {
if (m_mainCamera == null) if (!m_mainCamera)
{ {
m_mainCamera = Camera.main; m_mainCamera = Camera.main;
} }

View File

@ -17,7 +17,7 @@ namespace Explorer
{ {
if (CppExplorer.ShowMenu) if (CppExplorer.ShowMenu)
{ {
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1)) if (InputHelper.GetKey(KeyCode.LeftShift) && InputHelper.GetMouseButtonDown(1))
{ {
EnableInspect = !EnableInspect; EnableInspect = !EnableInspect;
} }
@ -35,7 +35,10 @@ namespace Explorer
public static void InspectRaycast() public static void InspectRaycast()
{ {
Ray ray = UnityHelpers.MainCamera.ScreenPointToRay(Input.mousePosition); if (!UnityHelpers.MainCamera)
return;
var ray = UnityHelpers.MainCamera.ScreenPointToRay(InputHelper.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 1000f)) if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
{ {
@ -43,7 +46,7 @@ namespace Explorer
m_objUnderMouseName = obj.transform.GetGameObjectPath(); m_objUnderMouseName = obj.transform.GetGameObjectPath();
if (Input.GetMouseButtonDown(0)) if (InputHelper.GetMouseButtonDown(0))
{ {
EnableInspect = false; EnableInspect = false;
m_objUnderMouseName = ""; m_objUnderMouseName = "";
@ -63,7 +66,7 @@ namespace Explorer
{ {
if (m_objUnderMouseName != "") if (m_objUnderMouseName != "")
{ {
var pos = Input.mousePosition; var pos = InputHelper.mousePosition;
var rect = new Rect( var rect = new Rect(
pos.x - (Screen.width / 2), // x pos.x - (Screen.width / 2), // x
Screen.height - pos.y - 50, // y Screen.height - pos.y - 50, // y

View File

@ -50,8 +50,6 @@ namespace Explorer
} }
public void OnGUI() public void OnGUI()
{
if (CppExplorer.ShowMenu)
{ {
var origSkin = GUI.skin; var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin; GUI.skin = UIStyles.WindowSkin;
@ -60,7 +58,6 @@ namespace Explorer
GUI.skin = origSkin; GUI.skin = origSkin;
} }
}
private void MainWindow(int id) private void MainWindow(int id)
{ {
@ -77,9 +74,12 @@ namespace Explorer
MainHeader(); MainHeader();
var page = Pages[m_currentPage]; var page = Pages[m_currentPage];
page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView);
page.scroll = GUIUnstrip.BeginScrollView(page.scroll);
page.DrawWindow(); page.DrawWindow();
GUILayout.EndScrollView();
GUIUnstrip.EndScrollView();
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID); MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);

View File

@ -14,11 +14,13 @@ namespace Explorer
{ {
public class ConsolePage : WindowPage public class ConsolePage : WindowPage
{ {
public override string Name { get => "C# Console"; set => base.Name = value; } public override string Name { get => "C# Console"; }
private ScriptEvaluator _evaluator; private ScriptEvaluator _evaluator;
private readonly StringBuilder _sb = new StringBuilder(); private readonly StringBuilder _sb = new StringBuilder();
private Vector2 inputAreaScroll;
private string MethodInput = ""; private string MethodInput = "";
private string UsingInput = ""; private string UsingInput = "";
@ -124,7 +126,12 @@ MelonLogger.Log(""hello world"");";
GUI.skin.label.alignment = TextAnchor.UpperLeft; GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.Label("Enter code here as though it is a method body:", null); GUILayout.Label("Enter code here as though it is a method body:", null);
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
inputAreaScroll = GUIUnstrip.BeginScrollView(inputAreaScroll, new GUILayoutOption[] { GUILayout.Height(250) });
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.ExpandHeight(true) });
GUIUnstrip.EndScrollView();
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null)) if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
{ {

View File

@ -12,29 +12,26 @@ namespace Explorer
{ {
public static ScenePage Instance; public static ScenePage Instance;
public override string Name { get => "Scene Explorer"; set => base.Name = value; } public override string Name { get => "Scene Explorer"; }
private int m_pageOffset = 0; public PageHelper Pages = new PageHelper();
private int m_limit = 20;
private int m_currentTotalCount = 0;
private float m_timeOfLastUpdate = -1f; private float m_timeOfLastUpdate = -1f;
private const int PASSIVE_UPDATE_INTERVAL = 1;
// ----- Holders for GUI elements ----- // private static bool m_getRootObjectsFailed;
private string m_currentScene = ""; private static string m_currentScene = "";
// gameobject list // gameobject list
private Transform m_currentTransform; private Transform m_currentTransform;
private List<GameObjectCache> m_objectList = new List<GameObjectCache>(); private readonly List<GameObjectCache> m_objectList = new List<GameObjectCache>();
// search bar // search bar
private bool m_searching = false; private bool m_searching = false;
private string m_searchInput = ""; private string m_searchInput = "";
private List<GameObjectCache> m_searchResults = new List<GameObjectCache>(); private List<GameObjectCache> m_searchResults = new List<GameObjectCache>();
// ------------ Init and Update ------------ //
public override void Init() public override void Init()
{ {
Instance = this; Instance = this;
@ -46,61 +43,6 @@ namespace Explorer
SetTransformTarget(null); SetTransformTarget(null);
} }
public void CheckOffset(ref int offset, int childCount)
{
if (offset >= childCount)
{
offset = 0;
m_pageOffset = 0;
}
}
public override void Update()
{
if (m_searching) return;
if (Time.time - m_timeOfLastUpdate < 1f) return;
m_timeOfLastUpdate = Time.time;
m_objectList = new List<GameObjectCache>();
int offset = m_pageOffset * m_limit;
var allTransforms = new List<Transform>();
// get current list of all transforms (either scene root or our current transform children)
if (m_currentTransform)
{
for (int i = 0; i < m_currentTransform.childCount; i++)
{
allTransforms.Add(m_currentTransform.GetChild(i));
}
}
else
{
var scene = SceneManager.GetSceneByName(m_currentScene);
var rootObjects = scene.GetRootGameObjects();
foreach (var obj in rootObjects)
{
allTransforms.Add(obj.transform);
}
}
m_currentTotalCount = allTransforms.Count;
// make sure offset doesn't exceed count
CheckOffset(ref offset, m_currentTotalCount);
// sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
for (int i = offset; i < offset + m_limit && i < m_currentTotalCount; i++)
{
var child = allTransforms[i];
m_objectList.Add(new GameObjectCache(child.gameObject));
}
}
public void SetTransformTarget(Transform t) public void SetTransformTarget(Transform t)
{ {
m_currentTransform = t; m_currentTransform = t;
@ -108,8 +50,7 @@ namespace Explorer
if (m_searching) if (m_searching)
CancelSearch(); CancelSearch();
m_timeOfLastUpdate = -1f; Update_Impl(true);
Update();
} }
public void TraverseUp() public void TraverseUp()
@ -128,29 +69,129 @@ namespace Explorer
{ {
m_searchResults = SearchSceneObjects(m_searchInput); m_searchResults = SearchSceneObjects(m_searchInput);
m_searching = true; m_searching = true;
m_currentTotalCount = m_searchResults.Count; Pages.ItemCount = m_searchResults.Count;
} }
public void CancelSearch() public void CancelSearch()
{ {
m_searching = false; m_searching = false;
if (m_getRootObjectsFailed && !m_currentTransform)
{
GetRootObjectsManual_Impl();
}
} }
public List<GameObjectCache> SearchSceneObjects(string _search) public List<GameObjectCache> SearchSceneObjects(string _search)
{ {
var matches = new List<GameObjectCache>(); var matches = new List<GameObjectCache>();
foreach (var obj in Resources.FindObjectsOfTypeAll<GameObject>()) foreach (var obj in Resources.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
{ {
if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == m_currentScene) var go = obj.TryCast<GameObject>();
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
{ {
matches.Add(new GameObjectCache(obj)); matches.Add(new GameObjectCache(go));
} }
} }
return matches; return matches;
} }
public override void Update()
{
if (m_searching) return;
if (Time.time - m_timeOfLastUpdate < PASSIVE_UPDATE_INTERVAL) return;
m_timeOfLastUpdate = Time.time;
Update_Impl();
}
private void Update_Impl(bool manual = false)
{
List<Transform> allTransforms = new List<Transform>();
// get current list of all transforms (either scene root or our current transform children)
if (m_currentTransform)
{
for (int i = 0; i < m_currentTransform.childCount; i++)
{
allTransforms.Add(m_currentTransform.GetChild(i));
}
}
else
{
if (!m_getRootObjectsFailed)
{
try
{
var scene = SceneManager.GetSceneByName(m_currentScene);
allTransforms.AddRange(scene.GetRootGameObjects()
.Select(it => it.transform));
}
catch
{
MelonLogger.Log("Exception getting root scene objects, falling back to backup method...");
m_getRootObjectsFailed = true;
allTransforms.AddRange(GetRootObjectsManual_Impl());
}
}
else
{
if (!manual)
{
return;
}
allTransforms.AddRange(GetRootObjectsManual_Impl());
}
}
Pages.ItemCount = allTransforms.Count;
int offset = Pages.CalculateOffsetIndex();
// sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_objectList.Clear();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
{
var child = allTransforms[i];
m_objectList.Add(new GameObjectCache(child.gameObject));
}
}
private IEnumerable<Transform> GetRootObjectsManual_Impl()
{
try
{
var array = Resources.FindObjectsOfTypeAll(ReflectionHelpers.TransformType);
var list = new List<Transform>();
foreach (var obj in array)
{
var transform = obj.TryCast<Transform>();
if (transform.parent == null && transform.gameObject.scene.name == m_currentScene)
{
list.Add(transform);
}
}
return list;
}
catch (Exception e)
{
MelonLogger.Log("Exception getting root scene objects (manual): "
+ e.GetType() + ", " + e.Message + "\r\n"
+ e.StackTrace);
return new Transform[0];
}
}
// --------- GUI Draw Function --------- // // --------- GUI Draw Function --------- //
public override void DrawWindow() public override void DrawWindow()
@ -174,11 +215,9 @@ namespace Explorer
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
catch (Exception e) catch
{ {
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message); // supress
MelonLogger.Log(e.StackTrace);
m_currentTransform = null;
} }
} }
@ -188,7 +227,27 @@ namespace Explorer
// Current Scene label // Current Scene label
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) }); GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
try SceneChangeButtons();
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 SceneChangeButtons()
{ {
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC. // Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
var scenes = SceneManager.GetAllScenes().ToList(); var scenes = SceneManager.GetAllScenes().ToList();
@ -220,56 +279,29 @@ namespace Explorer
} }
} }
} }
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() private void DrawPageButtons()
{ {
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("Limit per page: ", new GUILayoutOption[] { GUILayout.Width(100) }); Pages.DrawLimitInputArea();
var limit = m_limit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(30) }); if (Pages.ItemCount > Pages.ItemsPerPage)
if (int.TryParse(limit, out int lim))
{ {
m_limit = lim; if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.scroll);
Update_Impl(true);
} }
// prev/next page buttons Pages.CurrentPageLabel();
if (m_currentTotalCount > m_limit)
{
int count = m_currentTotalCount;
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1;
if (GUILayout.Button("< Prev", null))
{
if (m_pageOffset > 0) m_pageOffset--;
m_timeOfLastUpdate = -1f;
Update();
}
GUI.skin.label.alignment = TextAnchor.MiddleCenter; if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
if (GUILayout.Button("Next >", null))
{ {
if (m_pageOffset < maxOffset) m_pageOffset++; Pages.TurnPage(Turn.Right, ref this.scroll);
m_timeOfLastUpdate = -1f;
Update(); Update_Impl(true);
} }
} }
@ -299,12 +331,24 @@ namespace Explorer
else else
{ {
GUILayout.Label("Scene Root GameObjects:", null); GUILayout.Label("Scene Root GameObjects:", null);
if (m_getRootObjectsFailed)
{
if (GUILayout.Button("Update Root Object List (auto-update failed!)", null))
{
Update_Impl(true);
}
}
} }
if (m_objectList.Count > 0) if (m_objectList.Count > 0)
{ {
foreach (var obj in m_objectList) for (int i = 0; i < m_objectList.Count; i++)
{ {
var obj = m_objectList[i];
if (obj == null) continue;
if (!obj.RefGameObject) if (!obj.RefGameObject)
{ {
string label = "<color=red><i>null"; string label = "<color=red><i>null";
@ -319,7 +363,7 @@ namespace Explorer
} }
else else
{ {
UIHelpers.FastGameobjButton(obj.RefGameObject, UIHelpers.GOButton_Impl(obj.RefGameObject,
obj.EnabledColor, obj.EnabledColor,
obj.Label, obj.Label,
obj.RefGameObject.activeSelf, obj.RefGameObject.activeSelf,
@ -342,21 +386,15 @@ namespace Explorer
if (m_searchResults.Count > 0) if (m_searchResults.Count > 0)
{ {
int offset = m_pageOffset * m_limit; int offset = Pages.CalculateOffsetIndex();
if (offset >= m_searchResults.Count) for (int i = offset; i < offset + Pages.ItemsPerPage && i < m_searchResults.Count; i++)
{
offset = 0;
m_pageOffset = 0;
}
for (int i = offset; i < offset + m_limit && i < m_searchResults.Count; i++)
{ {
var obj = m_searchResults[i]; var obj = m_searchResults[i];
if (obj.RefGameObject) if (obj.RefGameObject)
{ {
UIHelpers.FastGameobjButton(obj.RefGameObject, UIHelpers.GOButton_Impl(obj.RefGameObject,
obj.EnabledColor, obj.EnabledColor,
obj.Label, obj.Label,
obj.RefGameObject.activeSelf, obj.RefGameObject.activeSelf,

View File

@ -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
{ {
@ -17,15 +13,15 @@ namespace Explorer
{ {
public static SearchPage Instance; public static SearchPage Instance;
public override string Name { get => "Object Search"; set => base.Name = value; } public override string Name { get => "Object Search"; }
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;
public PageHelper Pages = new PageHelper();
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>(); private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
public SceneFilter SceneMode = SceneFilter.Any; public SceneFilter SceneMode = SceneFilter.Any;
@ -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()
@ -78,6 +74,9 @@ namespace Explorer
var cache = CacheObjectBase.GetCacheObject(toCache); var cache = CacheObjectBase.GetCacheObject(toCache);
m_searchResults.Add(cache); m_searchResults.Add(cache);
} }
Pages.ItemCount = 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();
@ -107,38 +105,43 @@ namespace Explorer
int count = m_searchResults.Count; int count = m_searchResults.Count;
if (count > this.m_limit) GUILayout.BeginHorizontal(null);
Pages.DrawLimitInputArea();
if (count > Pages.ItemsPerPage)
{ {
// prev/next page buttons // prev/next page buttons
GUILayout.BeginHorizontal(null);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1; if (Pages.ItemCount > Pages.ItemsPerPage)
if (GUILayout.Button("< Prev", null))
{ {
if (m_pageOffset > 0) m_pageOffset--; if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.resultsScroll);
} }
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.resultsScroll);
} }
}
}
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
}
resultsScroll = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView); resultsScroll = GUIUnstrip.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 = Pages.CalculateOffsetIndex();
if (offset >= count) m_pageOffset = 0;
for (int i = offset; i < offset + m_limit && i < count; i++) for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{ {
m_searchResults[i].Draw(MainMenu.MainRect, 0f); m_searchResults[i].Draw(MainMenu.MainRect, 0f);
//m_searchResults[i].DrawValue(MainMenu.MainRect);
} }
} }
else else
@ -146,7 +149,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(); GUIUnstrip.EndScrollView();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
catch catch
@ -169,16 +172,6 @@ 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;
GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
var resultinput = m_limit.ToString();
resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
if (int.TryParse(resultinput, out int _i) && _i > 0)
{
m_limit = _i;
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -256,11 +249,11 @@ 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));
} }
private List<object> FindAllObjectsOfType(string _search, string _type) private List<object> FindAllObjectsOfType(string searchQuery, string typeName)
{ {
Il2CppSystem.Type searchType = null; Il2CppSystem.Type searchType = null;
@ -268,13 +261,18 @@ namespace Explorer
{ {
try try
{ {
var findType = ReflectionHelpers.GetTypeByName(_type); if (ReflectionHelpers.GetTypeByName(typeName) is Type t)
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName); {
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName); searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
}
else
{
throw new Exception($"Could not find a Type by the name of '{typeName}'!");
}
} }
catch (Exception e) catch (Exception e)
{ {
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace); MelonLogger.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
} }
} }
else if (TypeMode == TypeFilter.Object) else if (TypeMode == TypeFilter.Object)
@ -292,7 +290,10 @@ namespace Explorer
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType)) if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
{ {
MelonLogger.LogError("Your Custom Class Type must inherit from UnityEngine.Object!"); if (searchType != null)
{
MelonLogger.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
}
return new List<object>(); return new List<object>();
} }
@ -307,12 +308,13 @@ namespace Explorer
{ {
if (i >= 2000) break; if (i >= 2000) break;
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower())) if (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower()))
{ {
continue; continue;
} }
if (searchType == ReflectionHelpers.ComponentType && ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType())) if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
{ {
// Transforms shouldn't really be counted as Components, skip them. // Transforms shouldn't really be counted as Components, skip them.
// They're more akin to GameObjects. // They're more akin to GameObjects.
@ -370,43 +372,70 @@ namespace Explorer
// ====== other ======== // ====== other ========
private static bool FilterName(string name)
{
// Don't really want these instances.
return !name.StartsWith("Mono")
&& !name.StartsWith("System")
&& !name.StartsWith("Il2CppSystem")
&& !name.StartsWith("Iced");
}
// credit: ManlyMarco (RuntimeUnityEditor) // credit: ManlyMarco (RuntimeUnityEditor)
public static IEnumerable<object> GetInstanceClassScanner() public static IEnumerable<object> GetInstanceClassScanner()
{ {
var query = AppDomain.CurrentDomain.GetAssemblies() var query = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => !x.FullName.StartsWith("Mono")) .SelectMany(t => t.TryGetTypes())
.SelectMany(GetTypesSafe)
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters); .Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
var flags = BindingFlags.Public | BindingFlags.Static;
var flatFlags = flags | BindingFlags.FlattenHierarchy;
foreach (var type in query) foreach (var type in query)
{ {
object obj = null; object obj = null;
try try
{ {
obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null); var pi = type.GetProperty("Instance", flags);
if (pi == null)
{
pi = type.GetProperty("Instance", flatFlags);
} }
catch
if (pi != null)
{ {
try obj = pi.GetValue(null);
{
obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null);
} }
catch else
{ {
var fi = type.GetField("Instance", flags);
if (fi == null)
{
fi = type.GetField("Instance", flatFlags);
}
if (fi != null)
{
obj = fi.GetValue(null);
} }
} }
if (obj != null && !obj.ToString().StartsWith("Mono")) }
catch { }
if (obj != null)
{ {
var t = ReflectionHelpers.GetActualType(obj);
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t))
{
continue;
}
yield return obj; yield return obj;
} }
} }
} }
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
{
try { return asm.GetTypes(); }
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
catch { return Enumerable.Empty<Type>(); }
}
} }
} }

View File

@ -9,7 +9,7 @@ namespace Explorer
{ {
public abstract class WindowPage public abstract class WindowPage
{ {
public virtual string Name { get; set; } public virtual string Name { get; }
public Vector2 scroll = Vector2.zero; public Vector2 scroll = Vector2.zero;

View File

@ -0,0 +1,406 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Reflection;
using UnityEngineInternal;
using Harmony;
namespace Explorer
{
public class GUIUnstrip
{
public static int s_ScrollControlId;
public static bool ScrollFailed = false;
public static bool ManualUnstripFailed = false;
private static GenericStack ScrollStack
{
get
{
if (m_scrollViewStatesInfo == null)
{
try
{
m_scrollViewStatesInfo = typeof(GUI).GetProperty("scrollViewStates");
if (m_scrollViewStatesInfo == null) throw new Exception();
}
catch
{
m_scrollViewStatesInfo = typeof(GUI).GetProperty("s_scrollViewStates");
}
}
return (GenericStack)m_scrollViewStatesInfo?.GetValue(null, null);
}
}
private static PropertyInfo m_scrollViewStatesInfo;
public static Rect GetLastRect()
{
EventType type = Event.current.type;
Rect last;
if (type != EventType.Layout && type != EventType.Used)
{
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
}
else
{
last = GUILayoutUtility.kDummyRect;
}
return last;
}
// Fix for BeginScrollView.
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
{
// First, just try normal way, may not have been stripped or was unstripped successfully.
if (!ScrollFailed)
{
try
{
return GUILayout.BeginScrollView(scroll, options);
}
catch
{
ScrollFailed = true;
}
}
// Try manual implementation.
if (!ManualUnstripFailed)
{
try
{
return BeginScrollView_ImplLayout(scroll, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options);
}
catch (Exception e)
{
MelonLogger.Log("Exception on manual BeginScrollView: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
ManualUnstripFailed = true;
}
}
// Sorry! No scrolling for you.
return scroll;
}
public static void EndScrollView(bool handleScrollWheel = true)
{
// Only end the scroll view for the relevant BeginScrollView option, if any.
if (!ScrollFailed)
{
GUILayout.EndScrollView();
}
else if (!ManualUnstripFailed)
{
GUILayoutUtility.EndLayoutGroup();
EndScrollView_Impl(handleScrollWheel);
}
}
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
{
GUIUtility.CheckOnGUI();
var guiscrollGroup = GUILayoutUtility.BeginLayoutGroup(background, null, Il2CppType.Of<GUIScrollGroup>())
.TryCast<GUIScrollGroup>();
EventType type = Event.current.type;
if (type == EventType.Layout)
{
guiscrollGroup.resetCoords = true;
guiscrollGroup.isVertical = true;
guiscrollGroup.stretchWidth = 1;
guiscrollGroup.stretchHeight = 1;
guiscrollGroup.verticalScrollbar = verticalScrollbar;
guiscrollGroup.horizontalScrollbar = horizontalScrollbar;
guiscrollGroup.needsVerticalScrollbar = alwaysShowVertical;
guiscrollGroup.needsHorizontalScrollbar = alwaysShowHorizontal;
guiscrollGroup.ApplyOptions(options);
}
return BeginScrollView_Impl(guiscrollGroup.rect,
scrollPosition,
new Rect(0f, 0f, guiscrollGroup.clientWidth, guiscrollGroup.clientHeight),
alwaysShowHorizontal,
alwaysShowVertical,
horizontalScrollbar,
verticalScrollbar,
background
);
}
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
{
GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive);
var scrollViewState = GUIUtility.GetStateObject(Il2CppType.Of<ScrollViewState>(), controlID).TryCast<ScrollViewState>();
var scrollExt = ScrollViewStateUnstrip.FromPointer(scrollViewState.Pointer);
if (scrollExt == null) throw new Exception($"Could not get scrollExt for pointer '{scrollViewState.Pointer}'!");
bool apply = scrollExt.apply;
if (apply)
{
scrollPosition = scrollExt.scrollPosition;
scrollExt.apply = false;
}
scrollExt.position = position;
scrollExt.scrollPosition = scrollPosition;
scrollExt.visibleRect = scrollExt.viewRect = viewRect;
var rect = scrollExt.visibleRect;
rect.width = position.width;
rect.height = position.height;
ScrollStack.Push(scrollViewState);
Rect screenRect = new Rect(position.x, position.y, position.width, position.height);
EventType type = Event.current.type;
if (type != EventType.Layout)
{
if (type != EventType.Used)
{
bool flag = alwaysShowVertical;
bool flag2 = alwaysShowHorizontal;
if (flag2 || viewRect.width > screenRect.width)
{
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
flag2 = true;
}
if (flag || viewRect.height > screenRect.height)
{
rect.width = position.width - verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
screenRect.width -= verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
flag = true;
if (!flag2 && viewRect.width > screenRect.width)
{
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
flag2 = true;
}
}
if (Event.current.type == EventType.Repaint && background != GUIStyle.none)
{
background.Draw(position, position.Contains(Event.current.mousePosition), false, flag2 && flag, false);
}
if (flag2 && horizontalScrollbar != GUIStyle.none)
{
scrollPosition.x = HorizBar_Impl(
new Rect(
position.x,
position.yMax - horizontalScrollbar.fixedHeight,
screenRect.width,
horizontalScrollbar.fixedHeight),
scrollPosition.x,
Mathf.Min(screenRect.width, viewRect.width),
0f,
viewRect.width,
horizontalScrollbar
);
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
scrollPosition.x = ((horizontalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.x, 0f, Mathf.Max(viewRect.width - position.width, 0f)) : 0f);
}
if (flag && verticalScrollbar != GUIStyle.none)
{
scrollPosition.y = VertBar_Impl(
new Rect(
screenRect.xMax + (float)verticalScrollbar.margin.left,
screenRect.y,
verticalScrollbar.fixedWidth,
screenRect.height),
scrollPosition.y,
Mathf.Min(screenRect.height, viewRect.height),
0f,
viewRect.height,
verticalScrollbar
);
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
scrollPosition.y = ((verticalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.y, 0f, Mathf.Max(viewRect.height - position.height, 0f)) : 0f);
}
}
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
}
GUIClip.Push(screenRect, new Vector2(Mathf.Round(-scrollPosition.x - viewRect.x), Mathf.Round(-scrollPosition.y - viewRect.y)), Vector2.zero, false);
return scrollPosition;
}
public static float HorizBar_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, leftValue, rightValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "leftbutton"),
GUI.skin.GetStyle(style.name + "rightbutton"),
true);
}
public static float VertBar_Impl(Rect position, float value, float size, float topValue, float bottomValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, topValue, bottomValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "upbutton"),
GUI.skin.GetStyle(style.name + "downbutton"),
false);
}
private static void EndScrollView_Impl(bool handleScrollWheel)
{
GUIUtility.CheckOnGUI();
if (ScrollStack.Count <= 0) return;
var state = ScrollStack.Peek().TryCast<ScrollViewState>();
var scrollExt = ScrollViewStateUnstrip.FromPointer(state.Pointer);
if (scrollExt == null) throw new Exception("Could not get scrollExt!");
GUIClip.Pop();
ScrollStack.Pop();
var position = scrollExt.position;
if (handleScrollWheel && Event.current.type == EventType.ScrollWheel && position.Contains(Event.current.mousePosition))
{
var pos = scrollExt.scrollPosition;
pos.x = Mathf.Clamp(scrollExt.scrollPosition.x + Event.current.delta.x * 20f, 0f, scrollExt.viewRect.width - scrollExt.visibleRect.width);
pos.y = Mathf.Clamp(scrollExt.scrollPosition.y + Event.current.delta.y * 20f, 0f, scrollExt.viewRect.height - scrollExt.visibleRect.height);
if (scrollExt.scrollPosition.x < 0f)
{
pos.x = 0f;
}
if (pos.y < 0f)
{
pos.y = 0f;
}
scrollExt.apply = true;
Event.current.Use();
}
}
private static float Scroller_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
{
GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
Rect position2;
Rect rect;
Rect rect2;
if (horiz)
{
position2 = new Rect(position.x + leftButton.fixedWidth, position.y, position.width - leftButton.fixedWidth - rightButton.fixedWidth, position.height);
rect = new Rect(position.x, position.y, leftButton.fixedWidth, position.height);
rect2 = new Rect(position.xMax - rightButton.fixedWidth, position.y, rightButton.fixedWidth, position.height);
}
else
{
position2 = new Rect(position.x, position.y + leftButton.fixedHeight, position.width, position.height - leftButton.fixedHeight - rightButton.fixedHeight);
rect = new Rect(position.x, position.y, position.width, leftButton.fixedHeight);
rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight);
}
value = Slider_Impl(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
bool flag = Event.current.type == EventType.MouseUp;
if (ScrollerRepeatButton_Impl(controlID, rect, leftButton))
{
value -= 10f * ((leftValue >= rightValue) ? -1f : 1f);
}
if (ScrollerRepeatButton_Impl(controlID, rect2, rightButton))
{
value += 10f * ((leftValue >= rightValue) ? -1f : 1f);
}
if (flag && Event.current.type == EventType.Used)
{
s_ScrollControlId = 0;
}
if (leftValue < rightValue)
{
value = Mathf.Clamp(value, leftValue, rightValue - size);
}
else
{
value = Mathf.Clamp(value, rightValue, leftValue - size);
}
return value;
}
public static float Slider_Impl(Rect position, float value, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
{
if (id == 0)
{
id = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
}
var sliderHandler = new SliderHandlerUnstrip(position, value, size, start, end, slider, thumb, horiz, id);
return sliderHandler.Handle();
}
private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style)
{
bool result = false;
if (GUI.DoRepeatButton(rect, GUIContent.none, style, FocusType.Passive))
{
bool flag = s_ScrollControlId != scrollerID;
s_ScrollControlId = scrollerID;
if (flag)
{
result = true;
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(250.0);
}
else if (Il2CppSystem.DateTime.Now >= GUI.nextScrollStepTime)
{
result = true;
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(30.0);
}
if (Event.current.type == EventType.Repaint)
{
GUI.InternalRepaintEditorWindow();
}
}
return result;
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class ScrollViewStateUnstrip
{
public Rect position;
public Rect visibleRect;
public Rect viewRect;
public Vector2 scrollPosition;
public bool apply;
public static Dictionary<IntPtr, ScrollViewStateUnstrip> Dict = new Dictionary<IntPtr, ScrollViewStateUnstrip>();
public static ScrollViewStateUnstrip FromPointer(IntPtr ptr)
{
if (!Dict.ContainsKey(ptr))
{
Dict.Add(ptr, new ScrollViewStateUnstrip());
}
return Dict[ptr];
}
}
}

View File

@ -0,0 +1,371 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnhollowerRuntimeLib;
namespace Explorer
{
public struct SliderHandlerUnstrip
{
private readonly Rect position;
private readonly float currentValue;
private readonly float size;
private readonly float start;
private readonly float end;
private readonly GUIStyle slider;
private readonly GUIStyle thumb;
private readonly bool horiz;
private readonly int id;
public SliderHandlerUnstrip(Rect position, float currentValue, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
{
this.position = position;
this.currentValue = currentValue;
this.size = size;
this.start = start;
this.end = end;
this.slider = slider;
this.thumb = thumb;
this.horiz = horiz;
this.id = id;
}
public float Handle()
{
float result;
if (this.slider == null || this.thumb == null)
{
result = this.currentValue;
}
else
{
switch (this.CurrentEventType())
{
case EventType.MouseDown:
return this.OnMouseDown();
case EventType.MouseUp:
return this.OnMouseUp();
case EventType.MouseDrag:
return this.OnMouseDrag();
case EventType.Repaint:
return this.OnRepaint();
}
result = this.currentValue;
}
return result;
}
private float OnMouseDown()
{
float result;
if (!this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
{
result = this.currentValue;
}
else
{
GUI.scrollTroughSide = 0;
GUIUtility.hotControl = this.id;
this.CurrentEvent().Use();
if (this.ThumbSelectionRect().Contains(this.CurrentEvent().mousePosition))
{
this.StartDraggingWithValue(this.ClampedCurrentValue());
result = this.currentValue;
}
else
{
GUI.changed = true;
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(250.0);
GUI.scrollTroughSide = this.CurrentScrollTroughSide();
result = this.PageMovementValue();
}
else
{
float num = this.ValueForCurrentMousePosition();
this.StartDraggingWithValue(num);
result = this.Clamp(num);
}
}
}
return result;
}
private float OnMouseDrag()
{
float result;
if (GUIUtility.hotControl != this.id)
{
result = this.currentValue;
}
else
{
SliderState sliderState = this.SliderState();
if (!sliderState.isDragging)
{
result = this.currentValue;
}
else
{
GUI.changed = true;
this.CurrentEvent().Use();
float num = this.MousePosition() - sliderState.dragStartPos;
float value = sliderState.dragStartValue + num / this.ValuesPerPixel();
result = this.Clamp(value);
}
}
return result;
}
private float OnMouseUp()
{
if (GUIUtility.hotControl == this.id)
{
this.CurrentEvent().Use();
GUIUtility.hotControl = 0;
}
return this.currentValue;
}
private float OnRepaint()
{
this.slider.Draw(this.position, GUIContent.none, this.id);
if (!this.IsEmptySlider() && this.currentValue >= this.MinValue() && this.currentValue <= this.MaxValue())
{
this.thumb.Draw(this.ThumbRect(), GUIContent.none, this.id);
}
float result;
if (GUIUtility.hotControl != this.id || !this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
{
result = this.currentValue;
}
else if (this.ThumbRect().Contains(this.CurrentEvent().mousePosition))
{
if (GUI.scrollTroughSide != 0)
{
GUIUtility.hotControl = 0;
}
result = this.currentValue;
}
else
{
GUI.InternalRepaintEditorWindow();
if (SystemClock.now < GUI.nextScrollStepTime)
{
result = this.currentValue;
}
else if (this.CurrentScrollTroughSide() != GUI.scrollTroughSide)
{
result = this.currentValue;
}
else
{
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(30.0);
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
GUI.changed = true;
result = this.PageMovementValue();
}
else
{
result = this.ClampedCurrentValue();
}
}
}
return result;
}
private EventType CurrentEventType()
{
return this.CurrentEvent().GetTypeForControl(this.id);
}
private int CurrentScrollTroughSide()
{
float num = (!this.horiz) ? this.CurrentEvent().mousePosition.y : this.CurrentEvent().mousePosition.x;
float num2 = (!this.horiz) ? this.ThumbRect().y : this.ThumbRect().x;
return (num <= num2) ? -1 : 1;
}
private bool IsEmptySlider()
{
return this.start == this.end;
}
private bool SupportsPageMovements()
{
return this.size != 0f && GUI.usePageScrollbars;
}
private float PageMovementValue()
{
float num = this.currentValue;
int num2 = (this.start <= this.end) ? 1 : -1;
if (this.MousePosition() > this.PageUpMovementBound())
{
num += this.size * (float)num2 * 0.9f;
}
else
{
num -= this.size * (float)num2 * 0.9f;
}
return this.Clamp(num);
}
private float PageUpMovementBound()
{
float result;
if (this.horiz)
{
result = this.ThumbRect().xMax - this.position.x;
}
else
{
result = this.ThumbRect().yMax - this.position.y;
}
return result;
}
private Event CurrentEvent()
{
return Event.current;
}
private float ValueForCurrentMousePosition()
{
float result;
if (this.horiz)
{
result = (this.MousePosition() - this.ThumbRect().width * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
}
else
{
result = (this.MousePosition() - this.ThumbRect().height * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
}
return result;
}
private float Clamp(float value)
{
return Mathf.Clamp(value, this.MinValue(), this.MaxValue());
}
private Rect ThumbSelectionRect()
{
return this.ThumbRect();
}
private void StartDraggingWithValue(float dragStartValue)
{
SliderState sliderState = this.SliderState();
sliderState.dragStartPos = this.MousePosition();
sliderState.dragStartValue = dragStartValue;
sliderState.isDragging = true;
}
private SliderState SliderState()
{
return (SliderState)GUIUtility.GetStateObject(Il2CppType.Of<SliderState>(), this.id).TryCast<SliderState>();
}
private Rect ThumbRect()
{
return (!this.horiz) ? this.VerticalThumbRect() : this.HorizontalThumbRect();
}
private Rect VerticalThumbRect()
{
float num = this.ValuesPerPixel();
Rect result;
if (this.start < this.end)
{
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * num + this.ThumbSize());
}
else
{
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() + this.size - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * -num + this.ThumbSize());
}
return result;
}
private Rect HorizontalThumbRect()
{
float num = this.ValuesPerPixel();
Rect result;
if (this.start < this.end)
{
result = new Rect((this.ClampedCurrentValue() - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y + (float)this.slider.padding.top, this.size * num + this.ThumbSize(), this.position.height - (float)this.slider.padding.vertical);
}
else
{
result = new Rect((this.ClampedCurrentValue() + this.size - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y, this.size * -num + this.ThumbSize(), this.position.height);
}
return result;
}
private float ClampedCurrentValue()
{
return this.Clamp(this.currentValue);
}
private float MousePosition()
{
float result;
if (this.horiz)
{
result = this.CurrentEvent().mousePosition.x - this.position.x;
}
else
{
result = this.CurrentEvent().mousePosition.y - this.position.y;
}
return result;
}
private float ValuesPerPixel()
{
float result;
if (this.horiz)
{
result = (this.position.width - (float)this.slider.padding.horizontal - this.ThumbSize()) / (this.end - this.start);
}
else
{
result = (this.position.height - (float)this.slider.padding.vertical - this.ThumbSize()) / (this.end - this.start);
}
return result;
}
private float ThumbSize()
{
float result;
if (this.horiz)
{
result = ((this.thumb.fixedWidth == 0f) ? ((float)this.thumb.padding.horizontal) : this.thumb.fixedWidth);
}
else
{
result = ((this.thumb.fixedHeight == 0f) ? ((float)this.thumb.padding.vertical) : this.thumb.fixedHeight);
}
return result;
}
private float MaxValue()
{
return Mathf.Max(this.start, this.end) - this.size;
}
private float MinValue()
{
return Mathf.Min(this.start, this.end);
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public static class UnstripExtensions
{
public static Rect GetLastUnstripped(this GUILayoutGroup group)
{
Rect result;
if (group.m_Cursor > 0 && group.m_Cursor <= group.entries.Count)
{
GUILayoutEntry guilayoutEntry = group.entries[group.m_Cursor - 1];
result = guilayoutEntry.rect;
}
else
{
result = GUILayoutEntry.kDummyRect;
}
return result;
}
}
}

View File

@ -21,15 +21,25 @@ 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 Component[] m_components;
private Vector2 m_compScroll = Vector2.zero; private Vector2 m_compScroll = Vector2.zero;
private PageHelper CompPages = new PageHelper();
private readonly Vector3[] m_cachedInput = new Vector3[3];
private float m_translateAmount = 0.3f; private float m_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;
private bool m_freeze;
private Vector3 m_frozenPosition;
private Quaternion m_frozenRotation;
private Vector3 m_frozenScale;
private bool m_autoApplyTransform;
private bool m_autoUpdateTransform;
private bool m_localContext;
private readonly List<Component> m_cachedDestroyList = new List<Component>(); private readonly List<Component> m_cachedDestroyList = new List<Component>();
//private string m_addComponentInput = ""; //private string m_addComponentInput = "";
@ -38,12 +48,6 @@ namespace Explorer
public bool GetObjectAsGameObject() public bool GetObjectAsGameObject()
{ {
if (Target == null)
{
MelonLogger.Log("Target is null!");
return false;
}
var targetType = Target.GetType(); var targetType = Target.GetType();
if (targetType == typeof(GameObject)) if (targetType == typeof(GameObject))
@ -71,32 +75,87 @@ namespace Explorer
m_name = m_object.name; m_name = m_object.name;
m_scene = string.IsNullOrEmpty(m_object.scene.name) m_scene = string.IsNullOrEmpty(m_object.scene.name)
? "None" ? "None (Asset/Resource)"
: m_object.scene.name; : m_object.scene.name;
var list = new List<Transform>(); CacheTransformValues();
for (int i = 0; i < m_object.transform.childCount; i++)
{ Update();
list.Add(m_object.transform.GetChild(i));
} }
m_children = list.ToArray();
private void CacheTransformValues()
{
if (m_localContext)
{
m_cachedInput[0] = m_object.transform.localPosition;
m_cachedInput[1] = m_object.transform.localEulerAngles;
}
else
{
m_cachedInput[0] = m_object.transform.position;
m_cachedInput[1] = m_object.transform.eulerAngles;
}
m_cachedInput[2] = m_object.transform.localScale;
} }
public override void Update() public override void Update()
{ {
try try
{ {
if (Target == null)
{
MelonLogger.Log("Target is null!");
DestroyWindow();
return;
}
else if (Target is UnityEngine.Object uObj)
{
if (!uObj)
{
MelonLogger.Log("Target was destroyed!");
DestroyWindow();
return;
}
}
if (!m_object && !GetObjectAsGameObject()) if (!m_object && !GetObjectAsGameObject())
{ {
throw new Exception("Object is null!"); throw new Exception("Object is null!");
} }
var list = new List<Component>(); if (m_freeze)
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
{ {
list.Add(comp); if (m_localContext)
{
m_object.transform.localPosition = m_frozenPosition;
m_object.transform.localRotation = m_frozenRotation;
} }
m_components = list.ToArray(); else
{
m_object.transform.position = m_frozenPosition;
m_object.transform.rotation = m_frozenRotation;
}
m_object.transform.localScale = m_frozenScale;
}
// update child objects
var childList = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++)
{
childList.Add(m_object.transform.GetChild(i));
}
childList.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_children = childList.ToArray();
ChildPages.ItemCount = m_children.Length;
// update components
var compList = new Il2CppSystem.Collections.Generic.List<Component>();
m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
m_components = compList.ToArray();
CompPages.ItemCount = m_components.Length;
} }
catch (Exception e) catch (Exception e)
{ {
@ -106,7 +165,7 @@ namespace Explorer
private void DestroyOnException(Exception e) private void DestroyOnException(Exception e)
{ {
MelonLogger.Log($"{e.GetType()}, {e.Message}"); MelonLogger.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
DestroyWindow(); DestroyWindow();
} }
@ -154,18 +213,22 @@ namespace Explorer
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box); GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
} }
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); scroll = GUIUnstrip.BeginScrollView(scroll);
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null); GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
if (m_scene == UnityHelpers.ActiveSceneName) if (m_scene == UnityHelpers.ActiveSceneName)
{ {
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) })) if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
{ {
ScenePage.Instance.SetTransformTarget(m_object.transform); ScenePage.Instance.SetTransformTarget(m_object.transform);
MainMenu.SetCurrentPage(0); MainMenu.SetCurrentPage(0);
} }
} }
if (GUILayout.Button("Reflection Inspect", new GUILayoutOption[] { GUILayout.Width(150) }))
{
WindowManager.InspectObject(Target, out _, true);
}
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -201,7 +264,7 @@ namespace Explorer
GameObjectControls(); GameObjectControls();
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
if (!WindowManager.TabView) if (!WindowManager.TabView)
{ {
@ -218,29 +281,47 @@ namespace Explorer
private void TransformList(Rect m_rect) private void TransformList(Rect m_rect)
{ {
GUILayout.BeginVertical(GUI.skin.box, null); // new GUILayoutOption[] { GUILayout.Height(250) }); GUILayout.BeginVertical(GUI.skin.box, null);
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView); m_transformScroll = GUIUnstrip.BeginScrollView(m_transformScroll);
GUILayout.Label("<b><size=15>Children</size></b>", null);
GUILayout.BeginHorizontal(null);
ChildPages.DrawLimitInputArea();
if (ChildPages.ItemCount > ChildPages.ItemsPerPage)
{
ChildPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
ChildPages.TurnPage(Turn.Left, ref this.m_transformScroll);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
ChildPages.TurnPage(Turn.Right, ref this.m_transformScroll);
}
}
GUILayout.EndHorizontal();
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.ItemsPerPage && j < ChildPages.ItemCount); 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, m_rect.width / 2 - 60);
} UIHelpers.GOButton(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, m_rect.width / 2 - 60);
} }
} }
else else
@ -248,17 +329,37 @@ namespace Explorer
GUILayout.Label("<i>None</i>", null); GUILayout.Label("<i>None</i>", null);
} }
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
private void ComponentList(Rect m_rect) private void ComponentList(Rect m_rect)
{ {
GUILayout.BeginVertical(GUI.skin.box, null); // new GUILayoutOption[] { GUILayout.Height(250) }); GUILayout.BeginVertical(GUI.skin.box, null);
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView); m_compScroll = GUIUnstrip.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.ItemCount > CompPages.ItemsPerPage)
{
CompPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
CompPages.TurnPage(Turn.Left, ref this.m_compScroll);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
CompPages.TurnPage(Turn.Right, ref this.m_compScroll);
}
}
GUILayout.EndHorizontal();
GUI.skin.button.alignment = TextAnchor.MiddleLeft; GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (m_cachedDestroyList.Count > 0) if (m_cachedDestroyList.Count > 0)
{ {
@ -267,15 +368,15 @@ namespace Explorer
if (m_components != null) if (m_components != null)
{ {
foreach (var component in m_components) int start = CompPages.CalculateOffsetIndex();
for (int j = start; (j < start + CompPages.ItemsPerPage && j < CompPages.ItemCount); j++)
{ {
var component = m_components[j];
if (!component) continue; if (!component) continue;
var ilType = component.GetIl2CppType(); var ilType = component.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType)) if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
@ -286,7 +387,7 @@ namespace Explorer
{ {
GUILayout.Space(26); GUILayout.Space(26);
} }
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) })) if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
{ {
ReflectObject(component); ReflectObject(component);
} }
@ -308,7 +409,7 @@ namespace Explorer
} }
} }
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
@ -355,6 +456,16 @@ namespace Explorer
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset; m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
} }
var lbl = m_freeze ? "<color=lime>Unfreeze</color>" : "<color=orange>Freeze Pos/Rot</color>";
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(110) }))
{
m_freeze = !m_freeze;
if (m_freeze)
{
UpdateFreeze();
}
}
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -379,10 +490,52 @@ namespace Explorer
GUILayout.BeginVertical(GUI.skin.box, null); GUILayout.BeginVertical(GUI.skin.box, null);
var t = m_object.transform; m_cachedInput[0] = TranslateControl(TranslateType.Position, ref m_translateAmount, false);
TranslateControl(t, TranslateType.Position, ref m_translateAmount, false); m_cachedInput[1] = TranslateControl(TranslateType.Rotation, ref m_rotateAmount, true);
TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true); m_cachedInput[2] = TranslateControl(TranslateType.Scale, ref m_scaleAmount, false);
TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false);
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("<color=lime>Apply to Transform</color>", null) || m_autoApplyTransform)
{
if (m_localContext)
{
m_object.transform.localPosition = m_cachedInput[0];
m_object.transform.localEulerAngles = m_cachedInput[1];
}
else
{
m_object.transform.position = m_cachedInput[0];
m_object.transform.eulerAngles = m_cachedInput[1];
}
m_object.transform.localScale = m_cachedInput[2];
if (m_freeze)
{
UpdateFreeze();
}
}
if (GUILayout.Button("<color=lime>Update from Transform</color>", null) || m_autoUpdateTransform)
{
CacheTransformValues();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?");
BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?");
GUILayout.EndHorizontal();
bool b = m_localContext;
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "red") + ">Use local transform values?</color>", null);
if (b != m_localContext)
{
m_localContext = b;
CacheTransformValues();
if (m_freeze)
{
UpdateFreeze();
}
}
GUILayout.EndVertical(); GUILayout.EndVertical();
@ -396,6 +549,30 @@ namespace Explorer
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
private void UpdateFreeze()
{
if (m_localContext)
{
m_frozenPosition = m_object.transform.localPosition;
m_frozenRotation = m_object.transform.localRotation;
}
else
{
m_frozenPosition = m_object.transform.position;
m_frozenRotation = m_object.transform.rotation;
}
m_frozenScale = m_object.transform.localScale;
}
private void BoolToggle(ref bool value, string message)
{
string lbl = "<color=";
lbl += value ? "lime" : "red";
lbl += $">{message}</color>";
value = GUILayout.Toggle(value, lbl, null);
}
public enum TranslateType public enum TranslateType
{ {
Position, Position,
@ -403,50 +580,55 @@ namespace Explorer
Scale Scale
} }
private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime) private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime)
{ {
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<color=cyan><b>" + mode + "</b></color>:", new GUILayoutOption[] { GUILayout.Width(65) }); GUILayout.Label($"<color=cyan><b>{(m_localContext ? "Local " : "")}{mode}</b></color>:",
new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) });
Vector3 vector = Vector3.zero; var transform = m_object.transform;
switch (mode) switch (mode)
{ {
case TranslateType.Position: vector = transform.localPosition; break; case TranslateType.Position:
case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break; var pos = m_localContext ? transform.localPosition : transform.position;
case TranslateType.Scale: vector = transform.localScale; break; GUILayout.Label(pos.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
break;
case TranslateType.Rotation:
var rot = m_localContext ? transform.localEulerAngles : transform.eulerAngles;
GUILayout.Label(rot.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
break;
case TranslateType.Scale:
GUILayout.Label(transform.localScale.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
break;
} }
GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
Vector3 input = m_cachedInput[(int)mode];
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUI.skin.label.alignment = TextAnchor.MiddleRight; GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) }); GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
PlusMinusFloat(ref vector.x, amount, multByTime); PlusMinusFloat(ref input.x, amount, multByTime);
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) }); GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
PlusMinusFloat(ref vector.y, amount, multByTime); PlusMinusFloat(ref input.y, amount, multByTime);
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) }); GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
PlusMinusFloat(ref vector.z, amount, multByTime); PlusMinusFloat(ref input.z, amount, multByTime);
switch (mode)
{
case TranslateType.Position: transform.localPosition = vector; break;
case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break;
case TranslateType.Scale: transform.localScale = vector; break;
}
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) }); GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
var input = amount.ToString("F3"); var amountInput = amount.ToString("F3");
input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) }); amountInput = GUILayout.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
if (float.TryParse(input, out float f)) if (float.TryParse(amountInput, out float f))
{ {
amount = f; amount = f;
} }
GUI.skin.label.alignment = TextAnchor.UpperLeft; GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
return input;
} }
private void PlusMinusFloat(ref float f, float amount, bool multByTime) private void PlusMinusFloat(ref float f, float amount, bool multByTime)
@ -466,7 +648,5 @@ namespace Explorer
f += multByTime ? amount * Time.deltaTime : amount; f += multByTime ? amount * Time.deltaTime : amount;
} }
} }
} }
} }

View File

@ -20,8 +20,8 @@ namespace Explorer
private CacheObjectBase[] m_allCachedMembers; private CacheObjectBase[] m_allCachedMembers;
private CacheObjectBase[] m_cachedMembersFiltered; private CacheObjectBase[] m_cachedMembersFiltered;
private int m_pageOffset;
private int m_limitPerPage = 20; public PageHelper Pages = new PageHelper();
private bool m_autoUpdate = false; private bool m_autoUpdate = false;
private string m_search = ""; private string m_search = "";
@ -67,6 +67,20 @@ namespace Explorer
public override void Update() public override void Update()
{ {
if (Target == null)
{
DestroyWindow();
return;
}
else if (Target is UnityEngine.Object uObj)
{
if (!uObj)
{
DestroyWindow();
return;
}
}
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray(); m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
if (m_autoUpdate) if (m_autoUpdate)
@ -138,7 +152,7 @@ namespace Explorer
var name = $"{member.DeclaringType.Name}.{member.Name}"; var name = $"{member.DeclaringType.Name}.{member.Name}";
// blacklist (should probably make a proper implementation) // blacklist (should probably make a proper implementation)
if (name == "Type.DeclaringMethod" || member.Name.Contains("Il2CppType") || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) if (name == "Type.DeclaringMethod" || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) //|| member.Name.Contains("Il2CppType")
{ {
continue; continue;
} }
@ -254,41 +268,32 @@ namespace Explorer
GUILayout.Space(10); GUILayout.Space(10);
Pages.ItemCount = m_cachedMembersFiltered.Length;
// prev/next page buttons // prev/next page buttons
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Limit per page:</b>", new GUILayoutOption[] { GUILayout.Width(125) });
var limitString = m_limitPerPage.ToString();
limitString = GUILayout.TextField(limitString, new GUILayoutOption[] { GUILayout.Width(60) });
if (int.TryParse(limitString, out int lim))
{
m_limitPerPage = lim;
}
int count = m_cachedMembersFiltered.Length; Pages.DrawLimitInputArea();
if (count > m_limitPerPage)
if (Pages.ItemCount > Pages.ItemsPerPage)
{ {
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limitPerPage)) - 1;
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) })) if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{ {
if (m_pageOffset > 0) m_pageOffset--; Pages.TurnPage(Turn.Left, ref this.scroll);
scroll = Vector2.zero;
} }
GUI.skin.label.alignment = TextAnchor.MiddleCenter; Pages.CurrentPageLabel();
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) })) if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{ {
if (m_pageOffset < maxOffset) m_pageOffset++; Pages.TurnPage(Turn.Right, ref this.scroll);
scroll = Vector2.zero;
} }
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
// ====== BODY ====== // ====== BODY ======
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); scroll = GUIUnstrip.BeginScrollView(scroll);
GUILayout.Space(10); GUILayout.Space(10);
@ -297,18 +302,9 @@ namespace Explorer
GUILayout.BeginVertical(GUI.skin.box, null); GUILayout.BeginVertical(GUI.skin.box, null);
var members = this.m_cachedMembersFiltered; var members = this.m_cachedMembersFiltered;
int start = m_pageOffset * m_limitPerPage; int start = Pages.CalculateOffsetIndex();
if (start >= count) for (int j = start; (j < start + Pages.ItemsPerPage && j < members.Length); j++)
{
int maxOffset = (int)Mathf.Ceil((float)(m_cachedMembersFiltered.Length / (decimal)m_limitPerPage)) - 1;
if (m_pageOffset > maxOffset)
{
m_pageOffset = 0;
}
}
for (int j = start; (j < start + m_limitPerPage && j < members.Length); j++)
{ {
var holder = members[j]; var holder = members[j];
@ -325,12 +321,12 @@ namespace Explorer
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
// if not last element // if not last element
if (!(j == (start + m_limitPerPage - 1) || j == (members.Length - 1))) if (!(j == (start + Pages.ItemsPerPage - 1) || j == (members.Length - 1)))
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true); UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
if (!WindowManager.TabView) if (!WindowManager.TabView)
{ {
@ -367,7 +363,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;
m_pageOffset = 0; Pages.PageOffset = 0;
scroll = Vector2.zero;
} }
GUI.color = Color.white; GUI.color = Color.white;
} }

View File

@ -31,17 +31,20 @@ namespace Explorer
GUI.skin.label.alignment = TextAnchor.MiddleCenter; GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) }); GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
var r = GUILayoutUtility.GetLastRect(); //var r = GUILayoutUtility.GetLastRect();
var r = GUIUnstrip.GetLastRect();
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)); var mousePos = InputHelper.mousePosition;
if (r.Contains(mouse) && Input.GetMouseButtonDown(0)) Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
if (r.Contains(mouse) && InputHelper.GetMouseButtonDown(0))
{ {
isResizing = true; isResizing = true;
m_currentWindow = ID; m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height); m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
} }
else if (!Input.GetMouseButton(0)) else if (!InputHelper.GetMouseButton(0))
{ {
isResizing = false; isResizing = false;
} }
@ -65,6 +68,7 @@ namespace Explorer
{ {
RESIZE_FAILED = true; RESIZE_FAILED = true;
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message); MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
return origRect; return origRect;
} }

View File

@ -26,6 +26,7 @@ namespace Explorer
} }
public override void Init() { } public override void Init() { }
public override void Update() public override void Update()
{ {
while (TargetTabID >= WindowManager.Windows.Count) while (TargetTabID >= WindowManager.Windows.Count)

View File

@ -86,23 +86,38 @@ namespace Explorer
// ========= Public Helpers ========= // ========= Public Helpers =========
public static UIWindow InspectObject(object obj, out bool createdNew) public static UIWindow InspectObject(object obj, out bool createdNew, bool forceReflection = false)
{ {
createdNew = false; createdNew = false;
UnityEngine.Object uObj = null; if (InputHelper.GetKey(KeyCode.LeftShift))
if (obj is UnityEngine.Object)
{ {
uObj = obj as UnityEngine.Object; forceReflection = true;
} }
Il2CppSystem.Object iObj = null;
if (obj is Il2CppSystem.Object isObj)
{
iObj = isObj;
}
if (!forceReflection)
{
foreach (var window in Windows) foreach (var window in Windows)
{ {
bool equals = ReferenceEquals(obj, window.Target); bool equals = ReferenceEquals(obj, window.Target);
if (!equals && uObj != null && window.Target is UnityEngine.Object uTarget) if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
{ {
equals = uObj.m_CachedPtr == uTarget.m_CachedPtr; if (iCurrent.GetIl2CppType().FullName != iTarget.GetIl2CppType().FullName)
{
if (iCurrent is Transform transform)
{
iCurrent = transform.gameObject;
}
}
equals = iCurrent.Pointer == iTarget.Pointer;
} }
if (equals) if (equals)
@ -111,9 +126,10 @@ namespace Explorer
return window; return window;
} }
} }
}
createdNew = true; createdNew = true;
if (obj is GameObject || obj is Transform) if (!forceReflection && (obj is GameObject || obj is Transform))
{ {
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject); return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
} }
@ -144,7 +160,7 @@ namespace Explorer
return new_window; return new_window;
} }
public static UIWindow InspectReflection(object obj) private static UIWindow InspectReflection(object obj)
{ {
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj); var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
FocusWindow(new_window); FocusWindow(new_window);
@ -176,7 +192,8 @@ namespace Explorer
private static bool RectContainsMouse(Rect rect) private static bool RectContainsMouse(Rect rect)
{ {
return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)); var mousePos = InputHelper.mousePosition;
return rect.Contains(new Vector2(mousePos.x, Screen.height - mousePos.y));
} }
public static int NextWindowID() public static int NextWindowID()