Compare commits

..

33 Commits

Author SHA1 Message Date
5d58993b07 1.7.31
* Added support for Il2Cpp Hashtable (non-generic Dict)
* Dictionaries should now display CacheOther values better (smaller buttons)
* Cleaned up and improved some of CacheDictionary performance
2020-09-15 17:38:10 +10:00
eea581f8d5 Update ResizeDrag.cs 2020-09-14 20:27:49 +10:00
9bb3c77bae 1.7.3
* Reverted some unstrip fixes from 1.7.2 because it was causing more problems than it solved.
2020-09-14 20:25:38 +10:00
477a6859d7 Update README.md 2020-09-14 17:07:52 +10:00
f8f9671746 Update README.md 2020-09-14 16:56:42 +10:00
dc2759c599 1.7.2
unstrip fixes
2020-09-14 01:42:29 +10:00
653b4a2304 Fix BeginVertical and BeginHorizontal 2020-09-13 23:16:12 +10:00
065ab033c9 Fix crash when inspecting RigidBody2D objects 2020-09-13 17:39:15 +10:00
11cbd24a6a 1.7.1 2020-09-13 17:29:01 +10:00
fbf9859e0f Fix for GUILayout.BeginArea unstrip and UnityEngine.SystemClock unstrip
and remove some old comments
2020-09-13 17:11:15 +10:00
2d7dfa53eb Fix scroll not working in 2017 games 2020-09-12 16:14:42 +10:00
eff8d63c81 Update README.md 2020-09-12 04:03:53 +10:00
006cf0fc2c Create icon.png 2020-09-12 04:03:30 +10:00
e5ca3530ff Merge branch 'master' of https://github.com/sinai-dev/CppExplorer 2020-09-12 04:01:18 +10:00
4de378907b Update GameObjectWindow.cs 2020-09-12 02:52:52 +10:00
bbdfb46a1e Update README.md 2020-09-12 02:35:41 +10:00
e6e2b3cd67 Update README.md 2020-09-12 02:30:34 +10:00
1d07046a74 Update README.md, hide Console when it fails to init 2020-09-12 02:20:27 +10:00
de663f34b2 1.7.0
* Fix for GuiLayout.Space unstrip
* Cleanups
2020-09-12 00:59:59 +10:00
8d648fec43 1.6.9
* Fix for games where patching Cursor methods fails.
* Added backup attempt for loading Cursor module if not present.
* HashSet collections are now supported by CacheList
* try/catch for loading Mod Config
2020-09-11 18:53:17 +10:00
835a81765e Add support for Hashset, add try/catch for loading settings 2020-09-11 00:17:13 +10:00
51ed936e30 Revert PR changes 2020-09-10 22:40:23 +10:00
ac16587cdc Merge pull request #11 from PsymoNBond/master
Fixed Rect init
2020-09-10 22:38:16 +10:00
1e1eaa6c38 Fixed Rect init 2020-09-10 14:16:25 +03:00
af17ae82c6 Update README.md 2020-09-10 20:47:13 +10:00
e23341c2b1 Update README.md 2020-09-10 20:36:24 +10:00
080bde4a63 Update README.md 2020-09-10 20:35:41 +10:00
1d739a1936 1.6.8
* Added a ModConfig, allowing you to define the main menu toggle keybind and the default window size (so far).
* Made the parsing of arguments more intelligent, should now behave as expected for null or empty arguments.
2020-09-10 20:31:09 +10:00
a927b5ed21 1.6.7
* Parameters (in Methods or Properties) with default values will now show these default values in the Inspector, and if you don't provide any input then this default value will be used as the argument.
* Removed an unnecessary update of cached members when you open a Reflection Inspector, should be a bit faster now.
* When entering arguments, the name of the argument is now white instead of cyan to avoid confusion with the Type name.
* A few clean ups
2020-09-10 18:02:41 +10:00
642c97812c fix img 2020-09-09 19:20:52 +10:00
e43d3579de Update img.png 2020-09-09 19:18:39 +10:00
6ea435deee 1.6.6
* Added better support for Properties with index parameters, can now support multiple parameters and non-int parameters.
* Parameters are now formatted in a more expected fashion (in the `(Type arg0, Type arg1)` format).
* Got rid of all the ugly yellow text.
* Cleaned up some minor GUI display / layout issues.
* Refactored some of CacheMethod into CacheObjectBase
2020-09-09 19:15:47 +10:00
94f749342d Update .gitignore 2020-09-09 00:53:31 +10:00
44 changed files with 1328 additions and 695 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@
*.user
*.userosscache
*.sln.docstates
*/mcs-unity*
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

View File

