Compare commits

..

15 Commits
1.5.9 ... 1.6.5

Author SHA1 Message Date
0769b7ef23 1.6.5
* Add expander to Unity struct inspectors, collapsed by default
* `UnityEngine.Color` labels in Reflection Inspector are now the same color as the value for convenience
* Cleaned up InputHelper
2020-09-08 23:47:17 +10:00
5086dcc82b Update README.md 2020-09-08 20:21:34 +10:00
56abd38e92 Add build instructions to Readme 2020-09-08 20:18:37 +10:00
a7e6ae87df Update README.md 2020-09-08 19:47:07 +10:00
b5c584bb02 Update README.md 2020-09-08 19:46:54 +10:00
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
29 changed files with 667 additions and 425 deletions

View File

@ -16,16 +16,9 @@
### Known issues
* CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
* Reflection may fail with certain types (eg. `Nullable<T>`, some Dictionary types, etc). Please see [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower#known-issues) for more details.
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
## Features
* Scene hierarchy explorer
* Search loaded assets with filters
* Traverse and manipulate GameObjects
* Generic Reflection inspector
* C# REPL Console
* Inspect-under-mouse
## How to install
Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
@ -39,13 +32,23 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
* Press F7 to show or hide the menu.
* 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
* A simple menu which allows you to traverse the Transform heirarchy of the scene.
* Click on a GameObject to set it as the current path, or <b>Inspect</b> it to send it to an Inspector Window.
[![](https://i.imgur.com/BzTOCvp.png)](https://i.imgur.com/BzTOCvp.png)
### Inspectors
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
@ -59,30 +62,22 @@ CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Re
* Allows you to see the children and components on a GameObject.
* Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
[![](https://i.imgur.com/DiDvl0Q.png)](https://i.imgur.com/DiDvl0Q.png)
### Reflection Inspector
* The Reflection Inspector is used for all other supported objects.
* Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods.
* Can search and filter members for the ones you are interested in.
[![](https://i.imgur.com/Pq127XD.png)](https://i.imgur.com/Pq127XD.png)
### Object Search
* You can search for an `UnityEngine.Object` with the Object Search feature.
* Filter by name, type, etc.
* For GameObjects and Transforms you can filter which scene they are found in too.
[![](https://i.imgur.com/lK2RthM.png)](https://i.imgur.com/lK2RthM.png)
### C# REPL console
* A simple C# REPL console, allows you to execute a method body on the fly.
[![](https://i.imgur.com/5U4D1a8.png)](https://i.imgur.com/5U4D1a8.png)
### Inspect-under-mouse
* Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse.
@ -97,6 +92,18 @@ CppExplorer can force the mouse to be visible and unlocked when the menu is open
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
* You can create your own plugin using one of the two plugins above as an example. Usually only a few simple Harmony patches are needed to fix the problem.
## Building
If you'd like to build this yourself, everything you need (other than MelonLoader) is included with this repository, there is no need for recursive cloning etc.
1. Install MelonLoader for your game.
2. Download this repository and open the `CppExplorer.sln` file in the `src` folder.
3. In Visual Studio, right-click the CppExplorer C# Project in the solution browser and click "Unload Project".
4. Right-click the "CppExplorer (unloaded)" and select "Edit Project File".
5. Scroll down until you see the `<ItemGroup>` containing the References.
6. Fix all of the paths in the `..\Steam\` directory for your game (use the full path if you need to).
7. Reload the project and everything should be working, you can now build and run it.
## Credits
Written by Sinai.

BIN
img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 KiB

View File

@ -16,15 +16,14 @@ namespace Explorer
public string ValueTypeName;
public Type ValueType;
// Reflection Inspector only
public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public string ReflectionException { get; set; }
public int PropertyIndex { get; private set; }
private string m_propertyIndexInput = "0";
public string ReflectionException { get; set; }
public string RichTextName => m_richTextName ?? GetRichTextName();
private string m_richTextName;

View File

@ -7,16 +7,17 @@ using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using System.Reflection;
using UnhollowerBaseLib;
namespace Explorer
{
public class CacheDictionary : CacheObjectBase
public class CacheDictionary : CacheObjectBase, IExpandHeight
{
public bool IsExpanded { get; set; }
public PageHelper Pages = new PageHelper();
public float WhiteSpace { get; set; } = 215f;
public float ButtonWidthOffset { get; set; } = 290f;
public float WhiteSpace = 215f;
public float ButtonWidthOffset = 290f;
public PageHelper Pages = new PageHelper();
private CacheObjectBase[] m_cachedKeys;
private CacheObjectBase[] m_cachedValues;
@ -48,44 +49,30 @@ namespace Explorer
}
private IDictionary m_iDictionary;
// ========== Methods ==========
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
private IDictionary Il2CppDictionaryToMono()
{
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
// make generic dictionary from key and value type
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// get keys and values
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
var values = ValueType.GetProperty("Values").GetValue(Value);
// create a list to hold them
var keyList = new List<object>();
// create lists to hold them
var keyList = new List<object>();
var valueList = new List<object>();
// get keys enumerator and store keys
var keyEnumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null);
var keyCollectionType = keyEnumerator.GetType();
var keyMoveNext = keyCollectionType.GetMethod("MoveNext");
var keyCurrent = keyCollectionType.GetProperty("Current");
while ((bool)keyMoveNext.Invoke(keyEnumerator, null))
{
keyList.Add(keyCurrent.GetValue(keyEnumerator));
}
// store entries with reflection
EnumerateWithReflection(keys, keyList);
EnumerateWithReflection(values, valueList);
// get values enumerator and store values
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
var valueCollectionType = valueEnumerator.GetType();
var valueMoveNext = valueCollectionType.GetMethod("MoveNext");
var valueCurrent = valueCollectionType.GetProperty("Current");
while ((bool)valueMoveNext.Invoke(valueEnumerator, null))
{
valueList.Add(valueCurrent.GetValue(valueEnumerator));
}
// make actual mono dictionary
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// finally iterate into actual dictionary
// finally iterate into dictionary
for (int i = 0; i < keyList.Count; i++)
{
dict.Add(keyList[i], valueList[i]);
@ -94,47 +81,63 @@ namespace Explorer
return dict;
}
// ========== Methods ==========
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 (m_keysType == null || m_valuesType == null)
if (this.MemInfo != null)
{
if (this.MemInfo != null)
Type memberType = null;
switch (this.MemInfo.MemberType)
{
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];
}
case MemberTypes.Field:
memberType = (MemInfo as FieldInfo).FieldType;
break;
case MemberTypes.Property:
memberType = (MemInfo as PropertyInfo).PropertyType;
break;
}
else if (Value != null)
if (memberType != null && memberType.IsGenericType)
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_keysType = type.GetGenericArguments()[0];
m_valuesType = type.GetGenericArguments()[1];
}
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];
}
}
return;
}
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
@ -165,6 +168,28 @@ namespace Explorer
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)
@ -258,15 +283,11 @@ namespace Explorer
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.MinWidth((window.width / 3) - 60f) });
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
key.DrawValue(window, (window.width / 2) - 30f);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
val.DrawValue(window, (window.width / 2) - 30f);
GUILayout.EndHorizontal();
}
}

View File

@ -8,13 +8,13 @@ using UnityEngine;
namespace Explorer
{
public class CacheList : CacheObjectBase
public class CacheList : CacheObjectBase, IExpandHeight
{
public bool IsExpanded { get; set; }
public PageHelper Pages = new PageHelper();
public float WhiteSpace { get; set; } = 215f;
public float ButtonWidthOffset { get; set; } = 290f;
public float WhiteSpace = 215f;
public float ButtonWidthOffset = 290f;
public PageHelper Pages = new PageHelper();
private CacheObjectBase[] m_cachedEntries;
@ -52,6 +52,7 @@ namespace Explorer
{
get => GetItemProperty();
}
private PropertyInfo m_itemProperty;
// ========== Methods ==========

View File

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

View File

@ -9,6 +9,8 @@ namespace Explorer
{
public class CacheColor : CacheObjectBase
{
private bool IsExpanded;
private string r = "0";
private string g = "0";
private string b = "0";
@ -28,9 +30,30 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Color</color>: {((Color)Value).ToString()}", null);
if (CanWrite)
{
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
var c = (Color)Value;
GUI.color = c;
GUILayout.Label($"<color=yellow>Color:</color> {c.ToString()}", null);
GUI.color = Color.white;
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;

View File

@ -9,6 +9,8 @@ namespace Explorer
{
public class CacheQuaternion : CacheObjectBase
{
private bool IsExpanded;
private string x = "0";
private string y = "0";
private string z = "0";
@ -26,9 +28,27 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
if (CanWrite)
{
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
if (CanWrite)
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;

View File

@ -9,6 +9,8 @@ namespace Explorer
{
public class CacheRect : CacheObjectBase
{
private bool IsExpanded;
private string x = "0";
private string y = "0";
private string w = "0";
@ -28,9 +30,27 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
if (CanWrite)
{
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
if (CanWrite)
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;

View File

@ -10,6 +10,8 @@ namespace Explorer
{
public class CacheVector : CacheObjectBase
{
private bool IsExpanded;
public int VectorSize = 2;
private string x = "0";
@ -63,9 +65,27 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
if (CanWrite)
{
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
if (CanWrite)
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;

View File

@ -12,15 +12,10 @@ namespace Explorer
{
public class CppExplorer : MelonMod
{
public const string GUID = "com.sinai.cppexplorer";
public const string VERSION = "1.5.9";
public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer"
#if Release_Unity2018
+ " (Unity 2018)"
#endif
;
public const string NAME = "CppExplorer";
public const string VERSION = "1.6.5";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.cppexplorer";
public static CppExplorer Instance { get; private set; }
@ -55,6 +50,8 @@ namespace Explorer
{
Instance = this;
InputHelper.Init();
new MainMenu();
new WindowManager();
@ -79,7 +76,7 @@ namespace Explorer
public override void OnUpdate()
{
// Check main toggle key input
if (Input.GetKeyDown(KeyCode.F7))
if (InputHelper.GetKeyDown(KeyCode.F7))
{
ShowMenu = !ShowMenu;
}
@ -87,7 +84,7 @@ namespace Explorer
if (ShowMenu)
{
// Check Force-Unlock input
if (Input.GetKeyDown(KeyCode.LeftAlt))
if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
{
ForceUnlockMouse = !ForceUnlockMouse;
}

View File

@ -12,32 +12,19 @@
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<AssemblyName>CppExplorer</AssemblyName>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Release\2019\</OutputPath>
<DefineConstants>Release_2019</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\2018\</OutputPath>
<DefineConstants>Release_Unity2018</DefineConstants>
<OutputPath>..\Release\</OutputPath>
<DefineConstants />
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<!-- Replace the '..\Steam\..` references with ones from your game (make sure to use the 'MelonLoader\' folder) -->
<Reference Include="Il2Cppmscorlib">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
@ -60,66 +47,33 @@
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- Unity 2019 build (InputLegacyModule.dll) -->
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
<Reference Include="UnityEngine">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Debug'">
<Reference Include="UnityEngine.CoreModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Debug'">
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Debug'">
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Debug'">
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Debug'">
<Reference Include="UnityEngine.UI">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</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>
<Compile Include="Helpers\IExpandHeight.cs" />
<Compile Include="CachedObjects\Struct\CacheColor.cs" />
<Compile Include="CachedObjects\Object\CacheDictionary.cs" />
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
@ -133,6 +87,7 @@
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
<Compile Include="CppExplorer.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" />

View File

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

View File

@ -3,14 +3,38 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace Explorer
{
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)
{
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

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

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

@ -0,0 +1,120 @@
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
{
// If Input module failed to load at all
public static bool NO_INPUT;
// Base UnityEngine.Input class
private static Type Input => _input ?? (_input = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
private static Type _input;
// Cached member infos
private static PropertyInfo _mousePosition;
private static MethodInfo _getKey;
private static MethodInfo _getKeyDown;
private static MethodInfo _getMouseButton;
private static MethodInfo _getMouseButtonDown;
public static void Init()
{
if (Input == null && !TryManuallyLoadInput())
{
NO_INPUT = true;
return;
}
// Cache reflection now that we know Input is loaded
_mousePosition = Input.GetProperty("mousePosition");
_getKey = Input.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
_getKeyDown = Input.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
_getMouseButton = Input.GetMethod("GetMouseButton", new Type[] { typeof(int) });
_getMouseButtonDown = Input.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
}
#pragma warning disable IDE1006 // Camel-case property (Unity style)
public static Vector3 mousePosition
{
get
{
if (NO_INPUT) return Vector3.zero;
return (Vector3)_mousePosition.GetValue(null);
}
}
#pragma warning restore IDE1006
public static bool GetKeyDown(KeyCode key)
{
if (NO_INPUT) return false;
return (bool)_getKeyDown.Invoke(null, new object[] { key });
}
public static bool GetKey(KeyCode key)
{
if (NO_INPUT) return false;
return (bool)_getKey.Invoke(null, new object[] { key });
}
/// <param name="btn">1 = left, 2 = middle, 3 = right, etc</param>
public static bool GetMouseButtonDown(int btn)
{
if (NO_INPUT) return false;
return (bool)_getMouseButtonDown.Invoke(null, new object[] { btn });
}
/// <param name="btn">1 = left, 2 = middle, 3 = right, etc</param>
public static bool GetMouseButton(int btn)
{
if (NO_INPUT) return false;
return (bool)_getMouseButton.Invoke(null, new object[] { btn });
}
private static bool TryManuallyLoadInput()
{
MelonLogger.Log("UnityEngine.Input is null, trying to load manually....");
if ((TryLoad("UnityEngine.InputLegacyModule.dll") || TryLoad("UnityEngine.CoreModule.dll")) && Input != null)
{
MelonLogger.Log("Ok!");
return true;
}
else
{
MelonLogger.Log("Could not load Input module!");
return false;
}
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;
}
}
}
}
}

View File

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

View File

@ -56,7 +56,10 @@ namespace Explorer
{
_horizBarStyle = new GUIStyle();
_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;
}
@ -73,7 +76,10 @@ namespace Explorer
{
_horizBarSmallStyle = new GUIStyle();
_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;
}

View File

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

View File

@ -17,7 +17,7 @@ namespace Explorer
{
if (CppExplorer.ShowMenu)
{
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
if (InputHelper.GetKey(KeyCode.LeftShift) && InputHelper.GetMouseButtonDown(1))
{
EnableInspect = !EnableInspect;
}
@ -35,7 +35,10 @@ namespace Explorer
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))
{
@ -43,7 +46,7 @@ namespace Explorer
m_objUnderMouseName = obj.transform.GetGameObjectPath();
if (Input.GetMouseButtonDown(0))
if (InputHelper.GetMouseButtonDown(0))
{
EnableInspect = false;
m_objUnderMouseName = "";
@ -63,7 +66,7 @@ namespace Explorer
{
if (m_objUnderMouseName != "")
{
var pos = Input.mousePosition;
var pos = InputHelper.mousePosition;
var rect = new Rect(
pos.x - (Screen.width / 2), // x
Screen.height - pos.y - 50, // y

View File

@ -19,6 +19,8 @@ namespace Explorer
private ScriptEvaluator _evaluator;
private readonly StringBuilder _sb = new StringBuilder();
private Vector2 inputAreaScroll;
private string MethodInput = "";
private string UsingInput = "";
@ -124,7 +126,12 @@ MelonLogger.Log(""hello world"");";
GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.Label("Enter code here as though it is a method body:", null);
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
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))
{

View File

@ -192,7 +192,7 @@ namespace Explorer
}
}
// --------- GUI Draw Function --------- //
// --------- GUI Draw Function --------- //
public override void DrawWindow()
{
@ -215,11 +215,9 @@ namespace Explorer
GUILayout.EndVertical();
}
catch (Exception e)
catch
{
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
m_currentTransform = null;
// supress
}
}
@ -229,39 +227,7 @@ namespace Explorer
// Current Scene label
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
try
{
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
var scenes = SceneManager.GetAllScenes().ToList();
if (scenes.Count > 1)
{
int changeWanted = 0;
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = -1;
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = 1;
}
if (changeWanted != 0)
{
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
index += changeWanted;
if (index > scenes.Count - 1)
{
index = 0;
}
else if (index < 0)
{
index = scenes.Count - 1;
}
m_currentScene = scenes[index].name;
}
}
}
catch { }
SceneChangeButtons();
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
GUILayout.EndHorizontal();
@ -269,7 +235,9 @@ namespace Explorer
// ----- 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();
@ -279,6 +247,39 @@ namespace Explorer
GUILayout.Space(5);
}
private void SceneChangeButtons()
{
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
var scenes = SceneManager.GetAllScenes().ToList();
if (scenes.Count > 1)
{
int changeWanted = 0;
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = -1;
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = 1;
}
if (changeWanted != 0)
{
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
index += changeWanted;
if (index > scenes.Count - 1)
{
index = 0;
}
else if (index < 0)
{
index = scenes.Count - 1;
}
m_currentScene = scenes[index].name;
}
}
}
private void DrawPageButtons()
{
GUILayout.BeginHorizontal(null);

View File

@ -385,8 +385,8 @@ namespace Explorer
public static IEnumerable<object> GetInstanceClassScanner()
{
var query = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(ReflectionHelpers.GetTypesSafe)
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
.SelectMany(t => t.TryGetTypes())
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
var flags = BindingFlags.Public | BindingFlags.Static;
var flatFlags = flags | BindingFlags.FlattenHierarchy;

View File

@ -41,8 +41,7 @@ namespace Explorer
}
}
private static PropertyInfo m_scrollViewStatesInfo;
// ======= public methods ======= //
public static Rect GetLastRect()
{
@ -73,7 +72,6 @@ namespace Explorer
catch
{
ScrollFailed = true;
return scroll;
}
}
@ -86,10 +84,8 @@ namespace Explorer
}
catch (Exception e)
{
MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_ImplLayout: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
MelonLogger.Log("Exception on manual BeginScrollView: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
ManualUnstripFailed = true;
return scroll;
}
}
@ -113,8 +109,6 @@ namespace Explorer
}
}
// ======= private methods ======= //
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
{

View File

@ -48,12 +48,6 @@ namespace Explorer
public bool GetObjectAsGameObject()
{
if (Target == null)
{
MelonLogger.Log("Target is null!");
return false;
}
var targetType = Target.GetType();
if (targetType == typeof(GameObject))
@ -108,6 +102,22 @@ namespace Explorer
{
try
{
if (Target == null)
{
MelonLogger.Log("Target is null!");
DestroyWindow();
return;
}
else if (Target is UnityEngine.Object uObj)
{
if (!uObj)
{
MelonLogger.Log("Target was destroyed!");
DestroyWindow();
return;
}
}
if (!m_object && !GetObjectAsGameObject())
{
throw new Exception("Object is null!");

View File

@ -22,8 +22,6 @@ namespace Explorer
private CacheObjectBase[] m_cachedMembersFiltered;
public PageHelper Pages = new PageHelper();
//private int m_pageOffset;
//private int m_limitPerPage = 20;
private bool m_autoUpdate = false;
private string m_search = "";
@ -69,6 +67,20 @@ namespace Explorer
public override void Update()
{
if (Target == null)
{
DestroyWindow();
return;
}
else if (Target is UnityEngine.Object uObj)
{
if (!uObj)
{
DestroyWindow();
return;
}
}
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
if (m_autoUpdate)

View File

@ -34,15 +34,17 @@ namespace Explorer
//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;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
else if (!Input.GetMouseButton(0))
else if (!InputHelper.GetMouseButton(0))
{
isResizing = false;
}

View File

@ -90,7 +90,7 @@ namespace Explorer
{
createdNew = false;
if (Input.GetKey(KeyCode.LeftShift))
if (InputHelper.GetKey(KeyCode.LeftShift))
{
forceReflection = true;
}
@ -192,7 +192,8 @@ namespace Explorer
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()