@ -1,7 +1,7 @@
# CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.7.1-green.svg)](https://github.com/HerpDerpinstine/MelonLoader)
<p align="center">
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
<img align="center" src="icon.png">
</p>
<p align="center">
@ -13,37 +13,62 @@
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
</p>
<p align="center">
<a href="https://github.com/sinai-dev/MonoExplorer">Looking for a Mono version?</a>
</p>
### Known issues
* CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
* 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.
- [Known issues](#known-issues)
- [How to install](#how-to-install)
- [How to use](#how-to-use)
- [Mod Config](#mod-config)
- [Features](#features)
- [Mouse Control](#mouse-control)
- [Building](#building)
- [Credits](#credits)
## Known issues
As of version 1.7+, CppExplorer has reached a fairly stable state for most Il2Cpp games.
* .NET 3.5 is not currently supported (Unity 5.6.1 and older), this might change in the future.
* Some methods may still fail with a `MissingMethodException`, please let me know if you experience this (with full MelonLoader log please).
* Reflection may fail with certain types, see [here](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.
## How to install
Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
1. Download <b>CppExplorer.zip</b> from [Releases](https://github.com/sinaioutlander/CppExplorer/releases).
1. Download <b>CppExplorer.zip</b> from [Releases](https://github.com/sinai-dev/CppExplorer/releases).
2. Unzip the file into the `Mods` folder in your game's installation directory, created by MelonLoader.
3. Make sure it's not in a sub-folder, `CppExplorer.dll` and `mcs.dll` should be directly in the `Mods\` folder.
## How to use
* Press F7 to show or hide the menu.
* Simply browse through the scene, search for objects, etc, most of it is pretty self-explanatory.
* Use the Scene Explorer or the Object Search to start Exploring, or the C# Console to test some code.
* See below for more specific details.
### Features
[![](img.png)](img.png)
### Mod Config
There is a simple Mod Config for the CppExplorer, which is generated the first time you run it.
This config is generated to `Mods\CppExplorer\config.xml`. Edit the config while the game is closed if you wish to change it.
`Main_Menu_Toggle` (KeyCode)
* Sets the keybinding for the Main Menu toggle (show/hide all CppExplorer windows)
* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes.
* Default: `F7`
`Default_Window_Size` (Vector2)
* Sets the default width and height for all CppExplorer windows when created.
* `x` is width, `y` is height.
* Default: `<x>550</x> <y>700</y>`
## Features
[![](overview.png)](overview.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.
@ -92,17 +117,33 @@ 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.
For example:
```csharp
using Explorer;
using Harmony;
// ...
[HarmonyPatch(typeof(MyGame.MenuClass), nameof(MyGame.MenuClass.CursorUpdate)]
public class MenuClass_CursorUpdate
{
[HarmonyPrefix]
public static bool Prefix()
{
// prevent method running if menu open, let it run if not.
return !CppExplorer.ShowMenu;
}
}
```
## 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.
2. Open the `src\CppExplorer.csproj` file in a text editor.
3. Scroll down until you see the `<ItemGroup>` containing the References.
4. Fix all of the paths in the `..\Steam\` directory for your game (use the full path if you need to).
5. Open the `src\CppExplorer.sln` project and build it.
6. The dll is built to the `Release\` folder in the root of the repository.
## Credits
@ -110,4 +151,4 @@ Written by Sinai.
Thanks to:
* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the UI style.
* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor in `mcs.dll` since it was causing an exception with the Hook it attempted.
* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor since it was causing an exception with the Hook it attempted.

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
img.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 KiB

BIN
lib/mcs.NET35.dll Normal file

Binary file not shown.

BIN
overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

View File

@ -19,8 +19,12 @@ namespace Explorer
public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public int PropertyIndex { get; private set; }
private string m_propertyIndexInput = "0";
public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
public bool m_evaluated = false;
public bool m_isEvaluating;
public ParameterInfo[] m_arguments = new ParameterInfo[0];
public string[] m_argumentInput = new string[0];
public string ReflectionException { get; set; }
@ -40,14 +44,10 @@ namespace Explorer
}
}
// ===== Abstract/Virtual Methods ===== //
public virtual void Init() { }
public abstract void DrawValue(Rect window, float width);
// ===== Static Methods ===== //
/// <summary>
/// Get CacheObject from only an object instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
@ -114,12 +114,21 @@ namespace Explorer
{
CacheObjectBase holder;
var pi = memberInfo as PropertyInfo;
var mi = memberInfo as MethodInfo;
// if PropertyInfo, check if can process args
if (pi != null && !CanProcessArgs(pi.GetIndexParameters()))
{
return null;
}
// 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 (mi != null)
{
if (CacheMethod.CanEvaluate(mi))
{
@ -163,7 +172,7 @@ namespace Explorer
{
holder = new CacheDictionary();
}
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType))
else if (ReflectionHelpers.IsEnumerable(valueType))
{
holder = new CacheList();
}
@ -181,24 +190,112 @@ namespace Explorer
holder.MemInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType;
holder.DeclaringInstance = declaringInstance;
}
if (pi != null)
{
holder.m_arguments = pi.GetIndexParameters();
}
else if (mi != null)
{
holder.m_arguments = mi.GetParameters();
}
holder.m_argumentInput = new string[holder.m_arguments.Length];
holder.UpdateValue();
}
holder.Init();
return holder;
}
// ======== Instance Methods =========
public static bool CanProcessArgs(ParameterInfo[] parameters)
{
foreach (var param in parameters)
{
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
{
return false;
}
}
return true;
}
public float CalcWhitespace(Rect window)
{
if (!(this is IExpandHeight)) return 0f;
float whitespace = (this as IExpandHeight).WhiteSpace;
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
return whitespace;
}
public object[] ParseArguments()
{
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 (!string.IsNullOrEmpty(input))
{
// strings can obviously just be used directly
if (type == typeof(string))
{
parsedArgs.Add(input);
continue;
}
else
{
// try to invoke the parse method and use that.
try
{
parsedArgs.Add(type.GetMethod("Parse", new Type[] { typeof(string) })
.Invoke(null, new object[] { input }));
continue;
}
catch
{
MelonLogger.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'.");
}
}
}
// Didn't use input, see if there is a default value.
if (m_arguments[i].HasDefaultValue)
{
parsedArgs.Add(m_arguments[i].DefaultValue);
continue;
}
// Try add a null arg I guess
parsedArgs.Add(null);
}
return parsedArgs.ToArray();
}
public virtual void UpdateValue()
{
if (MemInfo == null || !string.IsNullOrEmpty(ReflectionException))
if (MemInfo == null)
{
return;
}
if (HasParameters && !m_isEvaluating)
{
// Need to enter parameters first
return;
}
try
{
if (MemInfo.MemberType == MemberTypes.Field)
@ -209,13 +306,11 @@ namespace Explorer
else if (MemInfo.MemberType == MemberTypes.Property)
{
var pi = MemInfo as PropertyInfo;
bool isStatic = pi.GetAccessors()[0].IsStatic;
var target = isStatic ? null : DeclaringInstance;
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
if (pi.GetIndexParameters().Length > 0)
if (HasParameters)
{
var indexes = new object[] { PropertyIndex };
Value = pi.GetValue(target, indexes);
Value = pi.GetValue(target, ParseArguments());
}
else
{
@ -224,6 +319,8 @@ namespace Explorer
}
ReflectionException = null;
m_evaluated = true;
m_isEvaluating = false;
}
catch (Exception e)
{
@ -244,10 +341,9 @@ namespace Explorer
{
var pi = MemInfo as PropertyInfo;
if (pi.GetIndexParameters().Length > 0)
if (HasParameters)
{
var indexes = new object[] { PropertyIndex };
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, indexes);
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, ParseArguments());
}
else
{
@ -261,9 +357,10 @@ namespace Explorer
}
}
// ========= Instance Gui Draw ==========
// ========= Gui Draw ==========
public const float MAX_LABEL_WIDTH = 400f;
public const string EVALUATE_LABEL = "<color=lime>Evaluate</color>";
public static void ClampLabelWidth(Rect window, ref float labelWidth)
{
@ -282,53 +379,104 @@ namespace Explorer
if (MemInfo != null)
{
var name = RichTextName;
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
{
name += $"[{PropertyIndex}]";
}
GUILayout.Label(name, new GUILayoutOption[] { GUILayout.Width(labelWidth) });
GUILayout.Label(RichTextName, new GUILayoutOption[] { GUILayout.Width(labelWidth) });
}
else
{
GUILayout.Space(labelWidth);
GUIUnstrip.Space(labelWidth);
}
var cm = this as CacheMethod;
if (HasParameters)
{
GUILayout.BeginVertical(null);
if (m_isEvaluating)
{
for (int i = 0; i < m_arguments.Length; i++)
{
var name = m_arguments[i].Name;
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType.Name;
var label = "<color=#2df7b2>" + type + "</color> <color=#a6e9e9>" + name + "</color>";
if (m_arguments[i].HasDefaultValue)
{
label = $"<i>[{label} = {m_arguments[i].DefaultValue}]</i>";
}
GUILayout.BeginHorizontal(null);
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(20) });
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUILayout.Label(label, null);
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(null);
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
{
if (cm != null)
{
cm.Evaluate();
}
else
{
UpdateValue();
}
}
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
{
m_isEvaluating = false;
}
GUILayout.EndHorizontal();
}
else
{
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
{
m_isEvaluating = true;
}
}
GUILayout.EndVertical();
// new line and space
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUIUnstrip.Space(labelWidth);
}
else if (cm != null)
{
//GUILayout.BeginHorizontal(null);
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
{
cm.Evaluate();
}
// new line and space
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUIUnstrip.Space(labelWidth);
}
if (!string.IsNullOrEmpty(ReflectionException))
{
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
}
else if (Value == null && MemInfo?.MemberType != MemberTypes.Method)
else if ((HasParameters || this is CacheMethod) && !m_evaluated)
{
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=#2df7b2>{ValueTypeName}</color>)", null);
}
else if (Value == null && !(this is CacheMethod))
{
GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
}
else
{
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
{
GUILayout.Label("index:", new GUILayoutOption[] { GUILayout.Width(50) });
m_propertyIndexInput = GUILayout.TextField(m_propertyIndexInput, new GUILayoutOption[] { GUILayout.Width(100) });
if (GUILayout.Button("Set", new GUILayoutOption[] { GUILayout.Width(60) }))
{
if (int.TryParse(m_propertyIndexInput, out int i))
{
PropertyIndex = i;
UpdateValue();
}
else
{
MelonLogger.Log($"Could not parse '{m_propertyIndexInput}' to an int!");
}
}
// new line and space
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(labelWidth);
}
DrawValue(window, window.width - labelWidth - 90);
}
}
@ -348,15 +496,15 @@ namespace Explorer
m_richTextName = $"<color=#2df7b2>{MemInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemInfo.Name}</color>";
if (MemInfo is MethodInfo mi)
if (m_arguments.Length > 0 || this is CacheMethod)
{
m_richTextName += "(";
var _params = "";
foreach (var param in mi.GetParameters())
foreach (var param in m_arguments)
{
if (_params != "") _params += ", ";
_params += $"<color=#a6e9e9>{param.Name}</color>";
_params += $"<color=#2df7b2>{param.ParameterType.Name}</color> <color=#a6e9e9>{param.Name}</color>";
}
m_richTextName += _params;
m_richTextName += ")";

View File

@ -9,8 +9,6 @@ namespace Explorer
interface IExpandHeight
{
bool IsExpanded { get; set; }
float WhiteSpace { get; set; }
float ButtonWidthOffset { get; set; }
}
}

View File

@ -15,7 +15,6 @@ namespace Explorer
{
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public float ButtonWidthOffset { get; set; } = 290f;
public PageHelper Pages = new PageHelper();
@ -99,33 +98,16 @@ namespace Explorer
private void GetGenericArguments()
{
if (this.MemInfo != null)
if (ValueType.IsGenericType)
{
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;
m_keysType = ValueType.GetGenericArguments()[0];
m_valuesType = ValueType.GetGenericArguments()[1];
}
if (memberType != null && memberType.IsGenericType)
else
{
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];
}
// It's non-generic, just use System.Object to allow for anything.
m_keysType = typeof(object);
m_valuesType = typeof(object);
}
}
@ -152,7 +134,6 @@ namespace Explorer
foreach (var key in IDict.Keys)
{
var cache = GetCacheObject(key, TypeOfKeys);
cache.UpdateValue();
keys.Add(cache);
}
@ -160,7 +141,6 @@ namespace Explorer
foreach (var val in IDict.Values)
{
var cache = GetCacheObject(val, TypeOfValues);
cache.UpdateValue();
values.Add(cache);
}
@ -170,6 +150,11 @@ namespace Explorer
private bool EnsureDictionaryIsSupported()
{
if (typeof(IDictionary).IsAssignableFrom(ValueType))
{
return true;
}
try
{
return Check(TypeOfKeys) && Check(TypeOfValues);
@ -200,6 +185,8 @@ namespace Explorer
return;
}
var whitespace = CalcWhitespace(window);
int count = m_cachedKeys.Length;
if (!IsExpanded)
@ -217,24 +204,20 @@ namespace Explorer
}
}
var negativeWhitespace = window.width - (whitespace + 100f);
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) }))
string btnLabel = $"[{count}] <color=#2df7b2>Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUILayout.Space(5);
GUIUnstrip.Space(5);
if (IsExpanded)
{
float whitespace = WhiteSpace;
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
@ -242,7 +225,7 @@ namespace Explorer
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
Pages.CurrentPageLabel();
@ -258,7 +241,7 @@ namespace Explorer
Pages.DrawLimitInputArea();
GUILayout.Space(5);
GUIUnstrip.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
@ -272,7 +255,7 @@ namespace Explorer
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
//GUILayout.Space(whitespace);
//GUIUnstrip.Space(whitespace);
if (key == null || val == null)
{
@ -284,10 +267,10 @@ namespace Explorer
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
key.DrawValue(window, (window.width / 2) - 30f);
key.DrawValue(window, (window.width / 2) - 80f);
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
val.DrawValue(window, (window.width / 2) - 30f);
val.DrawValue(window, (window.width / 2) - 80f);
}
}

View File

@ -12,7 +12,6 @@ namespace Explorer
{
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public float ButtonWidthOffset { get; set; } = 290f;
public PageHelper Pages = new PageHelper();
@ -41,7 +40,7 @@ namespace Explorer
private Type m_genericTypeDef;
// Cached ToArray method for Lists
public MethodInfo GenericToArrayMethod
public MethodInfo CppListToArrayMethod
{
get => GetGenericToArrayMethod();
}
@ -61,7 +60,7 @@ namespace Explorer
{
if (m_enumerable == null && Value != null)
{
m_enumerable = Value as IEnumerable ?? GetEnumerableFromIl2CppList();
m_enumerable = Value as IEnumerable ?? EnumerateWithReflection();
}
return m_enumerable;
}
@ -101,21 +100,45 @@ namespace Explorer
return m_itemProperty;
}
private IEnumerable GetEnumerableFromIl2CppList()
private IEnumerable EnumerateWithReflection()
{
if (Value == null) return null;
if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>))
{
return (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]);
return (IEnumerable)CppListToArrayMethod?.Invoke(Value, new object[0]);
}
else if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
{
return CppHashSetToMono();
}
else
{
return ConvertIListToMono();
return CppIListToMono();
}
}
private IList ConvertIListToMono()
private IEnumerable CppHashSetToMono()
{
var set = new HashSet<object>();
// invoke GetEnumerator
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, 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))
{
set.Add(current.GetValue(enumerator));
}
return set;
}
private IList CppIListToMono()
{
try
{
@ -219,7 +242,6 @@ namespace Explorer
if (GetCacheObject(obj, t) is CacheObjectBase cached)
{
cached.UpdateValue();
list.Add(cached);
}
else
@ -246,6 +268,8 @@ namespace Explorer
return;
}
var whitespace = CalcWhitespace(window);
int count = m_cachedEntries.Length;
if (!IsExpanded)
@ -263,24 +287,20 @@ namespace Explorer
}
}
var negativeWhitespace = window.width - (whitespace + 100f);
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = "<color=yellow>[" + count + "] " + EntryType.FullName + "</color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
string btnLabel = $"[{count}] <color=#2df7b2>{EntryType.FullName}</color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(negativeWhitespace) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUILayout.Space(5);
GUIUnstrip.Space(5);
if (IsExpanded)
{
float whitespace = WhiteSpace;
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
@ -288,7 +308,7 @@ namespace Explorer
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
Pages.CurrentPageLabel();
@ -304,7 +324,7 @@ namespace Explorer
Pages.DrawLimitInputArea();
GUILayout.Space(5);
GUIUnstrip.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
@ -317,7 +337,7 @@ namespace Explorer
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
if (entry == null || entry.Value == null)
{

View File

@ -11,15 +11,8 @@ namespace Explorer
{
public class CacheMethod : CacheObjectBase
{
private bool m_evaluated = false;
private CacheObjectBase m_cachedReturnValue;
private bool m_isEvaluating;
private ParameterInfo[] m_arguments;
private string[] m_argumentInput;
public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
public static bool CanEvaluate(MethodInfo mi)
{
// TODO generic args
@ -29,25 +22,7 @@ namespace Explorer
}
// primitive and string args supported
foreach (var param in mi.GetParameters())
{
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
{
return false;
}
}
return true;
}
public override void Init()
{
base.Init();
var mi = MemInfo as MethodInfo;
m_arguments = mi.GetParameters();
m_argumentInput = new string[m_arguments.Length];
return CanProcessArgs(mi.GetParameters());
}
public override void UpdateValue()
@ -55,10 +30,11 @@ namespace Explorer
//base.UpdateValue();
}
private void Evaluate()
public void Evaluate()
{
var mi = MemInfo as MethodInfo;
m_isEvaluating = false;
var mi = MemInfo as MethodInfo;
object ret = null;
if (!HasParameters)
@ -67,43 +43,10 @@ namespace Explorer
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());
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
m_evaluated = true;
}
catch (Exception e)
@ -115,13 +58,6 @@ namespace Explorer
if (ret != null)
{
m_cachedReturnValue = GetCacheObject(ret);
if (m_cachedReturnValue is IExpandHeight expander)
{
expander.WhiteSpace = 0f;
expander.ButtonWidthOffset += 70f;
}
m_cachedReturnValue.UpdateValue();
}
else
@ -134,58 +70,6 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
GUILayout.BeginVertical(null);
string evaluateLabel = "<color=lime>Evaluate</color>";
if (HasParameters)
{
if (m_isEvaluating)
{
for (int i = 0; i < m_arguments.Length; i++)
{
var name = m_arguments[i].Name;
var input = m_argumentInput[i];
var type = m_arguments[i].ParameterType.Name;
GUILayout.BeginHorizontal(null);
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUILayout.Label(i + ": <color=cyan>" + name + "</color> <color=yellow>(" + type + ")</color>", null);
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(null);
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
{
Evaluate();
m_isEvaluating = false;
}
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
{
m_isEvaluating = false;
}
}
else
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
{
m_isEvaluating = true;
}
}
}
else
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
{
Evaluate();
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (m_evaluated)
{
if (m_cachedReturnValue != null)
@ -194,16 +78,13 @@ namespace Explorer
}
else
{
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
GUILayout.Label($"null (<color=#2df7b2>{ValueTypeName}</color>)", null);
}
}
else
{
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueTypeName}</color>)", null);
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=#2df7b2>{ValueTypeName}</color>)", null);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
}
}

View File

@ -42,15 +42,20 @@ namespace Explorer
if (!label.Contains(ValueTypeName))
{
label += $" ({ValueTypeName})";
label += $" (<color=#2df7b2>{ValueTypeName}</color>)";
}
else
{
label = label.Replace(ValueTypeName, $"<color=#2df7b2>{ValueTypeName}</color>");
}
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
{
label = unityObj.name + " | " + label;
}
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width - 15) }))
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width - 15) }))
{
WindowManager.InspectObject(Value, out bool _);
}

View File

@ -7,15 +7,16 @@ using UnityEngine;
namespace Explorer
{
public class CacheColor : CacheObjectBase
public class CacheColor : CacheObjectBase, IExpandHeight
{
private bool IsExpanded;
private string r = "0";
private string g = "0";
private string b = "0";
private string a = "0";
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public override void UpdateValue()
{
base.UpdateValue();
@ -48,43 +49,44 @@ namespace Explorer
}
}
var c = (Color)Value;
GUI.color = c;
GUILayout.Label($"<color=yellow>Color:</color> {c.ToString()}", null);
GUI.color = Color.white;
//var c = (Color)Value;
//GUI.color = c;
GUILayout.Label($"<color=#2df7b2>Color:</color> {((Color)Value).ToString()}", null);
//GUI.color = Color.white;
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
var whitespace = CalcWhitespace(window);
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();

View File

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

View File

@ -69,10 +69,10 @@ namespace Explorer
else
{
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
GUILayout.Label("<color=yellow><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f;
var maxwidth = window.width - 310f;
if (CanWrite) maxwidth -= 60;
if (dynSize > maxwidth)
@ -92,7 +92,7 @@ namespace Explorer
}
}
GUILayout.Space(5);
GUIUnstrip.Space(10);
}
}

View File

@ -7,14 +7,15 @@ using UnityEngine;
namespace Explorer
{
public class CacheQuaternion : CacheObjectBase
public class CacheQuaternion : CacheObjectBase, IExpandHeight
{
private bool IsExpanded;
private string x = "0";
private string y = "0";
private string z = "0";
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public override void UpdateValue()
{
base.UpdateValue();
@ -46,34 +47,35 @@ namespace Explorer
}
}
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
GUILayout.Label($"<color=#2df7b2>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
var whitespace = CalcWhitespace(window);
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();

View File

@ -7,15 +7,16 @@ using UnityEngine;
namespace Explorer
{
public class CacheRect : CacheObjectBase
public class CacheRect : CacheObjectBase, IExpandHeight
{
private bool IsExpanded;
private string x = "0";
private string y = "0";
private string w = "0";
private string h = "0";
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public override void UpdateValue()
{
base.UpdateValue();
@ -48,40 +49,41 @@ namespace Explorer
}
}
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
GUILayout.Label($"<color=#2df7b2>Rect</color>: {((Rect)Value).ToString()}", null);
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
var whitespace = CalcWhitespace(window);
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.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);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();

View File

@ -8,10 +8,8 @@ using UnityEngine;
namespace Explorer
{
public class CacheVector : CacheObjectBase
public class CacheVector : CacheObjectBase, IExpandHeight
{
private bool IsExpanded;
public int VectorSize = 2;
private string x = "0";
@ -21,6 +19,9 @@ namespace Explorer
private MethodInfo m_toStringMethod;
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public override void Init()
{
if (Value is Vector2)
@ -83,22 +84,23 @@ namespace Explorer
}
}
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
GUILayout.Label($"<color=#2df7b2>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
var whitespace = CalcWhitespace(window);
// always draw x and y
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.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);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
@ -107,7 +109,7 @@ namespace Explorer
{
// draw z
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
@ -116,7 +118,7 @@ namespace Explorer
{
// draw w
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
@ -124,7 +126,7 @@ namespace Explorer
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();

69
src/Config/ModConfig.cs Normal file
View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using UnityEngine;
namespace Explorer
{
public class ModConfig
{
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig));
[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\CppExplorer";
[XmlIgnore] private const string SETTINGS_PATH = EXPLORER_FOLDER + @"\config.xml";
[XmlIgnore] public static ModConfig Instance;
public KeyCode Main_Menu_Toggle = KeyCode.F7;
public Vector2 Default_Window_Size = new Vector2(550, 700);
public static void OnLoad()
{
if (!Directory.Exists(EXPLORER_FOLDER))
{
Directory.CreateDirectory(EXPLORER_FOLDER);
}
if (LoadSettings()) return;
Instance = new ModConfig();
SaveSettings();
}
// returns true if settings successfully loaded
public static bool LoadSettings(bool checkExist = true)
{
if (checkExist && !File.Exists(SETTINGS_PATH))
return false;
try
{
using (var file = File.OpenRead(SETTINGS_PATH))
{
Instance = (ModConfig)Serializer.Deserialize(file);
}
}
catch
{
return false;
}
return Instance != null;
}
public static void SaveSettings(bool checkExist = true)
{
if (checkExist && File.Exists(SETTINGS_PATH))
File.Delete(SETTINGS_PATH);
using (var file = File.Create(SETTINGS_PATH))
{
Serializer.Serialize(file, Instance);
}
}
}
}

View File

@ -13,7 +13,7 @@ namespace Explorer
public class CppExplorer : MelonMod
{
public const string NAME = "CppExplorer";
public const string VERSION = "1.6.5";
public const string VERSION = "1.7.31";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.cppexplorer";
@ -24,45 +24,26 @@ namespace Explorer
get => m_showMenu;
set => SetShowMenu(value);
}
private static bool m_showMenu;
public static bool ForceUnlockMouse
{
get => m_forceUnlock;
set => SetForceUnlock(value);
}
private static bool m_forceUnlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
public static bool ShouldForceMouse => ShowMenu && ForceUnlockMouse;
public static bool m_showMenu;
private static void SetShowMenu(bool show)
{
m_showMenu = show;
UpdateCursorControl();
CursorControl.UpdateCursorControl();
}
// ========== MonoBehaviour methods ==========
public override void OnApplicationStart()
{
Instance = this;
ModConfig.OnLoad();
InputHelper.Init();
new MainMenu();
new WindowManager();
// Get current cursor state and enable cursor
m_lastLockMode = Cursor.lockState;
m_lastVisibleState = Cursor.visible;
// Enable ShowMenu and ForceUnlockMouse
// (set m_showMenu to not call UpdateCursorState twice)
m_showMenu = true;
SetForceUnlock(true);
CursorControl.Init();
MelonLogger.Log($"CppExplorer {VERSION} initialized.");
}
@ -75,23 +56,18 @@ namespace Explorer
public override void OnUpdate()
{
// Check main toggle key input
if (InputHelper.GetKeyDown(KeyCode.F7))
if (InputHelper.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
{
ShowMenu = !ShowMenu;
}
if (ShowMenu)
{
// Check Force-Unlock input
if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
{
ForceUnlockMouse = !ForceUnlockMouse;
}
CursorControl.Update();
InspectUnderMouse.Update();
MainMenu.Instance.Update();
WindowManager.Instance.Update();
InspectUnderMouse.Update();
}
}
@ -99,101 +75,14 @@ namespace Explorer
{
if (!ShowMenu) return;
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
MainMenu.Instance.OnGUI();
WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI();
}
// =========== Cursor control ===========
private static void SetForceUnlock(bool unlock)
{
m_forceUnlock = unlock;
UpdateCursorControl();
}
private static void UpdateCursorControl()
{
m_currentlySettingCursor = true;
if (ShouldForceMouse)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else
{
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
}
m_currentlySettingCursor = false;
}
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
// value that we set back to when we close the menu or disable force-unlock.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)]
public class Cursor_set_lockState
{
[HarmonyPrefix]
public static void Prefix(ref CursorLockMode value)
{
if (!m_currentlySettingCursor)
{
m_lastLockMode = value;
if (ShouldForceMouse)
{
value = CursorLockMode.None;
}
}
}
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Setter)]
public class Cursor_set_visible
{
[HarmonyPrefix]
public static void Prefix(ref bool value)
{
if (!m_currentlySettingCursor)
{
m_lastVisibleState = value;
if (ShouldForceMouse)
{
value = true;
}
}
}
}
// Make it appear as though UnlockMouse is disabled to the rest of the application.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Getter)]
public class Cursor_get_lockState
{
[HarmonyPostfix]
public static void Postfix(ref CursorLockMode __result)
{
if (ShouldForceMouse)
{
__result = m_lastLockMode;
}
}
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Getter)]
public class Cursor_get_visible
{
[HarmonyPostfix]
public static void Postfix(ref bool __result)
{
if (ShouldForceMouse)
{
__result = m_lastVisibleState;
}
}
GUI.skin = origSkin;
}
}
}

View File

@ -29,6 +29,10 @@
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="mcs">
<HintPath>..\lib\mcs.dll</HintPath>
<Private>True</Private>
@ -73,7 +77,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\IExpandHeight.cs" />
<Compile Include="CachedObjects\IExpandHeight.cs" />
<Compile Include="CachedObjects\Struct\CacheColor.cs" />
<Compile Include="CachedObjects\Object\CacheDictionary.cs" />
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
@ -85,34 +89,38 @@
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
<Compile Include="Config\ModConfig.cs" />
<Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="Helpers\InputHelper.cs" />
<Compile Include="Menu\CursorControl.cs" />
<Compile Include="Tests\TestClass.cs" />
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
<Compile Include="UnstripFixes\LayoutUtilityUnstrip.cs" />
<Compile Include="UnstripFixes\ScrollViewStateUnstrip.cs" />
<Compile Include="Extensions\UnityExtensions.cs" />
<Compile Include="Helpers\PageHelper.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" />
<Compile Include="Helpers\UIHelpers.cs" />
<Compile Include="Menu\UIHelpers.cs" />
<Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="MainMenu\InspectUnderMouse.cs" />
<Compile Include="Menu\InspectUnderMouse.cs" />
<Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="UnstripFixes\SliderHandlerUnstrip.cs" />
<Compile Include="UnstripFixes\UnstripExtensions.cs" />
<Compile Include="Windows\ResizeDrag.cs" />
<Compile Include="Windows\TabViewWindow.cs" />
<Compile Include="Windows\UIWindow.cs" />
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
<Compile Include="MainMenu\Pages\Console\REPLHelper.cs" />
<Compile Include="MainMenu\Pages\WindowPage.cs" />
<Compile Include="Windows\WindowManager.cs" />
<Compile Include="MainMenu\MainMenu.cs" />
<Compile Include="Windows\GameObjectWindow.cs" />
<Compile Include="Windows\ReflectionWindow.cs" />
<Compile Include="MainMenu\Pages\ScenePage.cs" />
<Compile Include="MainMenu\Pages\SearchPage.cs" />
<Compile Include="Helpers\UIStyles.cs" />
<Compile Include="Menu\ResizeDrag.cs" />
<Compile Include="Menu\Windows\TabViewWindow.cs" />
<Compile Include="Menu\Windows\UIWindow.cs" />
<Compile Include="Menu\MainMenu\Pages\ConsolePage.cs" />
<Compile Include="Menu\MainMenu\Pages\Console\REPL.cs" />
<Compile Include="Menu\MainMenu\Pages\Console\REPLHelper.cs" />
<Compile Include="Menu\MainMenu\Pages\WindowPage.cs" />
<Compile Include="Menu\Windows\WindowManager.cs" />
<Compile Include="Menu\MainMenu\MainMenu.cs" />
<Compile Include="Menu\Windows\GameObjectWindow.cs" />
<Compile Include="Menu\Windows\ReflectionWindow.cs" />
<Compile Include="Menu\MainMenu\Pages\ScenePage.cs" />
<Compile Include="Menu\MainMenu\Pages\SearchPage.cs" />
<Compile Include="Menu\UIStyles.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@ -88,7 +88,8 @@ namespace Explorer
{
MelonLogger.Log("UnityEngine.Input is null, trying to load manually....");
if ((TryLoad("UnityEngine.InputLegacyModule.dll") || TryLoad("UnityEngine.CoreModule.dll")) && Input != null)
if ((ReflectionHelpers.LoadModule("UnityEngine.InputLegacyModule.dll") || ReflectionHelpers.LoadModule("UnityEngine.CoreModule.dll"))
&& Input != null)
{
MelonLogger.Log("Ok!");
return true;
@ -98,23 +99,6 @@ namespace Explorer
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

@ -3,6 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.IO;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
@ -34,16 +36,17 @@ namespace Explorer
public static bool IsEnumerable(Type t)
{
return typeof(IEnumerable).IsAssignableFrom(t);
return typeof(IEnumerable).IsAssignableFrom(t) || IsCppEnumerable(t);
}
// Only Il2Cpp List needs this check. C# List is IEnumerable.
public static bool IsCppList(Type t)
// Checks for Il2Cpp List or HashSet.
public static bool IsCppEnumerable(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);
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g);
}
else
{
@ -65,7 +68,8 @@ namespace Explorer
}
else
{
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t);
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t)
|| typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(t);
}
}
@ -89,18 +93,19 @@ namespace Explorer
{
if (obj == null) return null;
// Need to use GetIl2CppType for Il2CppSystem Objects
if (obj is Il2CppSystem.Object ilObject)
{
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
// Prevent weird behaviour when inspecting an Il2CppSystem.Type object.
if (ilObject is ILType)
{
return t;
return typeof(ILType);
}
return ilObject.GetType();
return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType();
}
// It's a normal object, this is fine
return obj.GetType();
}
@ -109,17 +114,33 @@ namespace Explorer
var list = new List<Type>();
var type = GetActualType(obj);
list.Add(type);
while (type.BaseType != null)
while (type != null)
{
type = type.BaseType;
list.Add(type);
type = type.BaseType;
}
return list.ToArray();
}
public static bool LoadModule(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 string ExceptionToString(Exception e)
{
if (IsFailedGeneric(e))

183
src/Menu/CursorControl.cs Normal file
View File

@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Harmony;
using MelonLoader;
using System.Reflection;
namespace Explorer
{
public class CursorControl
{
public static bool ForceUnlockMouse
{
get => m_forceUnlock;
set => SetForceUnlock(value);
}
private static bool m_forceUnlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
public static bool ShouldForceMouse => CppExplorer.ShowMenu && ForceUnlockMouse;
private static Type CursorType => m_cursorType ?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor"));
private static Type m_cursorType;
public static void Init()
{
try
{
// Check if Cursor class is loaded
if (CursorType == null)
{
MelonLogger.Log("Trying to manually load Cursor module...");
if (ReflectionHelpers.LoadModule("UnityEngine.CoreModule") && CursorType != null)
{
MelonLogger.Log("Ok!");
}
else
{
throw new Exception("Could not load UnityEngine.Cursor module!");
}
}
// Get current cursor state and enable cursor
m_lastLockMode = Cursor.lockState;
m_lastVisibleState = Cursor.visible;
// Setup Harmony Patches
TryPatch("lockState", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Prefix_set_lockState))), true);
TryPatch("lockState", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Postfix_get_lockState))), false);
TryPatch("visible", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Prefix_set_visible))), true);
TryPatch("visible", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Postfix_get_visible))), false);
}
catch (Exception e)
{
MelonLogger.Log($"Exception on CursorControl.Init! {e.GetType()}, {e.Message}");
}
// Enable ShowMenu and ForceUnlockMouse
// (set m_showMenu directly to not call UpdateCursorState twice)
CppExplorer.m_showMenu = true;
ForceUnlockMouse = true;
}
private static void TryPatch(string property, HarmonyMethod patch, bool setter)
{
try
{
var harmony = CppExplorer.Instance.harmonyInstance;
var prop = typeof(Cursor).GetProperty(property);
if (setter)
{
// setter is prefix
harmony.Patch(prop.GetSetMethod(), patch);
}
else
{
// getter is postfix
harmony.Patch(prop.GetGetMethod(), null, patch);
}
}
catch (Exception e)
{
MelonLogger.Log($"[NON-FATAL] Couldn't patch a method: {e.Message}");
}
}
private static void SetForceUnlock(bool unlock)
{
m_forceUnlock = unlock;
UpdateCursorControl();
}
public static void Update()
{
// Check Force-Unlock input
if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
{
ForceUnlockMouse = !ForceUnlockMouse;
}
}
public static void UpdateCursorControl()
{
try
{
m_currentlySettingCursor = true;
if (ShouldForceMouse)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
}
else
{
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
}
m_currentlySettingCursor = false;
}
catch (Exception e)
{
MelonLogger.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
}
}
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
// value that we set back to when we close the menu or disable force-unlock.
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
if (!m_currentlySettingCursor)
{
m_lastLockMode = value;
if (ShouldForceMouse)
{
value = CursorLockMode.None;
}
}
}
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
if (!m_currentlySettingCursor)
{
m_lastVisibleState = value;
if (ShouldForceMouse)
{
value = true;
}
}
}
[HarmonyPrefix]
public static void Postfix_get_lockState(ref CursorLockMode __result)
{
if (ShouldForceMouse)
{
__result = m_lastLockMode;
}
}
[HarmonyPrefix]
public static void Postfix_get_visible(ref bool __result)
{
if (ShouldForceMouse)
{
__result = m_lastVisibleState;
}
}
}
}

View File

@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using MelonLoader;
namespace Explorer
@ -21,15 +19,17 @@ namespace Explorer
Pages.Add(new SearchPage());
Pages.Add(new ConsolePage());
foreach (var page in Pages)
for (int i = 0; i < Pages.Count; i++)
{
var page = Pages[i];
page.Init();
}
}
public const int MainWindowID = 10;
public static Rect MainRect = new Rect(5, 5, 550, 700);
private static readonly List<WindowPage> Pages = new List<WindowPage>();
public const int MainWindowID = 5000;
public static Rect MainRect = new Rect(5,5, ModConfig.Instance.Default_Window_Size.x,ModConfig.Instance.Default_Window_Size.y);
public static readonly List<WindowPage> Pages = new List<WindowPage>();
private static int m_currentPage = 0;
public static void SetCurrentPage(int index)
@ -51,25 +51,20 @@ namespace Explorer
public void OnGUI()
{
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, CppExplorer.NAME);
GUI.skin = origSkin;
}
private void MainWindow(int id)
{
GUI.DragWindow(new Rect(0, 0, MainRect.width - 90, 20));
if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), "Hide (F7)"))
if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), $"Hide ({ModConfig.Instance.Main_Menu_Toggle})"))
{
CppExplorer.ShowMenu = false;
return;
}
GUILayout.BeginArea(new Rect(5, 25, MainRect.width - 10, MainRect.height - 35), GUI.skin.box);
GUIUnstrip.BeginArea(new Rect(5, 25, MainRect.width - 10, MainRect.height - 35), GUI.skin.box);
MainHeader();
@ -83,7 +78,7 @@ namespace Explorer
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
private void MainHeader()
@ -107,14 +102,16 @@ namespace Explorer
GUI.color = Color.white;
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
bool mouseState = CppExplorer.ForceUnlockMouse;
bool mouseState = CursorControl.ForceUnlockMouse;
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", null);
if (setMouse != mouseState) CppExplorer.ForceUnlockMouse = setMouse;
if (setMouse != mouseState) CursorControl.ForceUnlockMouse = setMouse;
WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", null);
GUILayout.EndHorizontal();
GUILayout.Space(10);
//GUIUnstrip.Space(10);
GUIUnstrip.Space(10);
GUI.color = Color.white;
}
}

View File

@ -58,7 +58,9 @@ MelonLogger.Log(""hello world"");";
}
catch (Exception e)
{
MelonLogger.Log($"Error setting up console!\r\nMessage: {e.Message}\r\nStack: {e.StackTrace}");
MelonLogger.Log($"Error setting up console!\r\nMessage: {e.Message}");
MainMenu.SetCurrentPage(0);
MainMenu.Pages.Remove(this);
}
}

View File

@ -244,7 +244,7 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.Space(5);
GUIUnstrip.Space(5);
}
private void SceneChangeButtons()

View File

@ -428,7 +428,7 @@ namespace Explorer
{
var t = ReflectionHelpers.GetActualType(obj);
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t))
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t))
{
continue;
}

View File

@ -32,12 +32,13 @@ namespace Explorer
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
//var r = GUILayoutUtility.GetLastRect();
var r = GUIUnstrip.GetLastRect();
var r = LayoutUtilityUnstrip.GetLastRect();
var mousePos = InputHelper.mousePosition;
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
try
{
var mouse = GUIUtility.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
if (r.Contains(mouse) && InputHelper.GetMouseButtonDown(0))
{
isResizing = true;
@ -56,6 +57,12 @@ namespace Explorer
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
}
}
catch
{
// throw safe Managed exception
throw new Exception("");
}
GUILayout.EndHorizontal();
}
@ -68,7 +75,7 @@ namespace Explorer
{
RESIZE_FAILED = true;
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
//MelonLogger.Log(e.StackTrace);
return origRect;
}
@ -82,20 +89,20 @@ namespace Explorer
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>Width:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
if (GUIUnstrip.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width -= 5f;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
if (GUIUnstrip.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width += 5f;
}
GUILayout.Label("<color=cyan>Height:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
if (GUIUnstrip.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height -= 5f;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
if (GUIUnstrip.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height += 5f;
}

View File

@ -12,10 +12,12 @@ namespace Explorer
public class GameObjectWindow : UIWindow
{
public override string Title => WindowManager.TabView
? $"<color=cyan>[G]</color> {m_object.name}"
: $"GameObject Inspector ({m_object.name})";
? $"<color=cyan>[G]</color> {TargetGO.name}"
: $"GameObject Inspector ({TargetGO.name})";
public GameObject m_object;
public GameObject TargetGO;
private static bool m_hideControls;
// gui element holders
private string m_name;
@ -23,11 +25,11 @@ namespace Explorer
private Transform[] m_children;
private Vector2 m_transformScroll = Vector2.zero;
private PageHelper ChildPages = new PageHelper();
private readonly PageHelper ChildPages = new PageHelper();
private Component[] m_components;
private Vector2 m_compScroll = Vector2.zero;
private PageHelper CompPages = new PageHelper();
private readonly PageHelper CompPages = new PageHelper();
private readonly Vector3[] m_cachedInput = new Vector3[3];
private float m_translateAmount = 0.3f;
@ -42,7 +44,7 @@ namespace Explorer
private bool m_localContext;
private readonly List<Component> m_cachedDestroyList = new List<Component>();
//private string m_addComponentInput = "";
private string m_addComponentInput = "";
private string m_setParentInput = "Enter a GameObject name or path";
@ -52,12 +54,12 @@ namespace Explorer
if (targetType == typeof(GameObject))
{
m_object = Target as GameObject;
TargetGO = Target as GameObject;
return true;
}
else if (targetType == typeof(Transform))
{
m_object = (Target as Transform).gameObject;
TargetGO = (Target as Transform).gameObject;
return true;
}
@ -73,10 +75,10 @@ namespace Explorer
return;
}
m_name = m_object.name;
m_scene = string.IsNullOrEmpty(m_object.scene.name)
m_name = TargetGO.name;
m_scene = string.IsNullOrEmpty(TargetGO.scene.name)
? "None (Asset/Resource)"
: m_object.scene.name;
: TargetGO.scene.name;
CacheTransformValues();
@ -87,15 +89,15 @@ namespace Explorer
{
if (m_localContext)
{
m_cachedInput[0] = m_object.transform.localPosition;
m_cachedInput[1] = m_object.transform.localEulerAngles;
m_cachedInput[0] = TargetGO.transform.localPosition;
m_cachedInput[1] = TargetGO.transform.localEulerAngles;
}
else
{
m_cachedInput[0] = m_object.transform.position;
m_cachedInput[1] = m_object.transform.eulerAngles;
m_cachedInput[0] = TargetGO.transform.position;
m_cachedInput[1] = TargetGO.transform.eulerAngles;
}
m_cachedInput[2] = m_object.transform.localScale;
m_cachedInput[2] = TargetGO.transform.localScale;
}
public override void Update()
@ -118,7 +120,7 @@ namespace Explorer
}
}
if (!m_object && !GetObjectAsGameObject())
if (!TargetGO && !GetObjectAsGameObject())
{
throw new Exception("Object is null!");
}
@ -127,22 +129,22 @@ namespace Explorer
{
if (m_localContext)
{
m_object.transform.localPosition = m_frozenPosition;
m_object.transform.localRotation = m_frozenRotation;
TargetGO.transform.localPosition = m_frozenPosition;
TargetGO.transform.localRotation = m_frozenRotation;
}
else
{
m_object.transform.position = m_frozenPosition;
m_object.transform.rotation = m_frozenRotation;
TargetGO.transform.position = m_frozenPosition;
TargetGO.transform.rotation = m_frozenRotation;
}
m_object.transform.localScale = m_frozenScale;
TargetGO.transform.localScale = m_frozenScale;
}
// update child objects
var childList = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++)
for (int i = 0; i < TargetGO.transform.childCount; i++)
{
childList.Add(m_object.transform.GetChild(i));
childList.Add(TargetGO.transform.GetChild(i));
}
childList.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_children = childList.ToArray();
@ -151,7 +153,7 @@ namespace Explorer
// update components
var compList = new Il2CppSystem.Collections.Generic.List<Component>();
m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
TargetGO.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
m_components = compList.ToArray();
@ -182,7 +184,7 @@ namespace Explorer
private void ReflectObject(Il2CppSystem.Object obj)
{
var window = WindowManager.InspectObject(obj, out bool created);
var window = WindowManager.InspectObject(obj, out bool created, true);
if (created)
{
@ -210,7 +212,7 @@ namespace Explorer
if (!WindowManager.TabView)
{
Header();
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
GUIUnstrip.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
}
scroll = GUIUnstrip.BeginScrollView(scroll);
@ -221,7 +223,7 @@ namespace Explorer
{
if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
{
ScenePage.Instance.SetTransformTarget(m_object.transform);
ScenePage.Instance.SetTransformTarget(TargetGO.transform);
MainMenu.SetCurrentPage(0);
}
}
@ -233,12 +235,12 @@ namespace Explorer
GUILayout.BeginHorizontal(null);
GUILayout.Label("Path:", new GUILayoutOption[] { GUILayout.Width(50) });
string pathlabel = m_object.transform.GetGameObjectPath();
if (m_object.transform.parent != null)
string pathlabel = TargetGO.transform.GetGameObjectPath();
if (TargetGO.transform.parent != null)
{
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
InspectGameObject(m_object.transform.parent);
InspectGameObject(TargetGO.transform.parent);
}
}
GUILayout.TextArea(pathlabel, null);
@ -270,7 +272,7 @@ namespace Explorer
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
}
catch (Exception e)
@ -360,6 +362,28 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
m_addComponentInput = GUILayout.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(130) });
if (GUILayout.Button("Add Comp", null))
{
if (ReflectionHelpers.GetTypeByName(m_addComponentInput) is Type compType)
{
if (typeof(Component).IsAssignableFrom(compType))
{
TargetGO.AddComponent(Il2CppType.From(compType));
}
else
{
MelonLogger.LogWarning($"Type '{compType.Name}' is not assignable from Component!");
}
}
else
{
MelonLogger.LogWarning($"Could not find a type by the name of '{m_addComponentInput}'!");
}
}
GUILayout.EndHorizontal();
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (m_cachedDestroyList.Count > 0)
{
@ -385,7 +409,7 @@ namespace Explorer
}
else
{
GUILayout.Space(26);
GUIUnstrip.Space(26);
}
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
{
@ -439,21 +463,41 @@ namespace Explorer
private void GameObjectControls()
{
if (m_hideControls)
{
GUILayout.BeginHorizontal(null);
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", new GUILayoutOption[] { GUILayout.Width(200) });
if (GUILayout.Button("^ Show ^", new GUILayoutOption[] { GUILayout.Width(75) }))
{
m_hideControls = false;
}
GUILayout.EndHorizontal();
return;
}
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
GUILayout.BeginHorizontal(null);
bool m_active = m_object.activeSelf;
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", new GUILayoutOption[] { GUILayout.Width(200) });
if (GUILayout.Button("v Hide v", new GUILayoutOption[] { GUILayout.Width(75) }))
{
m_hideControls = true;
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
bool m_active = TargetGO.activeSelf;
m_active = GUILayout.Toggle(m_active, (m_active ? "<color=lime>Enabled " : "<color=red>Disabled") + "</color>",
new GUILayoutOption[] { GUILayout.Width(80) });
if (m_object.activeSelf != m_active) { m_object.SetActive(m_active); }
if (TargetGO.activeSelf != m_active) { TargetGO.SetActive(m_active); }
UIHelpers.InstantiateButton(m_object, 100);
UIHelpers.InstantiateButton(TargetGO, 100);
if (GUILayout.Button("Set DontDestroyOnLoad", new GUILayoutOption[] { GUILayout.Width(170) }))
{
GameObject.DontDestroyOnLoad(m_object);
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
GameObject.DontDestroyOnLoad(TargetGO);
TargetGO.hideFlags |= HideFlags.DontUnloadUnusedAsset;
}
var lbl = m_freeze ? "<color=lime>Unfreeze</color>" : "<color=orange>Freeze Pos/Rot</color>";
@ -474,7 +518,7 @@ namespace Explorer
{
if (GameObject.Find(m_setParentInput) is GameObject newparent)
{
m_object.transform.parent = newparent.transform;
TargetGO.transform.parent = newparent.transform;
}
else
{
@ -484,7 +528,7 @@ namespace Explorer
if (GUILayout.Button("Detach from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
{
m_object.transform.parent = null;
TargetGO.transform.parent = null;
}
GUILayout.EndHorizontal();
@ -499,15 +543,15 @@ namespace Explorer
{
if (m_localContext)
{
m_object.transform.localPosition = m_cachedInput[0];
m_object.transform.localEulerAngles = m_cachedInput[1];
TargetGO.transform.localPosition = m_cachedInput[0];
TargetGO.transform.localEulerAngles = m_cachedInput[1];
}
else
{
m_object.transform.position = m_cachedInput[0];
m_object.transform.eulerAngles = m_cachedInput[1];
TargetGO.transform.position = m_cachedInput[0];
TargetGO.transform.eulerAngles = m_cachedInput[1];
}
m_object.transform.localScale = m_cachedInput[2];
TargetGO.transform.localScale = m_cachedInput[2];
if (m_freeze)
{
@ -541,7 +585,7 @@ namespace Explorer
if (GUILayout.Button("<color=red><b>Destroy</b></color>", new GUILayoutOption[] { GUILayout.Width(120) }))
{
GameObject.Destroy(m_object);
GameObject.Destroy(TargetGO);
DestroyWindow();
return;
}
@ -553,15 +597,15 @@ namespace Explorer
{
if (m_localContext)
{
m_frozenPosition = m_object.transform.localPosition;
m_frozenRotation = m_object.transform.localRotation;
m_frozenPosition = TargetGO.transform.localPosition;
m_frozenRotation = TargetGO.transform.localRotation;
}
else
{
m_frozenPosition = m_object.transform.position;
m_frozenRotation = m_object.transform.rotation;
m_frozenPosition = TargetGO.transform.position;
m_frozenRotation = TargetGO.transform.rotation;
}
m_frozenScale = m_object.transform.localScale;
m_frozenScale = TargetGO.transform.localScale;
}
private void BoolToggle(ref bool value, string message)
@ -586,7 +630,7 @@ namespace Explorer
GUILayout.Label($"<color=cyan><b>{(m_localContext ? "Local " : "")}{mode}</b></color>:",
new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) });
var transform = m_object.transform;
var transform = TargetGO.transform;
switch (mode)
{
case TranslateType.Position:
@ -639,11 +683,11 @@ namespace Explorer
{
f = f2;
}
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
if (GUIUnstrip.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
f -= multByTime ? amount * Time.deltaTime : amount;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
if (GUIUnstrip.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
f += multByTime ? amount * Time.deltaTime : amount;
}

View File

@ -28,20 +28,32 @@ namespace Explorer
public MemberTypes m_filter = MemberTypes.Property;
private bool m_hideFailedReflection = false;
// some extra caching
// some extra cast-caching
private UnityEngine.Object m_uObj;
private Component m_component;
private static readonly HashSet<string> _typeAndMemberBlacklist = new HashSet<string>
{
// Causes a crash
"Type.DeclaringMethod",
// Causes a crash
"Rigidbody2D.Cast",
};
private static readonly HashSet<string> _methodStartsWithBlacklist = new HashSet<string>
{
// Pointless (handled by Properties)
"get_",
"set_",
};
public override void Init()
{
var type = ReflectionHelpers.GetActualType(Target);
TargetType = ReflectionHelpers.GetActualType(Target);
TargetType = type;
var types = ReflectionHelpers.GetAllBaseTypes(Target);
CacheMembers(types);
CacheMembers(ReflectionHelpers.GetAllBaseTypes(Target));
// cache the extra cast-caching
if (Target is Il2CppSystem.Object ilObject)
{
var unityObj = ilObject.TryCast<UnityEngine.Object>();
@ -56,13 +68,6 @@ namespace Explorer
}
}
}
m_filter = MemberTypes.All;
m_autoUpdate = true;
Update();
m_autoUpdate = false;
m_filter = MemberTypes.Property;
}
public override void Update()
@ -99,28 +104,32 @@ namespace Explorer
private bool ShouldProcessMember(CacheObjectBase holder)
{
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType) return false;
// check MemberTypes filter
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType)
return false;
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
// hide failed reflection
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection)
return false;
if (m_search == "" || holder.MemInfo == null) return true;
// see if we should do name search
if (m_search == "" || holder.MemInfo == null)
return true;
var name = holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name;
return name.ToLower().Contains(m_search.ToLower());
// ok do name search
return (holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name)
.ToLower()
.Contains(m_search.ToLower());
}
private void CacheMembers(Type[] types)
{
var list = new List<CacheObjectBase>();
var names = new List<string>();
var cachedSigs = new List<string>();
foreach (var declaringType in types)
{
MemberInfo[] infos;
string exception = null;
try
{
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
@ -132,6 +141,7 @@ namespace Explorer
}
object target = Target;
string exception = null;
if (target is Il2CppSystem.Object ilObject)
{
@ -147,48 +157,64 @@ namespace Explorer
foreach (var member in infos)
{
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method)
{
var name = $"{member.DeclaringType.Name}.{member.Name}";
// blacklist (should probably make a proper implementation)
if (name == "Type.DeclaringMethod" || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) //|| member.Name.Contains("Il2CppType")
{
// make sure member type is Field, Method of Property (4 / 8 / 16)
int m = (int)member.MemberType;
if (m < 4 || m > 16)
continue;
}
// check blacklisted members
var name = member.DeclaringType.Name + "." + member.Name;
if (_typeAndMemberBlacklist.Any(it => it == name))
continue;
if (_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it)))
continue;
// compare signature to already cached members
var signature = $"{member.DeclaringType.Name}.{member.Name}";
if (member is MethodInfo mi)
{
name += " (";
foreach (var param in mi.GetParameters())
AppendParams(mi.GetParameters());
}
else if (member is PropertyInfo pi)
{
name += param.ParameterType.Name + ", ";
AppendParams(pi.GetIndexParameters());
}
name += ")";
void AppendParams(ParameterInfo[] _args)
{
signature += " (";
foreach (var param in _args)
{
signature += $"{param.ParameterType.Name} {param.Name}, ";
}
if (names.Contains(name))
signature += ")";
}
if (cachedSigs.Contains(signature))
{
continue;
}
// MelonLogger.Log($"Trying to cache member {signature}...");
try
{
var cached = CacheObjectBase.GetCacheObject(member, target);
if (cached != null)
{
names.Add(name);
cachedSigs.Add(signature);
list.Add(cached);
cached.ReflectionException = exception;
}
}
catch (Exception e)
{
MelonLogger.LogWarning($"Exception caching member {name}!");
MelonLogger.LogWarning($"Exception caching member {signature}!");
MelonLogger.Log(e.ToString());
}
}
}
}
m_allCachedMembers = list.ToArray();
}
@ -206,7 +232,7 @@ namespace Explorer
if (!WindowManager.TabView)
{
Header();
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
GUIUnstrip.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
}
GUILayout.BeginHorizontal(null);
@ -266,7 +292,7 @@ namespace Explorer
GUI.color = Color.white;
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUIUnstrip.Space(10);
Pages.ItemCount = m_cachedMembersFiltered.Length;
@ -295,7 +321,7 @@ namespace Explorer
scroll = GUIUnstrip.BeginScrollView(scroll);
GUILayout.Space(10);
GUIUnstrip.Space(10);
UIStyles.HorizontalLine(Color.grey);
@ -332,7 +358,7 @@ namespace Explorer
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
}
catch (Il2CppException e)

View File

@ -65,7 +65,7 @@ namespace Explorer
return;
}
GUILayout.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
GUIUnstrip.BeginArea(new Rect(5, 25, m_rect.width - 10, m_rect.height - 35), GUI.skin.box);
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginHorizontal(null);
@ -109,7 +109,7 @@ namespace Explorer
}
catch { }
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
catch { }
}

View File

@ -17,7 +17,7 @@ namespace Explorer
public object Target;
public int windowID;
public Rect m_rect = new Rect(0, 0, 550, 700);
public Rect m_rect = new Rect(0,0, ModConfig.Instance.Default_Window_Size.x,ModConfig.Instance.Default_Window_Size.y);
public Vector2 scroll = Vector2.zero;
@ -49,15 +49,7 @@ namespace Explorer
public void OnGUI()
{
if (CppExplorer.ShowMenu)
{
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Title);
GUI.skin = origSkin;
}
}
public void Header()

View File

@ -8,7 +8,6 @@ using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace Explorer
{

124
src/Tests/TestClass.cs Normal file
View File

@ -0,0 +1,124 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace Explorer.Tests
{
public class TestClass
{
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
private static TestClass m_instance;
public TestClass()
{
ILHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
ILHashSetTest.Add("1");
ILHashSetTest.Add("2");
ILHashSetTest.Add("3");
}
// test a non-generic dictionary
public Hashtable TestNonGenericDict()
{
return new Hashtable
{
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 },
};
}
// IL2CPP HASHTABLE NOT SUPPORTED! Cannot assign Il2CppSystem.Object from primitive struct / string.
// Technically they are "supported" but if they contain System types they will not work.
//public Il2CppSystem.Collections.Hashtable TestIl2CppNonGenericDict()
//{
// var table = new Il2CppSystem.Collections.Hashtable();
// table.Add("One", 1);
// table.Add("One", 2);
// table.Add("One", 3);
// return table;
//}
// test HashSets
public static HashSet<string> HashSetTest = new HashSet<string>
{
"One",
"Two",
"Three"
};
public static Il2CppSystem.Collections.Generic.HashSet<string> ILHashSetTest;
// Test indexed parameter
public string this[int arg0, string arg1]
{
get
{
return $"arg0: {arg0}, arg1: {arg1}";
}
}
// Test basic list
public static List<string> TestList = new List<string>
{
"1",
"2",
"3",
"etc..."
};
// Test a nested dictionary
public static Dictionary<int, Dictionary<string, int>> NestedDictionary = new Dictionary<int, Dictionary<string, int>>
{
{
1,
new Dictionary<string, int>
{
{
"Sub 1", 123
},
{
"Sub 2", 456
},
}
},
{
2,
new Dictionary<string, int>
{
{
"Sub 3", 789
},
{
"Sub 4", 000
},
}
},
};
// Test a basic method
public static Color TestMethod(float r, float g, float b, float a)
{
return new Color(r, g, b, a);
}
// A method with default arguments
public static Vector3 TestDefaultArgs(float arg0, float arg1, float arg2 = 5.0f)
{
return new Vector3(arg0, arg1, arg2);
}
}
}

View File

@ -20,42 +20,135 @@ namespace Explorer
public static bool ScrollFailed = false;
public static bool ManualUnstripFailed = false;
private static GenericStack ScrollStack
{
get
private static GenericStack ScrollStack => m_scrollStack ?? GetScrollStack();
private static PropertyInfo m_scrollViewStatesInfo;
private static GenericStack m_scrollStack;
public static DateTime nextScrollStepTime;
private static GenericStack GetScrollStack()
{
if (m_scrollViewStatesInfo == null)
{
try
if (typeof(GUI).GetProperty("scrollViewStates", ReflectionHelpers.CommonFlags) is PropertyInfo scrollStatesInfo)
{
m_scrollViewStatesInfo = typeof(GUI).GetProperty("scrollViewStates");
if (m_scrollViewStatesInfo == null) throw new Exception();
m_scrollViewStatesInfo = scrollStatesInfo;
}
catch
else if (typeof(GUI).GetProperty("s_ScrollViewStates", ReflectionHelpers.CommonFlags) is PropertyInfo s_scrollStatesInfo)
{
m_scrollViewStatesInfo = typeof(GUI).GetProperty("s_scrollViewStates");
m_scrollViewStatesInfo = s_scrollStatesInfo;
}
}
return (GenericStack)m_scrollViewStatesInfo?.GetValue(null, null);
}
}
private static PropertyInfo m_scrollViewStatesInfo;
public static Rect GetLastRect()
if (m_scrollViewStatesInfo?.GetValue(null, null) is GenericStack stack)
{
EventType type = Event.current.type;
Rect last;
if (type != EventType.Layout && type != EventType.Used)
{
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
m_scrollStack = stack;
}
else
{
last = GUILayoutUtility.kDummyRect;
m_scrollStack = new GenericStack();
}
return last;
return m_scrollStack;
}
public static void Space(float pixels)
{
GUIUtility.CheckOnGUI();
if (GUILayoutUtility.current.topLevel.isVertical)
LayoutUtilityUnstrip.GetRect(0, pixels, GUILayoutUtility.spaceStyle, new GUILayoutOption[] { GUILayout.Height(pixels) });
else
LayoutUtilityUnstrip.GetRect(pixels, 0, GUILayoutUtility.spaceStyle, new GUILayoutOption[] { GUILayout.Width(pixels) });
if (Event.current.type == EventType.Layout)
{
GUILayoutUtility.current.topLevel.entries[GUILayoutUtility.current.topLevel.entries.Count - 1].consideredForMargin = false;
}
}
// fix for repeatbutton
static public bool RepeatButton(Texture image, params GUILayoutOption[] options) { return DoRepeatButton(GUIContent.Temp(image), GUI.skin.button, options); }
static public bool RepeatButton(string text, params GUILayoutOption[] options) { return DoRepeatButton(GUIContent.Temp(text), GUI.skin.button, options); }
static public bool RepeatButton(GUIContent content, params GUILayoutOption[] options) { return DoRepeatButton(content, GUI.skin.button, options); }
static public bool RepeatButton(Texture image, GUIStyle style, params GUILayoutOption[] options) { return DoRepeatButton(GUIContent.Temp(image), style, options); }
static public bool RepeatButton(string text, GUIStyle style, params GUILayoutOption[] options) { return DoRepeatButton(GUIContent.Temp(text), style, options); }
// Make a repeating button. The button returns true as long as the user holds down the mouse
static public bool RepeatButton(GUIContent content, GUIStyle style, params GUILayoutOption[] options) { return DoRepeatButton(content, style, options); }
static bool DoRepeatButton(GUIContent content, GUIStyle style, GUILayoutOption[] options)
{ return GUI.RepeatButton(LayoutUtilityUnstrip.GetRect(content, style, options), content, style); }
// Fix for BeginArea
static public void BeginArea(Rect screenRect) { BeginArea(screenRect, GUIContent.none, GUIStyle.none); }
static public void BeginArea(Rect screenRect, string text) { BeginArea(screenRect, GUIContent.Temp(text), GUIStyle.none); }
static public void BeginArea(Rect screenRect, Texture image) { BeginArea(screenRect, GUIContent.Temp(image), GUIStyle.none); }
static public void BeginArea(Rect screenRect, GUIContent content) { BeginArea(screenRect, content, GUIStyle.none); }
static public void BeginArea(Rect screenRect, GUIStyle style) { BeginArea(screenRect, GUIContent.none, style); }
static public void BeginArea(Rect screenRect, string text, GUIStyle style) { BeginArea(screenRect, GUIContent.Temp(text), style); }
static public void BeginArea(Rect screenRect, Texture image, GUIStyle style) { BeginArea(screenRect, GUIContent.Temp(image), style); }
// Begin a GUILayout block of GUI controls in a fixed screen area.
static public void BeginArea(Rect screenRect, GUIContent content, GUIStyle style)
{
GUILayoutGroup g = GUILayoutUtility.BeginLayoutArea(style, Il2CppType.Of<GUILayoutGroup>());
if (Event.current.type == EventType.Layout)
{
g.resetCoords = true;
g.minWidth = g.maxWidth = screenRect.width;
g.minHeight = g.maxHeight = screenRect.height;
g.rect = Rect.MinMaxRect(screenRect.xMin, screenRect.yMin, g.rect.xMax, g.rect.yMax);
}
GUI.BeginGroup(g.rect, content, style);
}
// Close a GUILayout block started with BeginArea
static public void EndArea()
{
if (Event.current.type == EventType.Used)
return;
GUILayoutUtility.current.layoutGroups.Pop();
GUILayoutUtility.current.topLevel = GUILayoutUtility.current.layoutGroups.Peek().TryCast<GUILayoutGroup>();
GUI.EndGroup();
}
// Fix for BeginGroup
public static void BeginGroup(Rect position) { BeginGroup(position, GUIContent.none, GUIStyle.none); }
public static void BeginGroup(Rect position, string text) { BeginGroup(position, GUIContent.Temp(text), GUIStyle.none); }
public static void BeginGroup(Rect position, Texture image) { BeginGroup(position, GUIContent.Temp(image), GUIStyle.none); }
public static void BeginGroup(Rect position, GUIContent content) { BeginGroup(position, content, GUIStyle.none); }
public static void BeginGroup(Rect position, GUIStyle style) { BeginGroup(position, GUIContent.none, style); }
public static void BeginGroup(Rect position, string text, GUIStyle style) { BeginGroup(position, GUIContent.Temp(text), style); }
public static void BeginGroup(Rect position, Texture image, GUIStyle style) { BeginGroup(position, GUIContent.Temp(image), style); }
public static void BeginGroup(Rect position, GUIContent content, GUIStyle style) { BeginGroup(position, content, style, Vector2.zero); }
internal static void BeginGroup(Rect position, GUIContent content, GUIStyle style, Vector2 scrollOffset)
{
int id = GUIUtility.GetControlID(GUI.s_BeginGroupHash, FocusType.Passive);
if (content != GUIContent.none || style != GUIStyle.none)
{
switch (Event.current.type)
{
case EventType.Repaint:
style.Draw(position, content, id);
break;
default:
if (position.Contains(Event.current.mousePosition))
GUIUtility.mouseUsed = true;
break;
}
}
GUIClip.Push(position, scrollOffset, Vector2.zero, false);
}
public static void EndGroup()
{
GUIClip.Internal_Pop();
}
// Fix for BeginScrollView.
@ -112,8 +205,6 @@ namespace Explorer
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>();
@ -145,7 +236,7 @@ namespace Explorer
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
{
GUIUtility.CheckOnGUI();
// GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive);
@ -388,12 +479,12 @@ namespace Explorer
if (flag)
{
result = true;
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(250.0);
nextScrollStepTime = DateTime.Now.AddMilliseconds(250.0);
}
else if (Il2CppSystem.DateTime.Now >= GUI.nextScrollStepTime)
else if (DateTime.Now >= nextScrollStepTime)
{
result = true;
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(30.0);
nextScrollStepTime = DateTime.Now.AddMilliseconds(30.0);
}
if (Event.current.type == EventType.Repaint)
{

View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class LayoutUtilityUnstrip
{
public static Rect GetRect(float width, float height) { return DoGetRect(width, width, height, height, GUIStyle.none, null); }
public static Rect GetRect(float width, float height, GUIStyle style) { return DoGetRect(width, width, height, height, style, null); }
public static Rect GetRect(float width, float height, params GUILayoutOption[] options) { return DoGetRect(width, width, height, height, GUIStyle.none, options); }
// Reserve layout space for a rectangle with a fixed content area.
public static Rect GetRect(float width, float height, GUIStyle style, params GUILayoutOption[] options)
{ return DoGetRect(width, width, height, height, style, options); }
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight)
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, GUIStyle.none, null); }
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, GUIStyle style)
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, style, null); }
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, params GUILayoutOption[] options)
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, GUIStyle.none, options); }
// Reserve layout space for a flexible rect.
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, GUIStyle style, params GUILayoutOption[] options)
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, style, options); }
static Rect DoGetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, GUIStyle style, GUILayoutOption[] options)
{
switch (Event.current.type)
{
case EventType.Layout:
GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(minWidth, maxWidth, minHeight, maxHeight, style, options));
return GUILayoutUtility.kDummyRect;
case EventType.Used:
return GUILayoutUtility.kDummyRect;
default:
return GUILayoutUtility.current.topLevel.GetNext().rect;
}
}
public static Rect GetRect(GUIContent content, GUIStyle style) { return DoGetRect(content, style, null); }
// Reserve layout space for a rectangle for displaying some contents with a specific style.
public static Rect GetRect(GUIContent content, GUIStyle style, params GUILayoutOption[] options) { return DoGetRect(content, style, options); }
static Rect DoGetRect(GUIContent content, GUIStyle style, GUILayoutOption[] options)
{
GUIUtility.CheckOnGUI();
switch (Event.current.type)
{
case EventType.Layout:
if (style.isHeightDependantOnWidth)
{
GUILayoutUtility.current.topLevel.Add(new GUIWordWrapSizer(style, content, options));
}
else
{
Vector2 sizeConstraints = new Vector2(0, 0);
if (options != null)
{
foreach (var option in options)
{
if (float.TryParse(option.value.ToString(), out float f))
{
switch (option.type)
{
case GUILayoutOption.Type.maxHeight:
sizeConstraints.y = f;
break;
case GUILayoutOption.Type.maxWidth:
sizeConstraints.x = f;
break;
}
}
}
}
Vector2 size = style.CalcSizeWithConstraints(content, sizeConstraints);
// This is needed on non-integer scale ratios to avoid errors to accumulate in further layout calculations
size.x = Mathf.Ceil(size.x);
size.y = Mathf.Ceil(size.y);
GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(size.x, size.x, size.y, size.y, style, options));
}
return GUILayoutUtility.kDummyRect;
case EventType.Used:
return GUILayoutUtility.kDummyRect;
default:
var entry = GUILayoutUtility.current.topLevel.GetNext();
//GUIDebugger.LogLayoutEntry(entry.rect, entry.marginLeft, entry.marginRight, entry.marginTop, entry.marginBottom, entry.style);
return entry.rect;
}
}
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;
}
}
}

View File

@ -81,7 +81,7 @@ namespace Explorer
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(250.0);
GUIUnstrip.nextScrollStepTime = DateTime.Now.AddMilliseconds(250.0);
GUI.scrollTroughSide = this.CurrentScrollTroughSide();
result = this.PageMovementValue();
}
@ -155,7 +155,7 @@ namespace Explorer
else
{
GUI.InternalRepaintEditorWindow();
if (SystemClock.now < GUI.nextScrollStepTime)
if (DateTime.Now < GUIUnstrip.nextScrollStepTime)
{
result = this.currentValue;
}
@ -165,7 +165,7 @@ namespace Explorer
}
else
{
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(30.0);
GUIUnstrip.nextScrollStepTime = DateTime.Now.AddMilliseconds(30.0);
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;