Compare commits

..

76 Commits
1.6.2 ... 1.8.3

Author SHA1 Message Date
e7aa01ebc8 1.8.3
* Merging `mcs.dll` into the main `Explorer.dll` file, no longer needs to be in the Mods / Plugins folder.
2020-10-04 19:01:39 +11:00
bf6d526284 1.8.23
* Fixed an issue in Mono games when the target you are inspecting is destroyed (window would not close as it should).
* Cleaned up and refactored the Input support so it's easier to manage.
2020-10-03 20:19:44 +10:00
c991cb4b22 1.8.22
* Some performance improvements for the new InputSystem support (affects some 2019.3+ games)
* Fixed a small mistake with left/right mouse button checking.
2020-10-02 18:40:51 +10:00
3971e49ce1 Update README.md 2020-10-01 20:41:48 +10:00
6920ca1129 Update README.md 2020-10-01 20:26:25 +10:00
748e0cabcb 1.8.21
* Fixed a bug when editing a Text Field and the input string is `null`. Only affected Il2Cpp games, appeared in 1.8.0.
* Added a menu page for editing the Explorer Settings in-game, called `Options`.
* Added a new setting for default Items per Page Limit (for all "Pages" in Explorer).
2020-10-01 20:20:52 +10:00
b4b5f1ec93 1.8.2
* Added support for games which use the new InputSystem module and have disabled LegacyInputModule
2020-10-01 18:57:28 +10:00
5afaf85859 Update README.md 2020-10-01 17:16:00 +10:00
b65e417ecb Update GUIUnstrip.cs 2020-09-30 01:59:35 +10:00
23723a4ffd Some more unstrip fixes, and a few cleanups 2020-09-30 01:52:49 +10:00
dab7ecd441 Cleanups and refactorings, and some small UI fixes 2020-09-29 05:40:06 +10:00
f1406d016f Update README.md 2020-09-28 16:19:23 +10:00
99e801b3bd Update README.md 2020-09-28 02:11:35 +10:00
20133e123c Update README.md 2020-09-28 02:04:03 +10:00
142a2a4750 Update README.md 2020-09-27 23:04:42 +10:00
629403a74d 1.8.0 cleanup 2020-09-27 22:52:08 +10:00
873d0f277d Update README.md 2020-09-27 22:15:54 +10:00
99719fafaf Update README.md 2020-09-27 22:07:30 +10:00
b550356f14 1.8.0, merging Mono and Il2Cpp builds, adding BepInEx support
* Project renamed to Explorer to reflect the new scope
* Merged Mono and Il2Cpp builds
* Merged BepInEx and MelonLoader builds
* Some minor changes to accommodate for this
* The release DLL and the config file now use "Explorer" in place of "CppExplorer" for file and folder names
2020-09-27 22:04:23 +10:00
8c6202c194 Allow for inherited flags attributes 2020-09-23 19:42:37 +10:00
f203ae37fc 1.7.5
* Added support for Enums with [Flags] attribute (can set each flag individually)
* Added support for easier bitwise operations on ints (or any primitive assignable to int), and viewing the int as binary. This is intended for things like `Camera.cullingMask`, etc.
* Fixed an issue with Enums that contain duplicate values, for example `CameraClearFlags` (has duplicate values for 2).
2020-09-23 19:19:29 +10:00
2006a9ea76 Faster non-generic Il2Cpp casting 2020-09-21 22:45:33 +10:00
c9bc450d09 Update README.md 2020-09-21 17:48:13 +10:00
a1198f3a92 Update CacheColor.cs 2020-09-21 05:59:01 +10:00
04248a89ce Fix for cases when structs return null (due to null declaring instance) 2020-09-20 20:26:05 +10:00
3639824df3 Cleanup 2020-09-19 01:55:27 +10:00
939861b5f0 Cleanup 2020-09-19 01:44:38 +10:00
ad5fc04a3b Fix methods with multiple generic constraints 2020-09-19 01:27:33 +10:00
c39e097378 Add support for methods with ref/in/out args 2020-09-19 00:14:04 +10:00
129a7e3765 Improved interaction with generic methods and some minor UI fixes 2020-09-18 23:10:46 +10:00
643bb4519c Remove and sort usings 2020-09-18 18:38:11 +10:00
b154cbf39d Add support for generic methods, improved non-generic dictionary output 2020-09-18 18:03:17 +10:00
db91968519 Cleanup and improve syntax highlighting
* Static class members are now displayed in Italics and in a darker color, making them easier to distinguish.
* Cleaned up some issues related to syntax highlighting and refactored it into a global class.
* Methods and properties no longer display their arguments as part of the member name, they are only displayed when "Evaluate" is pressed.
2020-09-16 20:03:57 +10:00
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
0769b7ef23 1.6.5
* Add expander to Unity struct inspectors, collapsed by default
* `UnityEngine.Color` labels in Reflection Inspector are now the same color as the value for convenience
* Cleaned up InputHelper
2020-09-08 23:47:17 +10:00
5086dcc82b Update README.md 2020-09-08 20:21:34 +10:00
56abd38e92 Add build instructions to Readme 2020-09-08 20:18:37 +10:00
a7e6ae87df Update README.md 2020-09-08 19:47:07 +10:00
b5c584bb02 Update README.md 2020-09-08 19:46:54 +10:00
c8a3aecdf4 Update README.md 2020-09-08 17:09:23 +10:00
33c2378f41 Update README.md 2020-09-08 17:09:12 +10:00
38aafa7e5b 1.6.4
* Fix for games which do not load InputModule on startup. CppExplorer will now try to load the module itself.
* Cleanups
2020-09-08 17:07:10 +10:00
4bb0811b2c 1.6.3
* Merged the two builds into one, there is now only one release. Using Reflection for UnityEngine.Input
* A few small fixes and cleanups
2020-09-08 06:21:45 +10:00
4aefe1c5a3 a few tidy ups 2020-09-08 04:33:27 +10:00
69 changed files with 4530 additions and 2306 deletions

1
.gitignore vendored
View File

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

129
README.md
View File

@ -1,54 +1,93 @@
# 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">
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> Unity games, using <a href="https://github.com/HerpDerpinstine/MelonLoader">MelonLoader</a>.<br><br>
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> and <b>Mono</b> Unity games, using <a href="https://github.com/HerpDerpinstine/MelonLoader">MelonLoader</a> and <a href="https://github.com/BepInEx/BepInEx">BepInEx</a>.<br><br>
<a href="../../releases/latest">
<img src="https://img.shields.io/github/release/sinai-dev/CppExplorer.svg" />
<img src="https://img.shields.io/github/release/sinai-dev/Explorer.svg" />
</a>
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
<img src="https://img.shields.io/github/downloads/sinai-dev/Explorer/total.svg" />
</p>
### Known issues
* CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
<p align="center">
<img src="https://raw.githubusercontent.com/sinai-dev/Explorer/master/overview.png">
</p>
## Features
* Scene hierarchy explorer
* Search loaded assets with filters
* Traverse and manipulate GameObjects
* Generic Reflection inspector
* C# REPL Console
* Inspect-under-mouse
- [Current status](#current-status)
- [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)
## Current status
| Mod Loader | Il2Cpp | Mono | Mono (.NET 3.5) |
| ----------- | ------ | ---- | ----|
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Mono.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Mono.NET35.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) | ❔ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Mono.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Mono.NET35.zip) |
<b>IL2CPP Issues:</b>
* .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 Explorer menu may not work on all games at the moment.
## How to install
### MelonLoader
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 the relevant <b>Explorer_MelonLoader_.zip</b> from above.
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.
3. Make sure it's not in a sub-folder, `Explorer.dll` and `mcs.dll` should be directly in the `Mods\` folder.
### BepInEx
Requires [BepInEx](https://github.com/BepInEx/BepInEx) to be installed for your game.
1. Download the relevant <b>Explorer_BepInEx_.zip</b> from above.
2. Unzip the file into the `BepInEx\plugins\` folder in your game's installation directory, created by BepInEx.
3. Make sure it's not in a sub-folder, `Explorer.dll` and `mcs.dll` should be directly in the `plugins\` 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.
### Mod Config
There is a simple Mod Config for the Explorer. You can access the settings via the "Options" page of the main menu.
`Main Menu Toggle` (KeyCode)
* Sets the keybinding for the Main Menu toggle (show/hide all Explorer 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 Explorer windows when created.
* `x` is width, `y` is height.
* Default: `<x>550</x> <y>700</y>`
`Default Items per Page` (Int)
* Sets the default items per page when viewing lists or search results.
* Default: `20`
## Features
### Scene Explorer
* A simple menu which allows you to traverse the Transform heirarchy of the scene.
* Click on a GameObject to set it as the current path, or <b>Inspect</b> it to send it to an Inspector Window.
[![](https://i.imgur.com/BzTOCvp.png)](https://i.imgur.com/BzTOCvp.png)
### Inspectors
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
Explorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
<b>Tips:</b>
* When in Tab View, GameObjects are denoted by a [G] prefix, and Reflection objects are denoted by a [R] prefix.
@ -59,48 +98,68 @@ CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Re
* Allows you to see the children and components on a GameObject.
* Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
[![](https://i.imgur.com/DiDvl0Q.png)](https://i.imgur.com/DiDvl0Q.png)
### Reflection Inspector
* The Reflection Inspector is used for all other supported objects.
* Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods.
* Can search and filter members for the ones you are interested in.
[![](https://i.imgur.com/Pq127XD.png)](https://i.imgur.com/Pq127XD.png)
### Object Search
* You can search for an `UnityEngine.Object` with the Object Search feature.
* Filter by name, type, etc.
* For GameObjects and Transforms you can filter which scene they are found in too.
[![](https://i.imgur.com/lK2RthM.png)](https://i.imgur.com/lK2RthM.png)
### C# console
### C# REPL console
* A simple C# REPL console, allows you to execute a method body on the fly.
[![](https://i.imgur.com/5U4D1a8.png)](https://i.imgur.com/5U4D1a8.png)
* A simple C# console, allows you to execute a method body on the fly.
### Inspect-under-mouse
* Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse.
* Press Shift+RMB (Right Mouse Button) while the Explorer menu is open to begin Inspect-Under-Mouse.
* Hover over your desired object, if you see the name appear then you can click on it to inspect it.
* Only objects with Colliders are supported.
### Mouse Control
CppExplorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). However, you may also want to prevent the mouse clicking-through onto the game behind CppExplorer, this is possible but it requires specific patches for that game.
Explorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). However, you may also want to prevent the mouse clicking-through onto the game behind Explorer, this is possible but it requires specific patches for that game.
* For VRChat, use [VRCExplorerMouseControl](https://github.com/sinai-dev/VRCExplorerMouseControl)
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
* You can create your own plugin using one of the two plugins above as an example. Usually only a few simple Harmony patches are needed to fix the problem.
For example:
```csharp
using Explorer;
using Harmony; // or 'using HarmonyLib;' for BepInEx
// ...
[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 !ExplorerCore.ShowMenu;
}
}
```
## Building
If you'd like to build this yourself, everything you need (other than MelonLoader and/or BepInEx) is included with this repository.
1. Install MelonLoader or BepInEx for your game.
2. Open the `src\Explorer.csproj` file in a text editor.
3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader Il2Cpp game.
4. Open the `src\Explorer.sln` project.
5. Select `Solution 'Explorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it.
5. The DLLs are built to the `Release\` folder in the root of the repository.
## Credits
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: 23 KiB

BIN
lib/UnityEngine.dll Normal file

Binary file not shown.

BIN
lib/mcs.NET35.dll Normal file

Binary file not shown.

BIN
overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

View File

@ -0,0 +1,169 @@
using System;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public static class CacheFactory
{
public static CacheObjectBase GetTypeAndCacheObject(object obj)
=> GetTypeAndCacheObject(obj, null, null);
public static CacheObjectBase GetTypeAndCacheObject(MemberInfo memberInfo, object declarer)
=> GetTypeAndCacheObject(null, memberInfo, declarer);
public static CacheObjectBase GetTypeAndCacheObject(object obj, MemberInfo memberInfo, object declarer)
{
Type type = null;
if (memberInfo != null)
{
if (memberInfo is FieldInfo fi)
{
type = fi.FieldType;
}
else if (memberInfo is PropertyInfo pi)
{
type = pi.PropertyType;
}
else if (memberInfo is MethodInfo mi)
{
type = mi.ReturnType;
}
}
else if (obj != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
if (type == null)
{
return null;
}
return GetCacheObject(obj, memberInfo, declarer, type);
}
public static CacheObjectBase GetCacheObject(object obj, Type valueType)
=> GetCacheObject(obj, null, null, valueType);
private static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
{
CacheObjectBase cached;
var pi = memberInfo as PropertyInfo;
var mi = memberInfo as MethodInfo;
// Check if can process args
if ((pi != null && !CanProcessArgs(pi.GetIndexParameters()))
|| (mi != null && !CanProcessArgs(mi.GetParameters())))
{
return null;
}
if (mi != null)
{
cached = new CacheMethod();
}
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
{
cached = new CacheGameObject();
}
else if (valueType.IsPrimitive || valueType == typeof(string))
{
cached = new CachePrimitive();
}
else if (valueType.IsEnum)
{
if (valueType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
{
cached = new CacheEnumFlags();
}
else
{
cached = new CacheEnum();
}
}
else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
{
cached = new CacheVector();
}
else if (valueType == typeof(Quaternion))
{
cached = new CacheQuaternion();
}
else if (valueType == typeof(Color))
{
cached = new CacheColor();
}
else if (valueType == typeof(Rect))
{
cached = new CacheRect();
}
// must check this before IsEnumerable
else if (ReflectionHelpers.IsDictionary(valueType))
{
cached = new CacheDictionary();
}
else if (ReflectionHelpers.IsEnumerable(valueType))
{
cached = new CacheList();
}
else
{
cached = new CacheOther();
}
cached.Value = obj;
cached.ValueType = valueType;
if (memberInfo != null)
{
cached.MemInfo = memberInfo;
cached.DeclaringType = memberInfo.DeclaringType;
cached.DeclaringInstance = declaringInstance;
}
if (pi != null)
{
cached.m_arguments = pi.GetIndexParameters();
}
else if (mi != null)
{
cached.m_arguments = mi.GetParameters();
}
cached.m_argumentInput = new string[cached.m_arguments.Length];
cached.UpdateValue();
cached.Init();
return cached;
}
public static bool CanProcessArgs(ParameterInfo[] parameters)
{
foreach (var param in parameters)
{
var pType = param.ParameterType;
if (pType.IsByRef && pType.HasElementType)
{
pType = pType.GetElementType();
}
if (pType.IsPrimitive || pType == typeof(string))
{
continue;
}
else
{
return false;
}
}
return true;
}
}
}

View File

@ -2,10 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
namespace Explorer
@ -13,192 +9,44 @@ namespace Explorer
public abstract class CacheObjectBase
{
public object Value;
public string ValueTypeName;
public Type ValueType;
public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
public int PropertyIndex { get; private set; }
private string m_propertyIndexInput = "0";
public virtual 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; }
public string RichTextName => m_richTextName ?? GetRichTextName();
private string m_richTextName;
public bool CanWrite
{
get
{
if (MemInfo is FieldInfo fi)
return !(fi.IsLiteral && !fi.IsInitOnly);
else if (MemInfo is PropertyInfo pi)
return pi.CanWrite;
else
return false;
}
}
// ===== Abstract/Virtual Methods ===== //
public bool CanWrite => m_canWrite ?? GetCanWrite();
private bool? m_canWrite;
public virtual void Init() { }
public abstract void DrawValue(Rect window, float width);
// ===== Static Methods ===== //
/// <summary>
/// Get CacheObject from only an object instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
public static CacheObjectBase GetCacheObject(object obj)
{
return GetCacheObject(obj, null, null);
}
/// <summary>
/// Get CacheObject from an object instance and provide the value type
/// Calls GetCacheObjectImpl directly</summary>
public static CacheObjectBase GetCacheObject(object obj, Type valueType)
{
return GetCacheObjectImpl(obj, null, null, valueType);
}
/// <summary>
/// Get CacheObject from only a MemberInfo and declaring instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (null, memberInfo, declaringInstance)</summary>
public static CacheObjectBase GetCacheObject(MemberInfo memberInfo, object declaringInstance)
{
return GetCacheObject(null, memberInfo, declaringInstance);
}
/// <summary>
/// Get CacheObject from either an object or MemberInfo, and don't provide the type.
/// This gets the type and then calls GetCacheObjectImpl</summary>
public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
{
Type type = null;
if (memberInfo != null)
{
if (memberInfo is FieldInfo fi)
{
type = fi.FieldType;
}
else if (memberInfo is PropertyInfo pi)
{
type = pi.PropertyType;
}
else if (memberInfo is MethodInfo mi)
{
type = mi.ReturnType;
}
}
else if (obj != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
if (type == null)
{
return null;
}
return GetCacheObjectImpl(obj, memberInfo, declaringInstance, type);
}
/// <summary>
/// Actual GetCacheObject implementation (private)
/// </summary>
private static CacheObjectBase GetCacheObjectImpl(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
{
CacheObjectBase holder;
// This is pretty ugly, could probably make a cleaner implementation.
// However, the only cleaner ways I can think of are slower and probably not worth it.
// Note: the order is somewhat important.
if (memberInfo is MethodInfo mi)
{
if (CacheMethod.CanEvaluate(mi))
{
holder = new CacheMethod();
}
else
{
return null;
}
}
else if (valueType == typeof(GameObject) || valueType == typeof(Transform))
{
holder = new CacheGameObject();
}
else if (valueType.IsPrimitive || valueType == typeof(string))
{
holder = new CachePrimitive();
}
else if (valueType.IsEnum)
{
holder = new CacheEnum();
}
else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
{
holder = new CacheVector();
}
else if (valueType == typeof(Quaternion))
{
holder = new CacheQuaternion();
}
else if (valueType == typeof(Color))
{
holder = new CacheColor();
}
else if (valueType == typeof(Rect))
{
holder = new CacheRect();
}
// must check this before IsEnumerable
else if (ReflectionHelpers.IsDictionary(valueType))
{
holder = new CacheDictionary();
}
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType))
{
holder = new CacheList();
}
else
{
holder = new CacheOther();
}
holder.Value = obj;
holder.ValueType = valueType;
holder.ValueTypeName = valueType.FullName;
if (memberInfo != null)
{
holder.MemInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType;
holder.DeclaringInstance = declaringInstance;
holder.UpdateValue();
}
holder.Init();
return holder;
}
// ======== Instance Methods =========
public abstract void DrawValue(Rect window, float width);
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,21 +57,14 @@ 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)
{
var indexes = new object[] { PropertyIndex };
Value = pi.GetValue(target, indexes);
}
else
{
Value = pi.GetValue(target, null);
}
Value = pi.GetValue(target, ParseArguments());
}
ReflectionException = null;
m_evaluated = true;
m_isEvaluating = false;
}
catch (Exception e)
{
@ -244,26 +85,92 @@ 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
{
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, null);
}
}
}
catch (Exception e)
{
MelonLogger.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
ExplorerCore.LogWarning($"Error setting value: {e.GetType()}, {e.Message}");
}
}
// ========= Instance Gui Draw ==========
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 (type.IsByRef)
{
type = type.GetElementType();
}
if (!string.IsNullOrEmpty(input))
{
if (type == typeof(string))
{
parsedArgs.Add(input);
continue;
}
else
{
try
{
var arg = type.GetMethod("Parse", new Type[] { typeof(string) })
.Invoke(null, new object[] { input });
parsedArgs.Add(arg);
continue;
}
catch
{
ExplorerCore.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'.");
}
}
}
// No input, see if there is a default value.
if (HasDefaultValue(m_arguments[i]))
{
parsedArgs.Add(m_arguments[i].DefaultValue);
continue;
}
// Try add a null arg I guess
parsedArgs.Add(null);
}
return parsedArgs.ToArray();
}
public static bool HasDefaultValue(ParameterInfo arg) => arg.DefaultValue != DBNull.Value;
// ========= Gui Draw ==========
public const float MAX_LABEL_WIDTH = 400f;
public const string EVALUATE_LABEL = "<color=lime>Evaluate</color>";
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 static void ClampLabelWidth(Rect window, ref float labelWidth)
{
@ -282,84 +189,239 @@ 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);
}
if (!string.IsNullOrEmpty(ReflectionException))
var cm = this as CacheMethod;
if (HasParameters)
{
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
}
else if (Value == null && MemInfo?.MemberType != MemberTypes.Method)
{
GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
}
else
{
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
GUILayout.BeginVertical(new GUILayoutOption[0]);
if (m_isEvaluating)
{
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 (cm != null && cm.GenericArgs.Length > 0)
{
if (int.TryParse(m_propertyIndexInput, out int i))
GUILayout.Label($"<b><color=orange>Generic Arguments:</color></b>", new GUILayoutOption[0]);
for (int i = 0; i < cm.GenericArgs.Length; i++)
{
PropertyIndex = i;
UpdateValue();
}
else
{
MelonLogger.Log($"Could not parse '{m_propertyIndexInput}' to an int!");
string types = "";
if (cm.GenericConstraints[i].Length > 0)
{
foreach (var constraint in cm.GenericConstraints[i])
{
if (types != "") types += ", ";
string type;
if (constraint == null)
type = "Any";
else
type = constraint.ToString();
types += $"<color={UIStyles.Syntax.Class_Instance}>{type}</color>";
}
}
else
{
types = $"<color={UIStyles.Syntax.Class_Instance}>Any</color>";
}
var input = cm.GenericArgInput[i];
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label(
$"<color={UIStyles.Syntax.StructGreen}>{cm.GenericArgs[i].Name}</color>",
new GUILayoutOption[] { GUILayout.Width(15) }
);
cm.GenericArgInput[i] = GUIUnstrip.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
GUILayout.Label(types, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
}
}
// new line and space
if (m_arguments.Length > 0)
{
GUILayout.Label($"<b><color=orange>Arguments:</color></b>", new GUILayoutOption[0]);
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={UIStyles.Syntax.Class_Instance}>{type}</color> ";
label += $"<color={UIStyles.Syntax.Local}>{name}</color>";
if (HasDefaultValue(m_arguments[i]))
{
label = $"<i>[{label} = {m_arguments[i].DefaultValue ?? "null"}]</i>";
}
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) });
m_argumentInput[i] = GUIUnstrip.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
GUILayout.Label(label, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
}
}
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
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();
GUILayout.BeginHorizontal(null);
GUILayout.Space(labelWidth);
}
else
{
var lbl = $"Evaluate (";
int len = m_arguments.Length;
if (cm != null) len += cm.GenericArgs.Length;
lbl += len + " params)";
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(150) }))
{
m_isEvaluating = true;
}
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(labelWidth);
}
else if (cm != null)
{
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
{
cm.Evaluate();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(labelWidth);
}
string typeName = $"<color={UIStyles.Syntax.Class_Instance}>{ValueType.FullName}</color>";
if (!string.IsNullOrEmpty(ReflectionException))
{
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", new GUILayoutOption[0]);
}
else if ((HasParameters || this is CacheMethod) && !m_evaluated)
{
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> ({typeName})", new GUILayoutOption[0]);
}
else if (Value == null && !(this is CacheMethod))
{
GUILayout.Label($"<i>null ({typeName})</i>", new GUILayoutOption[0]);
}
else
{
DrawValue(window, window.width - labelWidth - 90);
}
}
private bool GetCanWrite()
{
if (MemInfo is FieldInfo fi)
m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
else if (MemInfo is PropertyInfo pi)
m_canWrite = pi.CanWrite;
else
m_canWrite = false;
return (bool)m_canWrite;
}
private string GetRichTextName()
{
string memberColor = "";
switch (MemInfo.MemberType)
{
case MemberTypes.Field:
memberColor = "#c266ff"; break;
case MemberTypes.Property:
memberColor = "#72a6a6"; break;
case MemberTypes.Method:
memberColor = "#ff8000"; break;
};
bool isStatic = false;
m_richTextName = $"<color=#2df7b2>{MemInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemInfo.Name}</color>";
if (MemInfo is MethodInfo mi)
if (MemInfo is FieldInfo fi)
{
m_richTextName += "(";
var _params = "";
foreach (var param in mi.GetParameters())
if (fi.IsStatic)
{
if (_params != "") _params += ", ";
_params += $"<color=#a6e9e9>{param.Name}</color>";
isStatic = true;
memberColor = UIStyles.Syntax.Field_Static;
}
m_richTextName += _params;
m_richTextName += ")";
else
memberColor = UIStyles.Syntax.Field_Instance;
}
else if (MemInfo is MethodInfo mi)
{
if (mi.IsStatic)
{
isStatic = true;
memberColor = UIStyles.Syntax.Method_Static;
}
else
memberColor = UIStyles.Syntax.Method_Instance;
}
else if (MemInfo is PropertyInfo pi)
{
if (pi.GetAccessors()[0].IsStatic)
{
isStatic = true;
memberColor = UIStyles.Syntax.Prop_Static;
}
else
memberColor = UIStyles.Syntax.Prop_Instance;
}
string classColor;
if (MemInfo.DeclaringType.IsValueType)
{
classColor = UIStyles.Syntax.StructGreen;
}
else if (MemInfo.DeclaringType.IsAbstract && MemInfo.DeclaringType.IsSealed)
{
classColor = UIStyles.Syntax.Class_Static;
}
else
{
classColor = UIStyles.Syntax.Class_Instance;
}
m_richTextName = $"<color={classColor}>{MemInfo.DeclaringType.Name}</color>.";
if (isStatic) m_richTextName += "<i>";
m_richTextName += $"<color={memberColor}>{MemInfo.Name}</color>";
if (isStatic) m_richTextName += "</i>";
// generic method args
if (this is CacheMethod cm && cm.GenericArgs.Length > 0)
{
m_richTextName += "<";
var args = "";
for (int i = 0; i < cm.GenericArgs.Length; i++)
{
if (args != "") args += ", ";
args += $"<color={UIStyles.Syntax.StructGreen}>{cm.GenericArgs[i].Name}</color>";
}
m_richTextName += args;
m_richTextName += ">";
}
return m_richTextName;

View File

@ -0,0 +1,8 @@
namespace Explorer
{
interface IExpandHeight
{
bool IsExpanded { get; set; }
float WhiteSpace { get; set; }
}
}

View File

@ -1,13 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using System.Reflection;
#if CPP
using UnhollowerBaseLib;
#endif
namespace Explorer
{
@ -15,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();
@ -56,39 +52,23 @@ namespace Explorer
{
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
// make generic dictionary from key and value type
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// get keys and values
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
var values = ValueType.GetProperty("Values").GetValue(Value);
var keys = ValueType.GetProperty("Keys") .GetValue(Value, null);
var values = ValueType.GetProperty("Values").GetValue(Value, null);
// create lists to hold them
var keyList = new List<object>();
var valueList = new List<object>();
// get keys enumerator and store keys
var keyEnumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null);
var keyCollectionType = keyEnumerator.GetType();
var keyMoveNext = keyCollectionType.GetMethod("MoveNext");
var keyCurrent = keyCollectionType.GetProperty("Current");
while ((bool)keyMoveNext.Invoke(keyEnumerator, null))
{
keyList.Add(keyCurrent.GetValue(keyEnumerator));
}
// store entries with reflection
EnumerateWithReflection(keys, keyList);
EnumerateWithReflection(values, valueList);
// get values enumerator and store values
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
var valueCollectionType = valueEnumerator.GetType();
var valueMoveNext = valueCollectionType.GetMethod("MoveNext");
var valueCurrent = valueCollectionType.GetProperty("Current");
while ((bool)valueMoveNext.Invoke(valueEnumerator, null))
{
valueList.Add(valueCurrent.GetValue(valueEnumerator));
}
// make actual mono dictionary
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// finally iterate into actual dictionary
// finally iterate into dictionary
for (int i = 0; i < keyList.Count; i++)
{
dict.Add(keyList[i], valueList[i]);
@ -97,35 +77,34 @@ namespace Explorer
return dict;
}
private void EnumerateWithReflection(object collection, List<object> list)
{
// invoke GetEnumerator
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
// get the type of it
var enumeratorType = enumerator.GetType();
// reflect MoveNext and Current
var moveNext = enumeratorType.GetMethod("MoveNext");
var current = enumeratorType.GetProperty("Current");
// iterate
while ((bool)moveNext.Invoke(enumerator, null))
{
list.Add(current.GetValue(enumerator, null));
}
}
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;
}
if (memberType != null && memberType.IsGenericType)
{
m_keysType = memberType.GetGenericArguments()[0];
m_valuesType = memberType.GetGenericArguments()[1];
}
m_keysType = ValueType.GetGenericArguments()[0];
m_valuesType = ValueType.GetGenericArguments()[1];
}
else if (Value != null)
else
{
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);
}
}
@ -151,16 +130,16 @@ namespace Explorer
var keys = new List<CacheObjectBase>();
foreach (var key in IDict.Keys)
{
var cache = GetCacheObject(key, TypeOfKeys);
cache.UpdateValue();
Type t = ReflectionHelpers.GetActualType(key) ?? TypeOfKeys;
var cache = CacheFactory.GetCacheObject(key, t);
keys.Add(cache);
}
var values = new List<CacheObjectBase>();
foreach (var val in IDict.Values)
{
var cache = GetCacheObject(val, TypeOfValues);
cache.UpdateValue();
Type t = ReflectionHelpers.GetActualType(val) ?? TypeOfValues;
var cache = CacheFactory.GetCacheObject(val, t);
values.Add(cache);
}
@ -170,82 +149,33 @@ namespace Explorer
private bool EnsureDictionaryIsSupported()
{
if (typeof(IDictionary).IsAssignableFrom(ValueType))
{
return true;
}
#if CPP
try
{
//var ilTypes = new List<Il2CppSystem.Type>();
var monoTypes = new Type[] { TypeOfKeys, TypeOfValues };
return Check(TypeOfKeys) && Check(TypeOfValues);
foreach (var type in monoTypes)
bool Check(Type type)
{
var generic = typeof(Il2CppClassPointerStore<>).MakeGenericType(type);
if (generic == null) return false;
var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(type)
.GetField("NativeClassPtr")
.GetValue(null);
var genericPtr = (IntPtr)generic.GetField("NativeClassPtr").GetValue(null);
if (genericPtr == null) return false;
var classPtr = IL2CPP.il2cpp_class_get_type(genericPtr);
if (classPtr == null) return false;
var internalType = Il2CppSystem.Type.internal_from_handle(classPtr);
if (internalType == null) return false;
//ilTypes.Add(internalType);
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
}
}
catch
{
return false;
}
// Should be fine if we got this far, but I'll leave the rest below commented out just in case.
return true;
//MelonLogger.Log("Got both generic types, continuing...");
//var dictIlClass = IL2CPP.GetIl2CppClass("mscorlib.dll", "System.Collections.Generic", "Dictionary`2");
//if (dictIlClass == null) return;
//MelonLogger.Log("Got base dictionary Il2Cpp type");
//var ilClassFromType = IL2CPP.il2cpp_class_get_type(dictIlClass);
//if (ilClassFromType == null) return;
//MelonLogger.Log("got IntPtr from base dictionary type");
//var internalHandle = Il2CppSystem.Type.internal_from_handle(ilClassFromType);
//if (internalHandle == null) return;
//var generic = internalHandle.MakeGenericType(new Il2CppReferenceArray<Il2CppSystem.Type>(new Il2CppSystem.Type[]
//{
// ilTypes[0], ilTypes[1]
//}));
//if (generic == null) return;
//MelonLogger.Log("Made generic handle for our entry types");
//var nativeClassPtr = generic.TypeHandle.value;
//if (nativeClassPtr == null) return;
//MelonLogger.Log("Got the actual nativeClassPtr for the handle");
//var dictType = typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).MakeGenericType(TypeOfKeys, TypeOfValues);
//if (dictType == null) return;
//MelonLogger.Log("Made the generic type for the dictionary");
//var pointerStoreType = typeof(Il2CppClassPointerStore<>).MakeGenericType(dictType);
//if (pointerStoreType == null) return;
//MelonLogger.Log("Made the generic PointerStoreType for our dict");
//var ptrToSet = IL2CPP.il2cpp_class_from_type(nativeClassPtr);
//if (ptrToSet == null) return;
//MelonLogger.Log("Got class from nativeClassPtr, setting value...");
//pointerStoreType.GetField("NativeClassPtr").SetValue(null, ptrToSet);
//MelonLogger.Log("Ok");
#else
return false;
#endif
}
// ============= GUI Draw =============
@ -254,10 +184,12 @@ namespace Explorer
{
if (m_cachedKeys == null || m_cachedValues == null)
{
GUILayout.Label("Cached keys or values is null!", null);
GUILayout.Label("Cached keys or values is null!", new GUILayoutOption[0]);
return;
}
var whitespace = CalcWhitespace(window);
int count = m_cachedKeys.Length;
if (!IsExpanded)
@ -275,32 +207,28 @@ 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)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
Pages.CurrentPageLabel();
@ -316,7 +244,7 @@ namespace Explorer
Pages.DrawLimitInputArea();
GUILayout.Space(5);
GUIUnstrip.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
@ -328,24 +256,25 @@ namespace Explorer
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
//GUILayout.Space(whitespace);
//GUIUnstrip.Space(whitespace);
if (key == null || val == null)
{
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", new GUILayoutOption[0]);
}
else
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
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

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using UnityEngine;
namespace Explorer
{

View File

@ -1,9 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
@ -12,7 +10,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 +38,7 @@ namespace Explorer
private Type m_genericTypeDef;
// Cached ToArray method for Lists
public MethodInfo GenericToArrayMethod
public MethodInfo CppListToArrayMethod
{
get => GetGenericToArrayMethod();
}
@ -61,7 +58,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 +98,50 @@ namespace Explorer
return m_itemProperty;
}
private IEnumerable GetEnumerableFromIl2CppList()
private IEnumerable EnumerateWithReflection()
{
if (Value == null) return null;
#if CPP
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();
}
#else
return Value as IEnumerable;
#endif
}
private IList ConvertIListToMono()
#if CPP
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
{
@ -136,10 +162,11 @@ namespace Explorer
}
catch (Exception e)
{
MelonLogger.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
return null;
}
}
#endif
private Type GetEntryType()
{
@ -204,6 +231,7 @@ namespace Explorer
if (obj != null && ReflectionHelpers.GetActualType(obj) is Type t)
{
#if CPP
if (obj is Il2CppSystem.Object iObj)
{
try
@ -216,10 +244,10 @@ namespace Explorer
}
catch { }
}
#endif
if (GetCacheObject(obj, t) is CacheObjectBase cached)
if (CacheFactory.GetCacheObject(obj, t) is CacheObjectBase cached)
{
cached.UpdateValue();
list.Add(cached);
}
else
@ -242,10 +270,12 @@ namespace Explorer
{
if (m_cachedEntries == null)
{
GUILayout.Label("m_cachedEntries is null!", null);
GUILayout.Label("m_cachedEntries is null!", new GUILayoutOption[0]);
return;
}
var whitespace = CalcWhitespace(window);
int count = m_cachedEntries.Length;
if (!IsExpanded)
@ -263,32 +293,28 @@ 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.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)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
Pages.CurrentPageLabel();
@ -304,7 +330,7 @@ namespace Explorer
Pages.DrawLimitInputArea();
GUILayout.Space(5);
GUIUnstrip.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
@ -315,19 +341,20 @@ namespace Explorer
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Space(whitespace);
GUIUnstrip.Space(whitespace);
if (entry == null || entry.Value == null)
{
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", new GUILayoutOption[0]);
}
else
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
entry.DrawValue(window, window.width - (whitespace + 85));
}

View File

@ -1,53 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
using MelonLoader;
namespace Explorer
{
public class CacheMethod : CacheObjectBase
{
private bool m_evaluated = false;
private CacheObjectBase m_cachedReturnValue;
private bool m_isEvaluating;
private ParameterInfo[] m_arguments;
private string[] m_argumentInput;
public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
public Type[] GenericArgs { get; private set; }
public Type[][] GenericConstraints { get; private set; }
public static bool CanEvaluate(MethodInfo mi)
{
// TODO generic args
if (mi.GetGenericArguments().Length > 0)
{
return false;
}
// primitive and string args supported
foreach (var param in mi.GetParameters())
{
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
{
return false;
}
}
return true;
}
public string[] GenericArgInput = new string[0];
public override void Init()
{
base.Init();
var mi = (MemInfo as MethodInfo);
GenericArgs = mi.GetGenericArguments();
var mi = MemInfo as MethodInfo;
GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints())
.ToArray();
m_arguments = mi.GetParameters();
m_argumentInput = new string[m_arguments.Length];
GenericArgInput = new string[GenericArgs.Length];
ValueType = mi.ReturnType;
}
public override void UpdateValue()
@ -55,73 +35,36 @@ namespace Explorer
//base.UpdateValue();
}
private void Evaluate()
public void Evaluate()
{
var mi = MemInfo as MethodInfo;
object ret = null;
if (!HasParameters)
MethodInfo mi;
if (GenericArgs.Length > 0)
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
m_evaluated = true;
mi = MakeGenericMethodFromInput();
if (mi == null) return;
}
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;
mi = MemInfo as MethodInfo;
}
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);
}
object ret = null;
}
catch
{
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
break;
}
}
}
try
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, parsedArgs.ToArray());
m_evaluated = true;
}
catch (Exception e)
{
MelonLogger.Log($"Exception evaluating: {e.GetType()}, {e.Message}");
}
try
{
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
m_evaluated = true;
m_isEvaluating = false;
}
catch (Exception e)
{
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
if (ret != null)
{
m_cachedReturnValue = GetCacheObject(ret);
if (m_cachedReturnValue is IExpandHeight expander)
{
expander.WhiteSpace = 0f;
expander.ButtonWidthOffset += 70f;
}
m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret);
m_cachedReturnValue.UpdateValue();
}
else
@ -130,62 +73,54 @@ namespace Explorer
}
}
// ==== GUI DRAW ====
public override void DrawValue(Rect window, float width)
private MethodInfo MakeGenericMethodFromInput()
{
GUILayout.BeginVertical(null);
var mi = MemInfo as MethodInfo;
string evaluateLabel = "<color=lime>Evaluate</color>";
if (HasParameters)
var list = new List<Type>();
for (int i = 0; i < GenericArgs.Length; i++)
{
if (m_isEvaluating)
var input = GenericArgInput[i];
if (ReflectionHelpers.GetTypeByName(input) is Type t)
{
for (int i = 0; i < m_arguments.Length; i++)
if (GenericConstraints[i].Length == 0)
{
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();
list.Add(t);
}
else
{
foreach (var constraint in GenericConstraints[i].Where(x => x != null))
{
if (!constraint.IsAssignableFrom(t))
{
ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!");
return null;
}
}
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;
list.Add(t);
}
}
else
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
{
m_isEvaluating = true;
}
ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
$" Make sure you use the full name, including the NameSpace.");
return null;
}
}
else
{
GUILayout.BeginHorizontal(null);
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
{
Evaluate();
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
// make into a generic with type list
mi = mi.MakeGenericMethod(list.ToArray());
return mi;
}
// ==== GUI DRAW ====
public override void DrawValue(Rect window, float width)
{
string typeLabel = $"<color={UIStyles.Syntax.Class_Instance}>{ValueType.FullName}</color>";
if (m_evaluated)
{
if (m_cachedReturnValue != null)
@ -194,16 +129,13 @@ namespace Explorer
}
else
{
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
GUILayout.Label($"null ({typeLabel})", new GUILayoutOption[0]);
}
}
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> ({typeLabel})", new GUILayoutOption[0]);
}
GUILayout.EndHorizontal();
GUILayout.EndVertical();
}
}
}

View File

@ -1,9 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
@ -11,50 +6,75 @@ namespace Explorer
{
public class CacheOther : CacheObjectBase
{
public string ButtonLabel => m_btnLabel ?? GetButtonLabel();
private string m_btnLabel;
public MethodInfo ToStringMethod => m_toStringMethod ?? GetToStringMethod();
private MethodInfo m_toStringMethod;
public MethodInfo ToStringMethod
public override void UpdateValue()
{
get
{
if (m_toStringMethod == null)
{
try
{
m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0])
?? typeof(object).GetMethod("ToString", new Type[0]);
base.UpdateValue();
// test invoke
m_toStringMethod.Invoke(Value, null);
}
catch
{
m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]);
}
}
return m_toStringMethod;
}
GetButtonLabel();
}
public override void DrawValue(Rect window, float width)
{
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
if (!label.Contains(ValueTypeName))
{
label += $" ({ValueTypeName})";
}
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
{
label = unityObj.name + " | " + label;
}
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width) }))
if (GUILayout.Button(ButtonLabel, new GUILayoutOption[] { GUILayout.Width(width - 15) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
}
private MethodInfo GetToStringMethod()
{
try
{
m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0])
?? typeof(object).GetMethod("ToString", new Type[0]);
// test invoke
m_toStringMethod.Invoke(Value, null);
}
catch
{
m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]);
}
return m_toStringMethod;
}
private string GetButtonLabel()
{
if (Value == null) return null;
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
var classColor = ValueType.IsAbstract && ValueType.IsSealed
? UIStyles.Syntax.Class_Static
: UIStyles.Syntax.Class_Instance;
string typeLabel = $"<color={classColor}>{ValueType.FullName}</color>";
if (Value is UnityEngine.Object)
{
label = label.Replace($"({ValueType.FullName})", $"({typeLabel})");
}
else
{
if (!label.Contains(ValueType.FullName))
{
label += $" ({typeLabel})";
}
else
{
label = label.Replace(ValueType.FullName, typeLabel);
}
}
return m_btnLabel = label;
}
}
}

View File

@ -2,22 +2,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheColor : CacheObjectBase
public class CacheColor : CacheObjectBase, IExpandHeight
{
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();
if (Value == null) return;
var color = (Color)Value;
r = color.r.ToString();
@ -28,47 +32,69 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Color</color>: {((Color)Value).ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
//var c = (Color)Value;
//GUI.color = c;
GUILayout.Label($"<color=#2df7b2>Color:</color> {((Color)Value).ToString()}", new GUILayoutOption[0]);
//GUI.color = Color.white;
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = CalcWhitespace(window);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
r = GUILayout.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
r = GUIUnstrip.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
g = GUILayout.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
g = GUIUnstrip.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
b = GUILayout.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
b = GUIUnstrip.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
a = GUILayout.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
a = GUIUnstrip.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
}
}
@ -79,7 +105,7 @@ namespace Explorer
&& float.TryParse(b, out float fB)
&& float.TryParse(a, out float fA))
{
Value = new Color(fR, fB, fG, fA);
Value = new Color(fR, fG, fB, fA);
SetValue();
}
}

View File

@ -2,32 +2,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheEnum : CacheObjectBase
{
public Type EnumType;
public string[] EnumNames;
// public Type EnumType;
public string[] EnumNames = new string[0];
public override void Init()
{
try
if (ValueType == null && Value != null)
{
EnumType = Value.GetType();
}
catch
{
EnumType = (MemInfo as FieldInfo)?.FieldType ?? (MemInfo as PropertyInfo).PropertyType;
ValueType = Value.GetType();
}
if (EnumType != null)
if (ValueType != null)
{
EnumNames = Enum.GetNames(EnumType);
// using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags)
var values = Enum.GetValues(ValueType);
var list = new List<string>();
foreach (var value in values)
{
var v = value.ToString();
if (list.Contains(v)) continue;
list.Add(v);
}
EnumNames = list.ToArray();
}
else
{
@ -41,28 +46,28 @@ namespace Explorer
{
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) }))
{
SetEnum(ref Value, -1);
SetEnum(-1);
SetValue();
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) }))
{
SetEnum(ref Value, 1);
SetEnum(1);
SetValue();
}
}
GUILayout.Label(Value.ToString() + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
GUILayout.Label(Value.ToString() + $"<color={UIStyles.Syntax.StructGreen}><i> ({ValueType})</i></color>", new GUILayoutOption[0]);
}
public void SetEnum(ref object value, int change)
public void SetEnum(int change)
{
var names = EnumNames.ToList();
int newindex = names.IndexOf(value.ToString()) + change;
int newindex = names.IndexOf(Value.ToString()) + change;
if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count))
if (newindex >= 0 && newindex < names.Count)
{
value = Enum.Parse(EnumType, names[newindex]);
Value = Enum.Parse(ValueType, EnumNames[newindex]);
}
}
}

View File

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Explorer
{
public class CacheEnumFlags : CacheEnum, IExpandHeight
{
public bool[] m_enabledFlags = new bool[0];
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public override void Init()
{
base.Init();
UpdateValue();
}
public override void UpdateValue()
{
base.UpdateValue();
if (Value == null) return;
try
{
var enabledNames = Value.ToString().Split(',').Select(it => it.Trim());
m_enabledFlags = new bool[EnumNames.Length];
for (int i = 0; i < EnumNames.Length; i++)
{
m_enabledFlags[i] = enabledNames.Contains(EnumNames[i]);
}
}
catch (Exception e)
{
ExplorerCore.Log(e.ToString());
}
}
public override void DrawValue(Rect window, float width)
{
if (CanWrite)
{
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.Label(Value.ToString() + "<color=#2df7b2><i> (" + ValueType + ")</i></color>", new GUILayoutOption[0]);
if (IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = CalcWhitespace(window);
for (int i = 0; i < EnumNames.Length; i++)
{
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
m_enabledFlags[i] = GUILayout.Toggle(m_enabledFlags[i], EnumNames[i], new GUILayoutOption[0]);
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetFlagsFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
}
}
public void SetFlagsFromInput()
{
string val = "";
for (int i = 0; i < EnumNames.Length; i++)
{
if (m_enabledFlags[i])
{
if (val != "") val += ", ";
val += EnumNames[i];
}
}
Value = Enum.Parse(ValueType, val);
SetValue();
}
}
}

View File

@ -1,21 +1,28 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using MelonLoader;
using UnityEngine;
#if CPP
using UnhollowerRuntimeLib;
#endif
namespace Explorer
{
public class CachePrimitive : CacheObjectBase
{
private string m_valueToString;
private bool m_isBool;
private bool m_isString;
private string m_valueToString;
public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
private MethodInfo m_parseMethod;
private bool m_canBitwiseOperate;
private bool m_inBitwiseMode;
private string m_bitwiseOperatorInput = "0";
private string m_binaryInput;
public override void Init()
{
if (ValueType == null)
@ -37,13 +44,28 @@ namespace Explorer
{
m_isBool = true;
}
m_canBitwiseOperate = typeof(int).IsAssignableFrom(ValueType);
UpdateValue();
}
public override void UpdateValue()
{
base.UpdateValue();
RefreshToString();
}
private void RefreshToString()
{
m_valueToString = Value?.ToString();
if (m_canBitwiseOperate)
{
var _int = (int)Value;
m_binaryInput = Convert.ToString(_int, toBase: 2);
}
}
public override void DrawValue(Rect window, float width)
@ -55,72 +77,175 @@ namespace Explorer
if (CanWrite)
{
b = GUILayout.Toggle(b, label, null);
b = GUILayout.Toggle(b, label, new GUILayoutOption[0]);
if (b != (bool)Value)
{
SetValueFromInput(b.ToString());
Value = b;
SetValue();
}
}
else
{
GUILayout.Label(label, null);
GUILayout.Label(label, new GUILayoutOption[0]);
}
return;
}
else
// all other non-bool values use TextField
GUILayout.BeginVertical(new GUILayoutOption[0]);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
m_valueToString = GUIUnstrip.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.ExpandWidth(true) });
if (CanWrite)
{
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
GUILayout.Label("<color=yellow><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f;
if (CanWrite) maxwidth -= 60;
if (dynSize > maxwidth)
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
SetValueFromInput();
}
else
{
m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) });
}
if (CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
SetValueFromInput(m_valueToString);
}
}
GUILayout.Space(5);
}
if (m_canBitwiseOperate)
{
m_inBitwiseMode = GUILayout.Toggle(m_inBitwiseMode, "Bitwise?", new GUILayoutOption[0]);
}
GUIUnstrip.Space(10);
GUILayout.EndHorizontal();
if (m_inBitwiseMode)
{
DrawBitwise();
}
GUILayout.EndVertical();
}
public void SetValueFromInput(string valueString)
private void DrawBitwise()
{
if (CanWrite)
{
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("RHS:", new GUILayoutOption[] { GUILayout.Width(35) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
if (GUILayout.Button("~", new GUILayoutOption[] { GUILayout.Width(25) }))
{
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
{
Value = ~bit;
RefreshToString();
}
}
if (GUILayout.Button("<<", new GUILayoutOption[] { GUILayout.Width(25) }))
{
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
{
Value = (int)Value << bit;
RefreshToString();
}
}
if (GUILayout.Button(">>", new GUILayoutOption[] { GUILayout.Width(25) }))
{
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
{
Value = (int)Value >> bit;
RefreshToString();
}
}
if (GUILayout.Button("|", new GUILayoutOption[] { GUILayout.Width(25) }))
{
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
{
Value = (int)Value | bit;
RefreshToString();
}
}
if (GUILayout.Button("&", new GUILayoutOption[] { GUILayout.Width(25) }))
{
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
{
Value = (int)Value & bit;
RefreshToString();
}
}
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
if (int.TryParse(m_bitwiseOperatorInput, out int bit))
{
Value = (int)Value ^ bit;
RefreshToString();
}
}
m_bitwiseOperatorInput = GUIUnstrip.TextField(m_bitwiseOperatorInput, new GUILayoutOption[] { GUILayout.Width(55) });
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"<color=cyan>Binary:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
m_binaryInput = GUIUnstrip.TextField(m_binaryInput, new GUILayoutOption[0]);
if (CanWrite)
{
if (GUILayout.Button("Apply", new GUILayoutOption[0]))
{
SetValueFromBinaryInput();
}
}
GUILayout.EndHorizontal();
}
public void SetValueFromInput()
{
if (MemInfo == null)
{
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
ExplorerCore.Log("Trying to SetValue but the MemberInfo is null!");
return;
}
if (m_isString)
{
Value = valueString;
Value = m_valueToString;
}
else
{
try
{
Value = ParseMethod.Invoke(null, new object[] { valueString });
Value = ParseMethod.Invoke(null, new object[] { m_valueToString });
}
catch (Exception e)
{
MelonLogger.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
ExplorerCore.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
}
}
SetValue();
RefreshToString();
}
private void SetValueFromBinaryInput()
{
try
{
var method = typeof(Convert).GetMethod($"To{ValueType.Name}", new Type[] { typeof(string), typeof(int) });
Value = method.Invoke(null, new object[] { m_binaryInput, 2 });
SetValue();
RefreshToString();
}
catch (Exception e)
{
ExplorerCore.Log("Exception setting value: " + e.GetType() + ", " + e.Message);
}
}
}
}

View File

@ -2,21 +2,25 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheQuaternion : CacheObjectBase
public class CacheQuaternion : CacheObjectBase, IExpandHeight
{
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();
if (Value == null) return;
var euler = ((Quaternion)Value).eulerAngles;
x = euler.x.ToString();
@ -26,41 +30,60 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label($"<color=#2df7b2>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", new GUILayoutOption[0]);
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = CalcWhitespace(window);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
x = GUIUnstrip.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
y = GUIUnstrip.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
z = GUIUnstrip.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
}
}

View File

@ -2,22 +2,26 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheRect : CacheObjectBase
public class CacheRect : CacheObjectBase, IExpandHeight
{
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();
if (Value == null) return;
var rect = (Rect)Value;
x = rect.x.ToString();
@ -28,47 +32,66 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label($"<color=#2df7b2>Rect</color>: {((Rect)Value).ToString()}", new GUILayoutOption[0]);
if (CanWrite && IsExpanded)
{
GUILayout.EndHorizontal();
var whitespace = CalcWhitespace(window);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
x = GUIUnstrip.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
y = GUIUnstrip.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
w = GUIUnstrip.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
h = GUILayout.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
h = GUIUnstrip.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
}
}

View File

@ -2,13 +2,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public class CacheVector : CacheObjectBase
public class CacheVector : CacheObjectBase, IExpandHeight
{
public int VectorSize = 2;
@ -19,22 +18,31 @@ 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)
if (ValueType == null && Value != null)
{
ValueType = Value.GetType();
}
if (ValueType == typeof(Vector2))
{
VectorSize = 2;
m_toStringMethod = typeof(Vector2).GetMethod("ToString", new Type[0]);
}
else if (Value is Vector3)
else if (ValueType == typeof(Vector3))
{
VectorSize = 3;
m_toStringMethod = typeof(Vector3).GetMethod("ToString", new Type[0]);
}
else
{
VectorSize = 4;
m_toStringMethod = typeof(Vector4).GetMethod("ToString", new Type[0]);
}
m_toStringMethod = Value.GetType().GetMethod("ToString", new Type[0]);
}
public override void UpdateValue()
@ -63,55 +71,74 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
if (CanWrite)
{
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
}
GUILayout.Label($"<color=#2df7b2>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", new GUILayoutOption[0]);
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);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
x = GUIUnstrip.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
y = GUIUnstrip.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
if (VectorSize > 2)
{
// draw z
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
z = GUIUnstrip.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
}
if (VectorSize > 3)
{
// draw w
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
w = GUIUnstrip.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
}
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUIUnstrip.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
}
}

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

@ -0,0 +1,65 @@
using System.IO;
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\Explorer";
[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 int Default_Page_Limit = 20;
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()
{
if (!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()
{
if (File.Exists(SETTINGS_PATH))
File.Delete(SETTINGS_PATH);
using (var file = File.Create(SETTINGS_PATH))
{
Serializer.Serialize(file, Instance);
}
}
}
}

View File

@ -1,202 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
namespace Explorer
{
public class CppExplorer : MelonMod
{
public const string GUID = "com.sinai.cppexplorer";
public const string VERSION = "1.6.2";
public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer"
#if Release_Unity2018
+ " (Unity 2018)"
#endif
;
public static CppExplorer Instance { get; private set; }
public static bool ShowMenu
{
get => m_showMenu;
set => SetShowMenu(value);
}
private static bool m_showMenu;
public static bool ForceUnlockMouse
{
get => m_forceUnlock;
set => SetForceUnlock(value);
}
private static bool m_forceUnlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
public static bool ShouldForceMouse => ShowMenu && ForceUnlockMouse;
private static void SetShowMenu(bool show)
{
m_showMenu = show;
UpdateCursorControl();
}
// ========== MonoBehaviour methods ==========
public override void OnApplicationStart()
{
Instance = this;
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);
MelonLogger.Log($"CppExplorer {VERSION} initialized.");
}
public override void OnLevelWasLoaded(int level)
{
ScenePage.Instance?.OnSceneChange();
SearchPage.Instance?.OnSceneChange();
}
public override void OnUpdate()
{
// Check main toggle key input
if (Input.GetKeyDown(KeyCode.F7))
{
ShowMenu = !ShowMenu;
}
if (ShowMenu)
{
// Check Force-Unlock input
if (Input.GetKeyDown(KeyCode.LeftAlt))
{
ForceUnlockMouse = !ForceUnlockMouse;
}
MainMenu.Instance.Update();
WindowManager.Instance.Update();
InspectUnderMouse.Update();
}
}
public override void OnGUI()
{
if (!ShowMenu) return;
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;
}
}
}
}
}

View File

@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Explorer</RootNamespace>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<AssemblyName>CppExplorer</AssemblyName>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Release\2019\</OutputPath>
<DefineConstants>Release_2019</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_Unity2018|AnyCPU' ">
<AssemblyName>CppExplorer_Unity2018</AssemblyName>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\Release\2018\</OutputPath>
<DefineConstants>Release_Unity2018</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="Il2Cppmscorlib">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="mcs">
<HintPath>..\lib\mcs.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MelonLoader.ModHandler">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="UnhollowerBaseLib">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- Unity 2019 build (InputLegacyModule.dll) -->
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Debug'">
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
<!-- Unity 2018 build (InputModule.dll) -->
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Release_Unity2018'">
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\IExpandHeight.cs" />
<Compile Include="CachedObjects\Struct\CacheColor.cs" />
<Compile Include="CachedObjects\Object\CacheDictionary.cs" />
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
<Compile Include="CachedObjects\Object\CacheGameObject.cs" />
<Compile Include="CachedObjects\Object\CacheList.cs" />
<Compile Include="CachedObjects\Struct\CachePrimitive.cs" />
<Compile Include="CachedObjects\Other\CacheOther.cs" />
<Compile Include="CachedObjects\Other\CacheMethod.cs" />
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
<Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
<Compile Include="UnstripFixes\ScrollViewStateUnstrip.cs" />
<Compile Include="Extensions\UnityExtensions.cs" />
<Compile Include="Helpers\PageHelper.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" />
<Compile Include="Helpers\UIHelpers.cs" />
<Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="MainMenu\InspectUnderMouse.cs" />
<Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="UnstripFixes\SliderHandlerUnstrip.cs" />
<Compile Include="UnstripFixes\UnstripExtensions.cs" />
<Compile Include="Windows\ResizeDrag.cs" />
<Compile Include="Windows\TabViewWindow.cs" />
<Compile Include="Windows\UIWindow.cs" />
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
<Compile Include="MainMenu\Pages\Console\REPLHelper.cs" />
<Compile Include="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="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppExplorer", "CppExplorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release_Unity2018|Any CPU = Release_Unity2018|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.ActiveCfg = Release_Unity2018|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.Build.0 = Release_Unity2018|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DD5C0A5D-03F1-4CC3-8B4D-E10834908C5A}
EndGlobalSection
EndGlobal

279
src/Explorer.csproj Normal file
View File

@ -0,0 +1,279 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Release_ML_Cpp</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
<OutputType>Library</OutputType>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<AppDesignerFolder>Properties</AppDesignerFolder>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<OutputPath>..\Release\Explorer.MelonLoader.Il2Cpp\</OutputPath>
<DefineConstants>CPP,ML</DefineConstants>
<IsCpp>true</IsCpp>
<IsMelonLoader>true</IsMelonLoader>
<IsNet35>false</IsNet35>
<DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
<RootNamespace>Explorer</RootNamespace>
<AssemblyName>Explorer</AssemblyName>
<!-- Set this to the MelonLoader Il2Cpp Game folder, without the ending '\' character. -->
<MLCppGameFolder>D:\Steam\steamapps\common\Hellpoint</MLCppGameFolder>
<!-- Set this to the MelonLoader Mono Game folder, without the ending '\' character. -->
<MLMonoGameFolder>D:\Steam\steamapps\common\Outward</MLMonoGameFolder>
<!-- Set this to the BepInEx Il2Cpp Game folder, without the ending '\' character. -->
<BIECppGameFolder>D:\Steam\steamapps\common\Outward - Il2Cpp</BIECppGameFolder>
<!-- Set this to the BepInEx Mono Game folder, without the ending '\' character. -->
<BIEMonoGameFolder>D:\Steam\steamapps\common\Outward</BIEMonoGameFolder>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_ML_Cpp|AnyCPU' ">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputPath>..\Release\Explorer.MelonLoader.Il2Cpp\</OutputPath>
<DefineConstants>CPP,ML</DefineConstants>
<IsCpp>true</IsCpp>
<IsMelonLoader>true</IsMelonLoader>
<IsNet35>false</IsNet35>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_ML_Mono|AnyCPU' ">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputPath>..\Release\Explorer.MelonLoader.Mono\</OutputPath>
<DefineConstants>MONO,ML</DefineConstants>
<Prefer32Bit>false</Prefer32Bit>
<IsCpp>false</IsCpp>
<IsMelonLoader>true</IsMelonLoader>
<IsNet35>false</IsNet35>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_ML_Mono_NET35|AnyCPU' ">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<OutputPath>..\Release\Explorer.MelonLoader.Mono.NET35\</OutputPath>
<DefineConstants>MONO,ML,NET35</DefineConstants>
<IsCpp>false</IsCpp>
<IsMelonLoader>true</IsMelonLoader>
<IsNet35>true</IsNet35>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Cpp|AnyCPU' ">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputPath>..\Release\Explorer.BepInEx.Il2Cpp\</OutputPath>
<DefineConstants>CPP,BIE</DefineConstants>
<IsCpp>true</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<IsNet35>false</IsNet35>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Mono|AnyCPU' ">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputPath>..\Release\Explorer.BepInEx.Mono\</OutputPath>
<DefineConstants>MONO,BIE</DefineConstants>
<IsCpp>false</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<IsNet35>false</IsNet35>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Mono_NET35|AnyCPU' ">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<OutputPath>..\Release\Explorer.BepInEx.Mono.NET35\</OutputPath>
<DefineConstants>MONO,BIE,NET35</DefineConstants>
<IsCpp>false</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<IsNet35>true</IsNet35>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<!-- MCS ref -->
<Reference Include="mcs" Condition="'$(IsNet35)'=='false'">
<HintPath>..\lib\mcs.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="mcs" Condition="'$(IsNet35)'=='true'">
<HintPath>..\lib\mcs.NET35.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- Universal Mono UnityEngine.dll ref (v5.3) -->
<ItemGroup Condition="'$(IsCpp)'=='false'">
<Reference Include="UnityEngine">
<HintPath>..\lib\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- MelonLoader Mono ref -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|false'">
<Reference Include="MelonLoader.ModHandler">
<HintPath>$(MLMonoGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- BepInEx Mono refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='false|false'">
<Reference Include="BepInEx">
<HintPath>$(BIEMonoGameFolder)\BepInEx\core\BepInEx.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>$(BIEMonoGameFolder)\BepInEx\core\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- MelonLoader Il2Cpp refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|true'">
<Reference Include="MelonLoader.ModHandler">
<HintPath>$(MLCppGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnhollowerBaseLib">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2Cppmscorlib">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>$(MLCppGameFolder)\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- BepInEx Il2Cpp refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='false|true'">
<Reference Include="BepInEx">
<HintPath>$(BIECppGameFolder)\BepInEx\core\BepInEx.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>$(BIECppGameFolder)\BepInEx\core\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="BepInEx.IL2CPP">
<HintPath>$(BIECppGameFolder)\BepInEx\core\BepInEx.IL2CPP.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnhollowerBaseLib">
<HintPath>$(BIECppGameFolder)\BepInEx\core\UnhollowerBaseLib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2Cppmscorlib">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\Il2Cppmscorlib.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Il2CppSystem.Core">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\Il2CppSystem.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.CoreModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.PhysicsModule">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.PhysicsModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>$(BIECppGameFolder)\BepInEx\unhollowed\UnityEngine.UI.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="CachedObjects\CacheFactory.cs" />
<Compile Include="CachedObjects\IExpandHeight.cs" />
<Compile Include="CachedObjects\Struct\CacheColor.cs" />
<Compile Include="CachedObjects\Object\CacheDictionary.cs" />
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
<Compile Include="CachedObjects\Object\CacheGameObject.cs" />
<Compile Include="CachedObjects\Object\CacheList.cs" />
<Compile Include="CachedObjects\Struct\CacheEnumFlags.cs" />
<Compile Include="CachedObjects\Struct\CachePrimitive.cs" />
<Compile Include="CachedObjects\Other\CacheOther.cs" />
<Compile Include="CachedObjects\Other\CacheMethod.cs" />
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
<Compile Include="Config\ModConfig.cs" />
<Compile Include="ExplorerCore.cs" />
<Compile Include="ExplorerBepInPlugin.cs" />
<Compile Include="ExplorerMelonMod.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="Input\AbstractInput.cs" />
<Compile Include="Input\InputManager.cs" />
<Compile Include="Input\InputSystem.cs" />
<Compile Include="Input\LegacyInput.cs" />
<Compile Include="Menu\CursorControl.cs" />
<Compile Include="Menu\MainMenu\Pages\OptionsPage.cs" />
<Compile Include="Tests\TestClass.cs" />
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
<Compile Include="UnstripFixes\Internal_LayoutUtility.cs" />
<Compile Include="Extensions\UnityExtensions.cs" />
<Compile Include="Helpers\PageHelper.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" />
<Compile Include="Menu\UIHelpers.cs" />
<Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="Menu\InspectUnderMouse.cs" />
<Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="UnstripFixes\Internal_ScrollViewState.cs" />
<Compile Include="UnstripFixes\Internal_SliderHandler.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" />
<Compile Include="UnstripFixes\Internal.cs" />
<Compile Include="UnstripFixes\Internal_SliderState.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

37
src/Explorer.sln Normal file
View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30128.74
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Explorer", "Explorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Release_BIE_Cpp|Any CPU = Release_BIE_Cpp|Any CPU
Release_BIE_Mono_NET35|Any CPU = Release_BIE_Mono_NET35|Any CPU
Release_BIE_Mono|Any CPU = Release_BIE_Mono|Any CPU
Release_ML_Cpp|Any CPU = Release_ML_Cpp|Any CPU
Release_ML_Mono_NET35|Any CPU = Release_ML_Mono_NET35|Any CPU
Release_ML_Mono|Any CPU = Release_ML_Mono|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.ActiveCfg = Release_BIE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.Build.0 = Release_BIE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Mono_NET35|Any CPU.ActiveCfg = Release_BIE_Mono_NET35|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Mono_NET35|Any CPU.Build.0 = Release_BIE_Mono_NET35|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Mono|Any CPU.ActiveCfg = Release_BIE_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Mono|Any CPU.Build.0 = Release_BIE_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp|Any CPU.ActiveCfg = Release_ML_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp|Any CPU.Build.0 = Release_ML_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono_NET35|Any CPU.ActiveCfg = Release_ML_Mono_NET35|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono_NET35|Any CPU.Build.0 = Release_ML_Mono_NET35|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.ActiveCfg = Release_ML_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.Build.0 = Release_ML_Mono|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DD5C0A5D-03F1-4CC3-8B4D-E10834908C5A}
EndGlobalSection
EndGlobal

109
src/ExplorerBepInPlugin.cs Normal file
View File

@ -0,0 +1,109 @@
#if BIE
using System;
using System.IO;
using System.Reflection;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
#if CPP
using UnhollowerRuntimeLib;
using BepInEx.IL2CPP;
#endif
namespace Explorer
{
[BepInPlugin(ExplorerCore.GUID, ExplorerCore.NAME, ExplorerCore.VERSION)]
#if CPP
public class ExplorerBepInPlugin : BasePlugin
#else
public class ExplorerBepInPlugin : BaseUnityPlugin
#endif
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging =>
#if CPP
Instance?.Log;
#else
Instance?.Logger;
#endif
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
#if CPP
// temporary for Il2Cpp until scene change delegate works
private static string lastSceneName;
#endif
// Init
#if CPP
public override void Load()
{
#else
internal void Awake()
{
#endif
Instance = this;
#if CPP
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
var obj = new GameObject(
"ExplorerBehaviour",
new Il2CppSystem.Type[]
{
Il2CppType.Of<ExplorerBehaviour>()
}
);
GameObject.DontDestroyOnLoad(obj);
#else
SceneManager.activeSceneChanged += DoSceneChange;
#endif
new ExplorerCore();
//HarmonyInstance.PatchAll();
}
internal static void DoSceneChange(Scene arg0, Scene arg1)
{
ExplorerCore.OnSceneChange();
}
#if CPP // BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.
public class ExplorerBehaviour : MonoBehaviour
{
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
internal void Awake()
{
Logging.LogMessage("ExplorerBehaviour.Awake");
}
#endif
internal void Update()
{
ExplorerCore.Update();
#if CPP
var scene = SceneManager.GetActiveScene();
if (scene.name != lastSceneName)
{
lastSceneName = scene.name;
DoSceneChange(scene, scene);
}
#endif
}
internal void OnGUI()
{
ExplorerCore.OnGUI();
}
#if CPP
}
#endif
}
}
#endif

119
src/ExplorerCore.cs Normal file
View File

@ -0,0 +1,119 @@
using UnityEngine;
namespace Explorer
{
public class ExplorerCore
{
public const string NAME = "Explorer (" + PLATFORM + ", " + MODLOADER + ")";
public const string VERSION = "1.8.3";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.explorer";
public const string PLATFORM =
#if CPP
"Il2Cpp";
#else
"Mono";
#endif
public const string MODLOADER =
#if ML
"MelonLoader";
#else
"BepInEx";
#endif
public static ExplorerCore Instance { get; private set; }
public ExplorerCore()
{
Instance = this;
ModConfig.OnLoad();
new MainMenu();
new WindowManager();
InputManager.Init();
CursorControl.Init();
Log($"{NAME} {VERSION} initialized.");
}
public static bool ShowMenu
{
get => m_showMenu;
set => SetShowMenu(value);
}
public static bool m_showMenu;
private static void SetShowMenu(bool show)
{
m_showMenu = show;
CursorControl.UpdateCursorControl();
}
public static void Update()
{
if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
{
ShowMenu = !ShowMenu;
}
if (ShowMenu)
{
CursorControl.Update();
InspectUnderMouse.Update();
MainMenu.Instance.Update();
WindowManager.Instance.Update();
}
}
public static void OnGUI()
{
if (!ShowMenu) return;
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
MainMenu.Instance.OnGUI();
WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI();
GUI.skin = origSkin;
}
public static void OnSceneChange()
{
ScenePage.Instance?.OnSceneChange();
SearchPage.Instance?.OnSceneChange();
}
public static void Log(string message)
{
#if ML
MelonLoader.MelonLogger.Log(message);
#else
ExplorerBepInPlugin.Logging?.LogMessage(message);
#endif
}
public static void LogWarning(string message)
{
#if ML
MelonLoader.MelonLogger.LogWarning(message);
#else
ExplorerBepInPlugin.Logging?.LogWarning(message);
#endif
}
public static void LogError(string message)
{
#if ML
MelonLoader.MelonLogger.LogError(message);
#else
ExplorerBepInPlugin.Logging?.LogError(message);
#endif
}
}
}

37
src/ExplorerMelonMod.cs Normal file
View File

@ -0,0 +1,37 @@
#if ML
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MelonLoader;
namespace Explorer
{
public class ExplorerMelonMod : MelonMod
{
public static ExplorerMelonMod Instance;
public override void OnApplicationStart()
{
Instance = this;
new ExplorerCore();
}
public override void OnLevelWasLoaded(int level)
{
ExplorerCore.OnSceneChange();
}
public override void OnUpdate()
{
ExplorerCore.Update();
}
public override void OnGUI()
{
ExplorerCore.OnGUI();
}
}
}
#endif

View File

@ -1,16 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace Explorer
{
public static class ReflectionExtensions
{
#if CPP
public static object Il2CppCast(this object obj, Type castTo)
{
return ReflectionHelpers.Il2CppCast(obj, castTo);
}
#endif
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
{
try
{
return asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
try
{
return asm.GetExportedTypes();
}
catch
{
return e.Types.Where(t => t != null);
}
}
catch
{
return Enumerable.Empty<Type>();
}
}
}
}

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnityEngine;
using UnityEngine;
namespace Explorer
{

View File

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

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine;
namespace Explorer
{
@ -26,7 +21,7 @@ namespace Explorer
CalculateMaxOffset();
}
}
private int m_itemsPerPage = 20;
private int m_itemsPerPage = ModConfig.Instance.Default_Page_Limit;
public int ItemCount
{
@ -99,7 +94,7 @@ namespace Explorer
{
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.ItemsPerPage.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
limit = GUIUnstrip.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != ItemsPerPage.ToString() && int.TryParse(limit, out int i))
{
ItemsPerPage = i;

View File

@ -1,16 +1,16 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Reflection;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using BF = System.Reflection.BindingFlags;
using MelonLoader;
using System.Collections;
using Mono.CSharp;
#if CPP
using ILType = Il2CppSystem.Type;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
#endif
namespace Explorer
{
@ -18,25 +18,160 @@ namespace Explorer
{
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>();
public static Il2CppSystem.Type ObjectType => Il2CppType.Of<UnityEngine.Object>();
public static Il2CppSystem.Type ComponentType => Il2CppType.Of<Component>();
public static Il2CppSystem.Type BehaviourType => Il2CppType.Of<Behaviour>();
#if CPP
public static ILType GameObjectType => Il2CppType.Of<GameObject>();
public static ILType TransformType => Il2CppType.Of<Transform>();
public static ILType ObjectType => Il2CppType.Of<UnityEngine.Object>();
public static ILType ComponentType => Il2CppType.Of<Component>();
public static ILType BehaviourType => Il2CppType.Of<Behaviour>();
private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
private static readonly MethodInfo tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
private static readonly Dictionary<Type, MethodInfo> cachedTryCastMethods = new Dictionary<Type, MethodInfo>();
public static object Il2CppCast(object obj, Type castTo)
{
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) return obj;
return m_tryCastMethodInfo
.MakeGenericMethod(castTo)
.Invoke(obj, null);
if (!cachedTryCastMethods.ContainsKey(castTo))
{
cachedTryCastMethods.Add(castTo, tryCastMethodInfo.MakeGenericMethod(castTo));
}
return cachedTryCastMethods[castTo].Invoke(obj, null);
}
#else
public static Type GameObjectType => typeof(GameObject);
public static Type TransformType => typeof(Transform);
public static Type ObjectType => typeof(UnityEngine.Object);
public static Type ComponentType => typeof(Component);
public static Type BehaviourType => typeof(Behaviour);
#endif
public static bool IsEnumerable(Type t)
{
if (typeof(IEnumerable).IsAssignableFrom(t))
{
return true;
}
#if CPP
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.HashSet<>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
}
#else
return false;
#endif
}
public static bool IsDictionary(Type t)
{
if (typeof(IDictionary).IsAssignableFrom(t))
{
return true;
}
#if CPP
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t)
|| typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(t);
}
#else
return false;
#endif
}
public static Type GetTypeByName(string fullName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
{
if (type.FullName == fullName)
{
return type;
}
}
}
return null;
}
public static Type GetActualType(object obj)
{
if (obj == null) return null;
#if CPP
// Need to use GetIl2CppType for Il2CppSystem Objects
if (obj is Il2CppSystem.Object ilObject)
{
// Prevent weird behaviour when inspecting an Il2CppSystem.Type object.
if (ilObject is ILType)
{
return typeof(ILType);
}
return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType();
}
#endif
// It's a normal object, this is fine
return obj.GetType();
}
public static Type[] GetAllBaseTypes(object obj)
{
var list = new List<Type>();
var type = GetActualType(obj);
while (type != null)
{
list.Add(type);
type = type.BaseType;
}
return list.ToArray();
}
public static bool LoadModule(string module)
{
#if CPP
#if ML
var path = $@"MelonLoader\Managed\{module}.dll";
#else
var path = $@"BepInEx\unhollowed\{module}.dll";
#endif
if (!File.Exists(path)) return false;
try
{
Assembly.Load(File.ReadAllBytes(path));
return true;
}
catch (Exception e)
{
ExplorerCore.Log(e.GetType() + ", " + e.Message);
}
#endif
return false;
}
public static string ExceptionToString(Exception e)
{
#if CPP
if (IsFailedGeneric(e))
{
return "Unable to initialize this type.";
@ -45,10 +180,11 @@ namespace Explorer
{
return "Garbage collected in Il2Cpp.";
}
#endif
return e.GetType() + ", " + e.Message;
}
#if CPP
public static bool IsFailedGeneric(Exception e)
{
return IsExceptionOfType(e, typeof(TargetInvocationException)) && IsExceptionOfType(e, typeof(TypeLoadException));
@ -75,100 +211,6 @@ namespace Explorer
else
return false;
}
public static bool IsEnumerable(Type t)
{
return typeof(IEnumerable).IsAssignableFrom(t);
}
// Only Il2Cpp List needs this check. C# List is IEnumerable.
public static bool IsCppList(Type t)
{
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
}
}
public static bool IsDictionary(Type t)
{
if (typeof(IDictionary).IsAssignableFrom(t))
{
return true;
}
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t);
}
}
public static Type GetTypeByName(string typeName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in GetTypesSafe(asm))
{
if (type.FullName == typeName)
{
return type;
}
}
}
return null;
}
public static Type GetActualType(object obj)
{
if (obj == null) return null;
if (obj is Il2CppSystem.Object ilObject)
{
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
{
return t;
}
return ilObject.GetType();
}
return obj.GetType();
}
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
{
try { return asm.GetTypes(); }
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
catch { return Enumerable.Empty<Type>(); }
}
public static Type[] GetAllBaseTypes(object obj)
{
var list = new List<Type>();
var type = GetActualType(obj);
list.Add(type);
while (type.BaseType != null)
{
type = type.BaseType;
list.Add(type);
}
return list.ToArray();
}
#endif
}
}

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine;
namespace Explorer
{
@ -15,7 +10,7 @@ namespace Explorer
{
get
{
if (m_mainCamera == null)
if (!m_mainCamera)
{
m_mainCamera = Camera.main;
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Explorer.Input
{
public abstract class AbstractInput
{
public abstract void Init();
public abstract Vector2 MousePosition { get; }
public abstract bool GetKeyDown(KeyCode key);
public abstract bool GetKey(KeyCode key);
public abstract bool GetMouseButtonDown(int btn);
public abstract bool GetMouseButton(int btn);
}
}

55
src/Input/InputManager.cs Normal file
View File

@ -0,0 +1,55 @@
using System;
using System.Reflection;
using UnityEngine;
using Explorer.Input;
namespace Explorer
{
public static class InputManager
{
// If no Input modules loaded at all
public static bool NO_INPUT { get; private set; }
// If using new InputSystem module
public static bool USING_NEW_INPUT { get; private set; }
private static AbstractInput inputModule;
public static void Init()
{
if (InputSystem.TKeyboard != null || TryLoadModule("Unity.InputSystem", InputSystem.TKeyboard))
{
USING_NEW_INPUT = true;
inputModule = new InputSystem();
}
else if (LegacyInput.TInput != null || TryLoadModule("UnityEngine.Input", LegacyInput.TInput))
{
inputModule = new LegacyInput();
}
if (inputModule == null)
{
ExplorerCore.LogWarning("Could not find any Input module!");
NO_INPUT = true;
}
else
{
inputModule.Init();
}
bool TryLoadModule(string dll, Type check) => ReflectionHelpers.LoadModule(dll) && check != null;
}
public static Vector3 MousePosition => inputModule?.MousePosition ?? Vector3.zero;
public static bool GetKeyDown(KeyCode key) => inputModule?.GetKeyDown(key) ?? false;
public static bool GetKey(KeyCode key) => inputModule?.GetKey(key) ?? false;
/// <param name="btn">0 = left, 1 = right, 2 = middle.</param>
public static bool GetMouseButtonDown(int btn) => inputModule?.GetMouseButtonDown(btn) ?? false;
/// <param name="btn">0 = left, 1 = right, 2 = middle.</param>
public static bool GetMouseButton(int btn) => inputModule?.GetMouseButton(btn) ?? false;
}
}

109
src/Input/InputSystem.cs Normal file
View File

@ -0,0 +1,109 @@
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Explorer.Input
{
public class InputSystem : AbstractInput
{
public static Type TKeyboard => _keyboard ?? (_keyboard = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
private static Type _keyboard;
public static Type TMouse => _mouse ?? (_mouse = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Mouse"));
private static Type _mouse;
public static Type TKey => _key ?? (_key = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Key"));
private static Type _key;
private static PropertyInfo _btnIsPressedProp;
private static PropertyInfo _btnWasPressedProp;
private static object CurrentKeyboard => _currentKeyboard ?? (_currentKeyboard = _kbCurrentProp.GetValue(null, null));
private static object _currentKeyboard;
private static PropertyInfo _kbCurrentProp;
private static PropertyInfo _kbIndexer;
private static object CurrentMouse => _currentMouse ?? (_currentMouse = _mouseCurrentProp.GetValue(null, null));
private static object _currentMouse;
private static PropertyInfo _mouseCurrentProp;
private static object LeftMouseButton => _lmb ?? (_lmb = _leftButtonProp.GetValue(CurrentMouse, null));
private static object _lmb;
private static PropertyInfo _leftButtonProp;
private static object RightMouseButton => _rmb ?? (_rmb = _rightButtonProp.GetValue(CurrentMouse, null));
private static object _rmb;
private static PropertyInfo _rightButtonProp;
private static object MousePositionInfo => _pos ?? (_pos = _positionProp.GetValue(CurrentMouse, null));
private static object _pos;
private static PropertyInfo _positionProp;
private static MethodInfo _readVector2InputMethod;
public override Vector2 MousePosition => (Vector2)_readVector2InputMethod.Invoke(MousePositionInfo, new object[0]);
public override bool GetKeyDown(KeyCode key)
{
var parsedKey = Enum.Parse(TKey, key.ToString());
var actualKey = _kbIndexer.GetValue(CurrentKeyboard, new object[] { parsedKey });
return (bool)_btnWasPressedProp.GetValue(actualKey, null);
}
public override bool GetKey(KeyCode key)
{
var parsed = Enum.Parse(TKey, key.ToString());
var actualKey = _kbIndexer.GetValue(CurrentKeyboard, new object[] { parsed });
return (bool)_btnIsPressedProp.GetValue(actualKey, null);
}
public override bool GetMouseButtonDown(int btn)
{
switch (btn)
{
case 0: return (bool)_btnWasPressedProp.GetValue(LeftMouseButton, null);
case 1: return (bool)_btnWasPressedProp.GetValue(RightMouseButton, null);
// case 2: return (bool)_btnWasPressedProp.GetValue(MiddleMouseButton, null);
default: throw new NotImplementedException();
}
}
public override bool GetMouseButton(int btn)
{
switch (btn)
{
case 0: return (bool)_btnIsPressedProp.GetValue(LeftMouseButton, null);
case 1: return (bool)_btnIsPressedProp.GetValue(RightMouseButton, null);
// case 2: return (bool)_btnIsPressedProp.GetValue(MiddleMouseButton, null);
default: throw new NotImplementedException();
}
}
public override void Init()
{
ExplorerCore.Log("Initializing new InputSystem support...");
_kbCurrentProp = TKeyboard.GetProperty("current");
_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
var btnControl = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
_btnIsPressedProp = btnControl.GetProperty("isPressed");
_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
_mouseCurrentProp = TMouse.GetProperty("current");
_leftButtonProp = TMouse.GetProperty("leftButton");
_rightButtonProp = TMouse.GetProperty("rightButton");
_positionProp = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Pointer")
.GetProperty("position");
_readVector2InputMethod = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
.MakeGenericType(typeof(Vector2))
.GetMethod("ReadValue");
}
}
}

42
src/Input/LegacyInput.cs Normal file
View File

@ -0,0 +1,42 @@
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Explorer.Input
{
public class LegacyInput : AbstractInput
{
public static Type TInput => _input ?? (_input = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
private static Type _input;
private static PropertyInfo _mousePositionProp;
private static MethodInfo _getKeyMethod;
private static MethodInfo _getKeyDownMethod;
private static MethodInfo _getMouseButtonMethod;
private static MethodInfo _getMouseButtonDownMethod;
public override Vector2 MousePosition => (Vector3)_mousePositionProp.GetValue(null, null);
public override bool GetKey(KeyCode key) => (bool)_getKeyMethod.Invoke(null, new object[] { key });
public override bool GetKeyDown(KeyCode key) => (bool)_getKeyDownMethod.Invoke(null, new object[] { key });
public override bool GetMouseButton(int btn) => (bool)_getMouseButtonMethod.Invoke(null, new object[] { btn });
public override bool GetMouseButtonDown(int btn) => (bool)_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
public override void Init()
{
ExplorerCore.Log("Initializing Legacy Input support...");
_mousePositionProp = TInput.GetProperty("mousePosition");
_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
}
}
}

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

@ -0,0 +1,190 @@
using System;
using UnityEngine;
#if ML
using Harmony;
#else
using HarmonyLib;
#endif
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 => ExplorerCore.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)
{
ExplorerCore.Log("Trying to manually load Cursor module...");
if (ReflectionHelpers.LoadModule("UnityEngine.CoreModule") && CursorType != null)
{
ExplorerCore.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)
{
ExplorerCore.Log($"Exception on CursorControl.Init! {e.GetType()}, {e.Message}");
}
// Enable ShowMenu and ForceUnlockMouse
// (set m_showMenu directly to not call UpdateCursorState twice)
ExplorerCore.m_showMenu = true;
ForceUnlockMouse = true;
}
private static void TryPatch(string property, HarmonyMethod patch, bool setter)
{
try
{
// var harmony = ExplorerCore.Instance.harmonyInstance;
var harmony =
#if ML
ExplorerMelonMod.Instance.harmonyInstance;
#else
ExplorerBepInPlugin.HarmonyInstance;
#endif
;
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)
{
ExplorerCore.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 (InputManager.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)
{
ExplorerCore.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

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine;
namespace Explorer
{
@ -15,9 +10,9 @@ namespace Explorer
public static void Update()
{
if (CppExplorer.ShowMenu)
if (ExplorerCore.ShowMenu)
{
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
if (InputManager.GetKey(KeyCode.LeftShift) && InputManager.GetMouseButtonDown(1))
{
EnableInspect = !EnableInspect;
}
@ -35,7 +30,10 @@ namespace Explorer
public static void InspectRaycast()
{
Ray ray = UnityHelpers.MainCamera.ScreenPointToRay(Input.mousePosition);
if (!UnityHelpers.MainCamera)
return;
var ray = UnityHelpers.MainCamera.ScreenPointToRay(InputManager.MousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
{
@ -43,7 +41,7 @@ namespace Explorer
m_objUnderMouseName = obj.transform.GetGameObjectPath();
if (Input.GetMouseButtonDown(0))
if (InputManager.GetMouseButtonDown(0))
{
EnableInspect = false;
m_objUnderMouseName = "";
@ -63,7 +61,7 @@ namespace Explorer
{
if (m_objUnderMouseName != "")
{
var pos = Input.mousePosition;
var pos = InputManager.MousePosition;
var rect = new Rect(
pos.x - (Screen.width / 2), // x
Screen.height - pos.y - 50, // y

View File

@ -3,9 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using MelonLoader;
namespace Explorer
{
@ -20,27 +17,33 @@ namespace Explorer
Pages.Add(new ScenePage());
Pages.Add(new SearchPage());
Pages.Add(new ConsolePage());
Pages.Add(new OptionsPage());
foreach (var page in Pages)
for (int i = 0; i < Pages.Count; i++)
{
var page = Pages[i];
page.Init();
// If page failed to init, it will remove itself from the list. Lower the iterate counter.
if (!Pages.Contains(page)) i--;
}
}
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)
{
if (index < 0 || Pages.Count <= index)
{
MelonLogger.Log("cannot set page " + index);
ExplorerCore.Log("cannot set page " + index);
return;
}
m_currentPage = index;
GUI.BringWindowToFront(MainWindowID);
GUIUnstrip.BringWindowToFront(MainWindowID);
GUI.FocusWindow(MainWindowID);
}
@ -51,25 +54,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;
MainRect = GUIUnstrip.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, ExplorerCore.NAME);
}
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 (GUIUnstrip.Button(new Rect(MainRect.width - 90, 2, 80, 20), $"Hide ({ModConfig.Instance.Main_Menu_Toggle})"))
{
CppExplorer.ShowMenu = false;
ExplorerCore.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,12 +81,12 @@ namespace Explorer
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
private void MainHeader()
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
for (int i = 0; i < Pages.Count; i++)
{
if (m_currentPage == i)
@ -96,25 +94,27 @@ namespace Explorer
else
GUI.color = Color.white;
if (GUILayout.Button(Pages[i].Name, null))
if (GUILayout.Button(Pages[i].Name, new GUILayoutOption[0]))
{
m_currentPage = i;
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.color = Color.white;
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", new GUILayoutOption[0]);
bool mouseState = CppExplorer.ForceUnlockMouse;
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", null);
if (setMouse != mouseState) CppExplorer.ForceUnlockMouse = setMouse;
bool mouseState = CursorControl.ForceUnlockMouse;
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", new GUILayoutOption[0]);
if (setMouse != mouseState) CursorControl.ForceUnlockMouse = setMouse;
WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", null);
WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.Space(10);
//GUIUnstrip.Space(10);
GUIUnstrip.Space(10);
GUI.color = Color.white;
}
}

View File

@ -1,9 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Mono.CSharp;
using UnityEngine;
using Attribute = System.Attribute;

View File

@ -1,15 +1,14 @@
using System.Collections;
//using Il2CppSystem;
using MelonLoader;
using System;
using UnityEngine;
using System;
using Object = UnityEngine.Object;
namespace Explorer
{
public class ReplHelper : MonoBehaviour
{
#if CPP
public ReplHelper(IntPtr intPtr) : base(intPtr) { }
#endif
public T Find<T>() where T : Object
{

View File

@ -1,14 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using System.Reflection;
using Mono.CSharp;
using System.IO;
using MelonLoader;
using System.Reflection;
using System.Text;
using Mono.CSharp;
using UnityEngine;
namespace Explorer
{
@ -34,20 +30,29 @@ namespace Explorer
"System.Collections",
"System.Collections.Generic",
"System.Reflection",
#if ML
"MelonLoader"
#endif
};
public override void Init()
{
#if CPP
UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp<ReplHelper>();
#endif
try
{
MethodInput = @"// This is a basic C# REPL console.
MethodInput = @"// This is a basic C# console.
// Some common using directives are added by default, you can add more below.
// If you want to return some output, MelonLogger.Log() it.
// If you want to return some output, Debug.Log() or MelonLogger.Log() it.
MelonLogger.Log(""hello world"");";
"
#if ML
+ @"MelonLogger.Log(""hello world"");";
#else
+ @"Debug.Log(""hello world"");";
#endif
;
ResetConsole();
@ -58,7 +63,9 @@ MelonLogger.Log(""hello world"");";
}
catch (Exception e)
{
MelonLogger.Log($"Error setting up console!\r\nMessage: {e.Message}\r\nStack: {e.StackTrace}");
ExplorerCore.Log($"Error setting up console!\r\nMessage: {e.Message}");
MainMenu.SetCurrentPage(0);
MainMenu.Pages.Remove(this);
}
}
@ -111,7 +118,7 @@ MelonLogger.Log(""hello world"");";
{
if (!suppressWarning)
{
MelonLogger.LogWarning(e.GetType() + ", " + e.Message);
ExplorerCore.LogWarning(e.GetType() + ", " + e.Message);
}
}
@ -121,19 +128,19 @@ MelonLogger.Log(""hello world"");";
public override void DrawWindow()
{
GUILayout.Label("<b><size=15><color=cyan>C# REPL Console</color></size></b>", null);
GUILayout.Label("<b><size=15><color=cyan>C# Console</color></size></b>", new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.Label("Enter code here as though it is a method body:", null);
GUILayout.Label("Enter code here as though it is a method body:", new GUILayoutOption[0]);
inputAreaScroll = GUIUnstrip.BeginScrollView(inputAreaScroll, new GUILayoutOption[] { GUILayout.Height(250) });
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.ExpandHeight(true) });
MethodInput = GUIUnstrip.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.ExpandHeight(true) });
GUIUnstrip.EndScrollView();
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", new GUILayoutOption[0]))
{
try
{
@ -145,21 +152,21 @@ MelonLogger.Log(""hello world"");";
if (result != null && !Equals(result, VoidType.Value))
{
MelonLogger.Log("[Console Output]\r\n" + result.ToString());
ExplorerCore.Log("[Console Output]\r\n" + result.ToString());
}
}
}
catch (Exception e)
{
MelonLogger.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace);
ExplorerCore.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace);
}
}
GUILayout.Label("<b>Using directives:</b>", null);
GUILayout.Label("<b>Using directives:</b>", new GUILayoutOption[0]);
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
UsingInput = GUILayout.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
UsingInput = GUIUnstrip.TextField(UsingInput, new GUILayoutOption[] { GUILayout.Width(150) });
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
{
AddUsing(UsingInput);
@ -172,7 +179,7 @@ MelonLogger.Log(""hello world"");";
foreach (var asm in UsingDirectives)
{
GUILayout.Label(AsmToUsing(asm, true), null);
GUILayout.Label(AsmToUsing(asm, true), new GUILayoutOption[0]);
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Explorer.Tests;
using UnityEngine;
namespace Explorer
{
public class OptionsPage : WindowPage
{
public override string Name => "Options";
public string toggleKeyInputString = "";
public Vector2 defaultSizeInputVector;
public int defaultPageLimit;
private CacheObjectBase toggleKeyInput;
private CacheObjectBase defaultSizeInput;
private CacheObjectBase defaultPageLimitInput;
public override void Init()
{
toggleKeyInputString = ModConfig.Instance.Main_Menu_Toggle.ToString();
toggleKeyInput = CacheFactory.GetTypeAndCacheObject(typeof(OptionsPage).GetField("toggleKeyInputString"), this);
defaultSizeInputVector = ModConfig.Instance.Default_Window_Size;
defaultSizeInput = CacheFactory.GetTypeAndCacheObject(typeof(OptionsPage).GetField("defaultSizeInputVector"), this);
defaultPageLimit = ModConfig.Instance.Default_Page_Limit;
defaultPageLimitInput = CacheFactory.GetTypeAndCacheObject(typeof(OptionsPage).GetField("defaultPageLimit"), this);
}
public override void Update() { }
public override void DrawWindow()
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label("<color=orange><size=16><b>Settings</b></size></color>", new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[0]);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Menu Toggle Key:", new GUILayoutOption[] { GUILayout.Width(215f) });
toggleKeyInput.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Default Window Size:", new GUILayoutOption[] { GUILayout.Width(215f) });
defaultSizeInput.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Default Items per Page:", new GUILayoutOption[] { GUILayout.Width(215f) });
defaultPageLimitInput.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
if (GUILayout.Button("<color=lime><b>Apply and Save</b></color>", new GUILayoutOption[0]))
{
ApplyAndSave();
}
GUILayout.EndVertical();
//GUIUnstrip.Space(10f);
//GUI.skin.label.alignment = TextAnchor.MiddleCenter;
//GUILayout.Label("<color=orange><size=16><b>Other</b></size></color>", new GUILayoutOption[0]);
//GUI.skin.label.alignment = TextAnchor.MiddleLeft;
//GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[0]);
//if (GUILayout.Button("Inspect Test Class", new GUILayoutOption[0]))
//{
// WindowManager.InspectObject(TestClass.Instance, out bool _);
//}
//GUILayout.EndVertical();
}
private void ApplyAndSave()
{
if (Enum.Parse(typeof(KeyCode), toggleKeyInputString) is KeyCode key)
{
ModConfig.Instance.Main_Menu_Toggle = key;
}
else
{
ExplorerCore.LogWarning($"Could not parse '{toggleKeyInputString}' to KeyCode!");
}
ModConfig.Instance.Default_Window_Size = defaultSizeInputVector;
ModConfig.Instance.Default_Page_Limit = defaultPageLimit;
ModConfig.SaveSettings();
}
}
}

View File

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MelonLoader;
using UnityEngine;
using UnityEngine.SceneManagement;
@ -12,7 +10,7 @@ namespace Explorer
{
public static ScenePage Instance;
public override string Name { get => "Scene Explorer"; }
public override string Name { get => "Scenes"; }
public PageHelper Pages = new PageHelper();
@ -88,7 +86,11 @@ namespace Explorer
foreach (var obj in Resources.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
{
#if CPP
var go = obj.TryCast<GameObject>();
#else
var go = obj as GameObject;
#endif
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
{
matches.Add(new GameObjectCache(go));
@ -126,14 +128,22 @@ namespace Explorer
{
try
{
var scene = SceneManager.GetSceneByName(m_currentScene);
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
allTransforms.AddRange(scene.GetRootGameObjects()
.Select(it => it.transform));
if (scene.name == m_currentScene)
{
allTransforms.AddRange(scene.GetRootGameObjects()
.Select(it => it.transform));
break;
}
}
}
catch
{
MelonLogger.Log("Exception getting root scene objects, falling back to backup method...");
ExplorerCore.Log("Exception getting root scene objects, falling back to backup method...");
m_getRootObjectsFailed = true;
allTransforms.AddRange(GetRootObjectsManual_Impl());
@ -175,7 +185,11 @@ namespace Explorer
var list = new List<Transform>();
foreach (var obj in array)
{
#if CPP
var transform = obj.TryCast<Transform>();
#else
var transform = obj as Transform;
#endif
if (transform.parent == null && transform.gameObject.scene.name == m_currentScene)
{
list.Add(transform);
@ -185,14 +199,14 @@ namespace Explorer
}
catch (Exception e)
{
MelonLogger.Log("Exception getting root scene objects (manual): "
ExplorerCore.Log("Exception getting root scene objects (manual): "
+ e.GetType() + ", " + e.Message + "\r\n"
+ e.StackTrace);
return new Transform[0];
}
}
// --------- GUI Draw Function --------- //
// --------- GUI Draw Function --------- //
public override void DrawWindow()
{
@ -200,7 +214,7 @@ namespace Explorer
{
DrawHeaderArea();
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
DrawPageButtons();
@ -217,71 +231,81 @@ namespace Explorer
}
catch (Exception e)
{
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
m_currentTransform = null;
if (!e.Message.Contains("in a group with only"))
{
ExplorerCore.Log(e.ToString());
}
}
}
private void DrawHeaderArea()
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
// Current Scene label
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
try
{
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
var scenes = SceneManager.GetAllScenes().ToList();
if (scenes.Count > 1)
{
int changeWanted = 0;
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = -1;
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = 1;
}
if (changeWanted != 0)
{
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
index += changeWanted;
if (index > scenes.Count - 1)
{
index = 0;
}
else if (index < 0)
{
index = scenes.Count - 1;
}
m_currentScene = scenes[index].name;
}
}
}
catch { }
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
SceneChangeButtons();
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", new GUILayoutOption[0]);
GUILayout.EndHorizontal();
// ----- GameObject Search -----
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUILayout.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
m_searchInput = GUILayout.TextField(m_searchInput, null);
m_searchInput = GUIUnstrip.TextField(m_searchInput, new GUILayoutOption[0]);
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Search();
}
GUILayout.EndHorizontal();
GUILayout.Space(5);
GUIUnstrip.Space(5);
}
private void SceneChangeButtons()
{
var scenes = new List<Scene>();
var names = new List<string>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
names.Add(scene.name);
scenes.Add(scene);
}
if (scenes.Count > 1)
{
int changeWanted = 0;
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = -1;
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
changeWanted = 1;
}
if (changeWanted != 0)
{
int index = names.IndexOf(m_currentScene);
index += changeWanted;
if (index > scenes.Count - 1)
{
index = 0;
}
else if (index < 0)
{
index = scenes.Count - 1;
}
m_currentScene = scenes[index].name;
}
}
}
private void DrawPageButtons()
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
Pages.DrawLimitInputArea();
@ -312,7 +336,7 @@ namespace Explorer
{
if (m_currentTransform != null)
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
{
TraverseUp();
@ -329,11 +353,11 @@ namespace Explorer
}
else
{
GUILayout.Label("Scene Root GameObjects:", null);
GUILayout.Label("Scene Root GameObjects:", new GUILayoutOption[0]);
if (m_getRootObjectsFailed)
{
if (GUILayout.Button("Update Root Object List (auto-update failed!)", null))
if (GUILayout.Button("Update Root Object List (auto-update failed!)", new GUILayoutOption[0]))
{
Update_Impl(true);
}
@ -358,7 +382,7 @@ namespace Explorer
}
label += "</i></color>";
GUILayout.Label(label, null);
GUILayout.Label(label, new GUILayoutOption[0]);
}
else
{
@ -381,7 +405,7 @@ namespace Explorer
CancelSearch();
}
GUILayout.Label("Search Results:", null);
GUILayout.Label("Search Results:", new GUILayoutOption[0]);
if (m_searchResults.Count > 0)
{
@ -403,13 +427,13 @@ namespace Explorer
}
else
{
GUILayout.Label("<i><color=red>Null or destroyed!</color></i>", null);
GUILayout.Label("<i><color=red>Null or destroyed!</color></i>", new GUILayoutOption[0]);
}
}
}
else
{
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
GUILayout.Label("<color=red><i>No results found!</i></color>", new GUILayoutOption[0]);
}
}

View File

@ -2,10 +2,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.Reflection;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
@ -66,12 +64,19 @@ namespace Explorer
{
var toCache = obj;
#if CPP
if (toCache is Il2CppSystem.Object ilObject)
{
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
}
#else
if (toCache is GameObject || toCache is Transform)
{
toCache = toCache as GameObject ?? (toCache as Transform).gameObject;
}
#endif
var cache = CacheObjectBase.GetCacheObject(toCache);
var cache = CacheFactory.GetTypeAndCacheObject(toCache);
m_searchResults.Add(cache);
}
@ -84,7 +89,7 @@ namespace Explorer
try
{
// helpers
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUILayout.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
{
@ -97,15 +102,15 @@ namespace Explorer
SearchBox();
// results
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label("<b><color=orange>Results </color></b>" + " (" + m_searchResults.Count + ")", null);
GUILayout.Label("<b><color=orange>Results </color></b>" + " (" + m_searchResults.Count + ")", new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.UpperLeft;
int count = m_searchResults.Count;
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
Pages.DrawLimitInputArea();
@ -146,7 +151,7 @@ namespace Explorer
}
else
{
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
GUILayout.Label("<color=red><i>No results found!</i></color>", new GUILayoutOption[0]);
}
GUIUnstrip.EndScrollView();
@ -160,21 +165,21 @@ namespace Explorer
private void SearchBox()
{
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
// ----- GameObject Search -----
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label("<b><color=orange>Search</color></b>", null);
GUILayout.Label("<b><color=orange>Search</color></b>", new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
m_searchInput = GUIUnstrip.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Class Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
ClassFilterToggle(TypeFilter.Object, "Object");
@ -184,15 +189,15 @@ namespace Explorer
GUILayout.EndHorizontal();
if (TypeMode == TypeFilter.Custom)
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("Custom Class:", new GUILayoutOption[] { GUILayout.Width(250) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
m_typeInput = GUILayout.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) });
m_typeInput = GUIUnstrip.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) });
GUILayout.EndHorizontal();
}
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Scene Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
SceneFilterToggle(SceneFilter.Any, "Any", 60);
SceneFilterToggle(SceneFilter.This, "This Scene", 100);
@ -200,7 +205,7 @@ namespace Explorer
SceneFilterToggle(SceneFilter.None, "No Scene", 80);
GUILayout.EndHorizontal();
if (GUILayout.Button("<b><color=cyan>Search</color></b>", null))
if (GUILayout.Button("<b><color=cyan>Search</color></b>", new GUILayoutOption[0]))
{
Search();
}
@ -255,15 +260,23 @@ namespace Explorer
private List<object> FindAllObjectsOfType(string searchQuery, string typeName)
{
#if CPP
Il2CppSystem.Type searchType = null;
#else
Type searchType = null;
#endif
if (TypeMode == TypeFilter.Custom)
{
try
{
if (ReflectionHelpers.GetTypeByName(typeName) is Type t)
{
#if CPP
searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
#else
searchType = t;
#endif
}
else
{
@ -272,7 +285,7 @@ namespace Explorer
}
catch (Exception e)
{
MelonLogger.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
ExplorerCore.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
}
}
else if (TypeMode == TypeFilter.Object)
@ -292,7 +305,7 @@ namespace Explorer
{
if (searchType != null)
{
MelonLogger.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
ExplorerCore.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
}
return new List<object>();
}
@ -301,7 +314,7 @@ namespace Explorer
var allObjectsOfType = Resources.FindObjectsOfTypeAll(searchType);
//MelonLogger.Log("Found count: " + allObjectsOfType.Length);
//ExplorerCore.Log("Found count: " + allObjectsOfType.Length);
int i = 0;
foreach (var obj in allObjectsOfType)
@ -314,7 +327,11 @@ namespace Explorer
}
if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
#if CPP
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
#else
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetType()))
#endif
{
// Transforms shouldn't really be counted as Components, skip them.
// They're more akin to GameObjects.
@ -339,15 +356,18 @@ namespace Explorer
public static bool FilterScene(object obj, SceneFilter filter)
{
GameObject go;
GameObject go = null;
#if CPP
if (obj is Il2CppSystem.Object ilObject)
{
go = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Component>().gameObject;
}
else
#else
if (obj is GameObject || obj is Component)
{
go = (obj as GameObject) ?? (obj as Component).gameObject;
}
#endif
if (!go)
{
@ -385,8 +405,8 @@ namespace Explorer
public static IEnumerable<object> GetInstanceClassScanner()
{
var query = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(ReflectionHelpers.GetTypesSafe)
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
.SelectMany(t => t.TryGetTypes())
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
var flags = BindingFlags.Public | BindingFlags.Static;
var flatFlags = flags | BindingFlags.FlattenHierarchy;
@ -405,7 +425,7 @@ namespace Explorer
if (pi != null)
{
obj = pi.GetValue(null);
obj = pi.GetValue(null, null);
}
else
{
@ -428,7 +448,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

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine;
namespace Explorer
{

158
src/Menu/ResizeDrag.cs Normal file
View File

@ -0,0 +1,158 @@
using System;
#if CPP
using UnhollowerBaseLib;
#endif
using UnityEngine;
namespace Explorer
{
public class ResizeDrag
{
#if CPP
private static bool RESIZE_FAILED = false;
#endif
private static readonly GUIContent gcDrag = new GUIContent("<-- Drag to resize -->");
private static bool isResizing = false;
private static Rect m_currentResize;
private static int m_currentWindow;
public static Rect ResizeWindow(Rect _rect, int ID)
{
#if CPP
if (!RESIZE_FAILED)
{
var origRect = _rect;
try
{
GUILayout.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
#if ML
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
#else
GUILayout.Button(gcDrag.ToString(), new GUILayoutOption[] { GUILayout.Height(15) });
#endif
//var r = GUILayoutUtility.GetLastRect();
var r = Internal_LayoutUtility.GetLastRect();
var mousePos = InputManager.MousePosition;
try
{
var mouse = GUIUnstrip.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
if (r.Contains(mouse) && InputManager.GetMouseButtonDown(0))
{
isResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
else if (!InputManager.GetMouseButton(0))
{
isResizing = false;
}
if (isResizing && ID == m_currentWindow)
{
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
}
}
catch
{
// throw safe Managed exception
throw new Exception("");
}
GUILayout.EndHorizontal();
}
catch (Exception e) when (e.Message.StartsWith("System.ArgumentException"))
{
// suppress
return origRect;
}
catch (Exception e)
{
RESIZE_FAILED = true;
ExplorerCore.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
//ExplorerCore.Log(e.StackTrace);
return origRect;
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
else
{
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Resize window:", new GUILayoutOption[] { GUILayout.Width(100) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>Width:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUIUnstrip.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width -= 5f;
}
if (GUIUnstrip.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width += 5f;
}
GUILayout.Label("<color=cyan>Height:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUIUnstrip.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height -= 5f;
}
if (GUIUnstrip.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height += 5f;
}
GUILayout.EndHorizontal();
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
#else // mono
GUILayout.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
//var r = GUILayoutUtility.GetLastRect();
var r = GUILayoutUtility.GetLastRect();
var mousePos = InputManager.MousePosition;
var mouse = GUIUnstrip.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
if (r.Contains(mouse) && InputManager.GetMouseButtonDown(0))
{
isResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
else if (!InputManager.GetMouseButton(0))
{
isResizing = false;
}
if (isResizing && ID == m_currentWindow)
{
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
}
GUILayout.EndHorizontal();
#endif
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
return _rect;
}
}
}

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnhollowerRuntimeLib;
using UnityEngine;
using Object = UnityEngine.Object;
@ -62,13 +56,13 @@ namespace Explorer
if (!obj)
{
GUILayout.Label("<i><color=red>null</color></i>", null);
GUILayout.Label("<i><color=red>null</color></i>", new GUILayoutOption[0]);
return;
}
// ------ toggle active button ------
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.button.alignment = TextAnchor.UpperLeft;
GUI.color = activeColor;
@ -108,7 +102,7 @@ namespace Explorer
public static void SmallInspectButton(object obj)
{
if (GUILayout.Button("Inspect", null))
if (GUILayout.Button("Inspect", new GUILayoutOption[0]))
{
WindowManager.InspectObject(obj, out bool _);
}

View File

@ -1,16 +1,29 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Explorer
{
public class UIStyles
{
public class Syntax
{
public const string Field_Static = "#8d8dc6";
public const string Field_Instance = "#c266ff";
public const string Method_Static = "#b55b02";
public const string Method_Instance = "#ff8000";
public const string Prop_Static = "#588075";
public const string Prop_Instance = "#55a38e";
public const string Class_Static = "#3a8d71";
public const string Class_Instance = "#2df7b2";
public const string Local = "#a6e9e9";
public const string StructGreen = "#92c470";
}
public static Color LightGreen = new Color(Color.green.r - 0.3f, Color.green.g - 0.3f, Color.green.b - 0.3f);
public static GUISkin WindowSkin

View File

@ -1,21 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MelonLoader;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.SceneManagement;
#if CPP
using UnhollowerRuntimeLib;
#endif
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 +24,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 +43,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,16 +53,16 @@ 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;
}
MelonLogger.Log("Error: Target is null or not a GameObject/Transform!");
ExplorerCore.Log("Error: Target is null or not a GameObject/Transform!");
DestroyWindow();
return false;
}
@ -73,10 +74,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 +88,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()
@ -104,21 +105,21 @@ namespace Explorer
{
if (Target == null)
{
MelonLogger.Log("Target is null!");
ExplorerCore.Log("Target is null!");
DestroyWindow();
return;
}
else if (Target is UnityEngine.Object uObj)
if (Target is UnityEngine.Object uObj)
{
if (!uObj)
{
MelonLogger.Log("Target was destroyed!");
ExplorerCore.Log("Target was destroyed!");
DestroyWindow();
return;
}
}
if (!m_object && !GetObjectAsGameObject())
if (!TargetGO && !GetObjectAsGameObject())
{
throw new Exception("Object is null!");
}
@ -127,22 +128,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();
@ -150,10 +151,13 @@ namespace Explorer
ChildPages.ItemCount = m_children.Length;
// update components
#if CPP
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();
#else
m_components = TargetGO.GetComponents<Component>();
#endif
CompPages.ItemCount = m_components.Length;
}
@ -165,7 +169,7 @@ namespace Explorer
private void DestroyOnException(Exception e)
{
MelonLogger.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
ExplorerCore.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
DestroyWindow();
}
@ -180,9 +184,13 @@ namespace Explorer
}
}
#if CPP
private void ReflectObject(Il2CppSystem.Object obj)
{
var window = WindowManager.InspectObject(obj, out bool created);
#else
private void ReflectObject(object obj)
#endif
{
var window = WindowManager.InspectObject(obj, out bool created, true);
if (created)
{
@ -210,18 +218,18 @@ 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);
GUILayout.BeginHorizontal(null);
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", new GUILayoutOption[0]);
if (m_scene == UnityHelpers.ActiveSceneName)
{
if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
{
ScenePage.Instance.SetTransformTarget(m_object.transform);
ScenePage.Instance.SetTransformTarget(TargetGO.transform);
MainMenu.SetCurrentPage(0);
}
}
@ -231,26 +239,26 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
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);
GUIUnstrip.TextArea(pathlabel, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("Name:", new GUILayoutOption[] { GUILayout.Width(50) });
GUILayout.TextArea(m_name, null);
GUIUnstrip.TextArea(m_name, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
// --- Horizontal Columns section ---
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.BeginVertical(new GUILayoutOption[] { GUILayout.Width(rect.width / 2 - 17) });
TransformList(rect);
@ -270,7 +278,7 @@ namespace Explorer
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
}
catch (Exception e)
@ -281,12 +289,12 @@ namespace Explorer
private void TransformList(Rect m_rect)
{
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
m_transformScroll = GUIUnstrip.BeginScrollView(m_transformScroll);
GUILayout.Label("<b><size=15>Children</size></b>", null);
GUILayout.Label("<b><size=15>Children</size></b>", new GUILayoutOption[0]);
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
ChildPages.DrawLimitInputArea();
if (ChildPages.ItemCount > ChildPages.ItemsPerPage)
@ -294,7 +302,7 @@ namespace Explorer
ChildPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
@ -317,7 +325,7 @@ namespace Explorer
if (!obj)
{
GUILayout.Label("null", null);
GUILayout.Label("null", new GUILayoutOption[0]);
continue;
}
@ -326,7 +334,7 @@ namespace Explorer
}
else
{
GUILayout.Label("<i>None</i>", null);
GUILayout.Label("<i>None</i>", new GUILayoutOption[0]);
}
GUIUnstrip.EndScrollView();
@ -335,11 +343,11 @@ namespace Explorer
private void ComponentList(Rect m_rect)
{
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
m_compScroll = GUIUnstrip.BeginScrollView(m_compScroll);
GUILayout.Label("<b><size=15>Components</size></b>", null);
GUILayout.Label("<b><size=15>Components</size></b>", new GUILayoutOption[0]);
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
CompPages.DrawLimitInputArea();
if (CompPages.ItemCount > CompPages.ItemsPerPage)
@ -347,7 +355,7 @@ namespace Explorer
CompPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
@ -360,6 +368,33 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
var width = m_rect.width / 2 - 135f;
m_addComponentInput = GUIUnstrip.TextField(m_addComponentInput, new GUILayoutOption[] { GUILayout.Width(width) });
if (GUILayout.Button("Add Comp", new GUILayoutOption[0]))
{
if (ReflectionHelpers.GetTypeByName(m_addComponentInput) is Type compType)
{
if (typeof(Component).IsAssignableFrom(compType))
{
#if CPP
TargetGO.AddComponent(Il2CppType.From(compType));
#else
TargetGO.AddComponent(compType);
#endif
}
else
{
ExplorerCore.LogWarning($"Type '{compType.Name}' is not assignable from Component!");
}
}
else
{
ExplorerCore.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)
{
@ -376,18 +411,26 @@ namespace Explorer
if (!component) continue;
var ilType = component.GetIl2CppType();
GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
var type =
#if CPP
component.GetIl2CppType();
#else
component.GetType();
#endif
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(type))
{
#if CPP
BehaviourEnabledBtn(component.TryCast<Behaviour>());
#else
BehaviourEnabledBtn(component as Behaviour);
#endif
}
else
{
GUILayout.Space(26);
GUIUnstrip.Space(26);
}
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
if (GUILayout.Button("<color=cyan>" + type.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
{
ReflectObject(component);
}
@ -439,21 +482,41 @@ namespace Explorer
private void GameObjectControls()
{
GUILayout.BeginVertical(GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
GUILayout.Label("<b><size=15>GameObject Controls</size></b>", null);
if (m_hideControls)
{
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
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();
GUILayout.BeginHorizontal(null);
bool m_active = m_object.activeSelf;
return;
}
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[] { GUILayout.Width(520) });
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
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(new GUILayoutOption[0]);
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>";
@ -467,66 +530,66 @@ namespace Explorer
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
m_setParentInput = GUILayout.TextField(m_setParentInput, null);
m_setParentInput = GUIUnstrip.TextField(m_setParentInput, new GUILayoutOption[0]);
if (GUILayout.Button("Set Parent", new GUILayoutOption[] { GUILayout.Width(80) }))
{
if (GameObject.Find(m_setParentInput) is GameObject newparent)
{
m_object.transform.parent = newparent.transform;
TargetGO.transform.parent = newparent.transform;
}
else
{
MelonLogger.LogWarning($"Could not find gameobject '{m_setParentInput}'");
ExplorerCore.LogWarning($"Could not find gameobject '{m_setParentInput}'");
}
}
if (GUILayout.Button("Detach from parent", new GUILayoutOption[] { GUILayout.Width(160) }))
{
m_object.transform.parent = null;
TargetGO.transform.parent = null;
}
GUILayout.EndHorizontal();
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
m_cachedInput[0] = TranslateControl(TranslateType.Position, ref m_translateAmount, false);
m_cachedInput[1] = TranslateControl(TranslateType.Rotation, ref m_rotateAmount, true);
m_cachedInput[2] = TranslateControl(TranslateType.Scale, ref m_scaleAmount, false);
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("<color=lime>Apply to Transform</color>", null) || m_autoApplyTransform)
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
if (GUILayout.Button("<color=lime>Apply to Transform</color>", new GUILayoutOption[0]) || m_autoApplyTransform)
{
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)
{
UpdateFreeze();
}
}
if (GUILayout.Button("<color=lime>Update from Transform</color>", null) || m_autoUpdateTransform)
if (GUILayout.Button("<color=lime>Update from Transform</color>", new GUILayoutOption[0]) || m_autoUpdateTransform)
{
CacheTransformValues();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?");
BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?");
GUILayout.EndHorizontal();
bool b = m_localContext;
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "red") + ">Use local transform values?</color>", null);
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "orange") + ">Use local transform values?</color>", new GUILayoutOption[0]);
if (b != m_localContext)
{
m_localContext = b;
@ -541,7 +604,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,24 +616,24 @@ 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)
{
string lbl = "<color=";
lbl += value ? "lime" : "red";
lbl += value ? "lime" : "orange";
lbl += $">{message}</color>";
value = GUILayout.Toggle(value, lbl, null);
value = GUILayout.Toggle(value, lbl, new GUILayoutOption[0]);
}
public enum TranslateType
@ -582,11 +645,11 @@ namespace Explorer
private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime)
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
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:
@ -605,7 +668,7 @@ namespace Explorer
Vector3 input = m_cachedInput[(int)mode];
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
@ -619,7 +682,7 @@ namespace Explorer
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
var amountInput = amount.ToString("F3");
amountInput = GUILayout.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
amountInput = GUIUnstrip.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
if (float.TryParse(amountInput, out float f))
{
amount = f;
@ -634,16 +697,16 @@ namespace Explorer
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
{
string s = f.ToString("F3");
s = GUILayout.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
s = GUIUnstrip.TextField(s, new GUILayoutOption[] { GUILayout.Width(60) });
if (float.TryParse(s, out float f2))
{
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

@ -1,12 +1,11 @@
using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MelonLoader;
using UnhollowerBaseLib;
using UnityEngine;
#if CPP
using UnhollowerBaseLib;
#endif
namespace Explorer
{
@ -28,20 +27,33 @@ 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 CPP
if (Target is Il2CppSystem.Object ilObject)
{
var unityObj = ilObject.TryCast<UnityEngine.Object>();
@ -56,26 +68,25 @@ namespace Explorer
}
}
}
m_filter = MemberTypes.All;
m_autoUpdate = true;
Update();
m_autoUpdate = false;
m_filter = MemberTypes.Property;
#else
m_uObj = Target as UnityEngine.Object;
m_component = Target as Component;
#endif
}
public override void Update()
{
if (Target == null)
{
ExplorerCore.Log("Target is null!");
DestroyWindow();
return;
}
else if (Target is UnityEngine.Object uObj)
if (Target is UnityEngine.Object uObj)
{
if (!uObj)
{
ExplorerCore.Log("Target was destroyed!");
DestroyWindow();
return;
}
@ -99,40 +110,46 @@ 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);
}
catch
{
MelonLogger.Log($"Exception getting members for type: {declaringType.FullName}");
ExplorerCore.Log($"Exception getting members for type: {declaringType.FullName}");
continue;
}
object target = Target;
string exception = null;
#if CPP
if (target is Il2CppSystem.Object ilObject)
{
try
@ -144,49 +161,64 @@ namespace Explorer
exception = ReflectionHelpers.ExceptionToString(e);
}
}
#endif
foreach (var member in infos)
{
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method)
// 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 sig = $"{member.DeclaringType.Name}.{member.Name}";
if (_typeAndMemberBlacklist.Any(it => it == sig))
continue;
if (_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it)))
continue;
if (member is MethodInfo mi)
{
var name = $"{member.DeclaringType.Name}.{member.Name}";
AppendParams(mi.GetParameters());
}
else if (member is PropertyInfo pi)
{
AppendParams(pi.GetIndexParameters());
}
// blacklist (should probably make a proper implementation)
if (name == "Type.DeclaringMethod" || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) //|| member.Name.Contains("Il2CppType")
void AppendParams(ParameterInfo[] _args)
{
sig += " (";
foreach (var param in _args)
{
continue;
sig += $"{param.ParameterType.Name} {param.Name}, ";
}
sig += ")";
}
if (member is MethodInfo mi)
{
name += " (";
foreach (var param in mi.GetParameters())
{
name += param.ParameterType.Name + ", ";
}
name += ")";
}
if (names.Contains(name))
{
continue;
}
if (cachedSigs.Contains(sig))
{
continue;
}
try
// ExplorerCore.Log($"Trying to cache member {signature}...");
try
{
var cached = CacheFactory.GetTypeAndCacheObject(member, target);
if (cached != null)
{
var cached = CacheObjectBase.GetCacheObject(member, target);
if (cached != null)
{
names.Add(name);
list.Add(cached);
cached.ReflectionException = exception;
}
}
catch (Exception e)
{
MelonLogger.LogWarning($"Exception caching member {name}!");
MelonLogger.Log(e.ToString());
cachedSigs.Add(sig);
list.Add(cached);
cached.ReflectionException = exception;
}
}
catch (Exception e)
{
ExplorerCore.LogWarning($"Exception caching member {sig}!");
ExplorerCore.Log(e.ToString());
}
}
}
@ -206,20 +238,20 @@ 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);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("<b>Type:</b> <color=cyan>" + TargetType.FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(245f) });
if (m_uObj)
{
GUILayout.Label("Name: " + m_uObj.name, null);
GUILayout.Label("Name: " + m_uObj.name, new GUILayoutOption[0]);
}
GUILayout.EndHorizontal();
if (m_uObj)
{
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("<b>Tools:</b>", new GUILayoutOption[] { GUILayout.Width(80) });
UIHelpers.InstantiateButton(m_uObj);
if (m_component && m_component.gameObject is GameObject obj)
@ -240,12 +272,12 @@ namespace Explorer
UIStyles.HorizontalLine(Color.grey);
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("<b>Search:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
m_search = GUILayout.TextField(m_search, null);
m_search = GUIUnstrip.TextField(m_search, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("<b>Filter:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
FilterToggle(MemberTypes.All, "All");
FilterToggle(MemberTypes.Property, "Properties");
@ -253,7 +285,7 @@ namespace Explorer
FilterToggle(MemberTypes.Method, "Methods");
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label("<b>Values:</b>", new GUILayoutOption[] { GUILayout.Width(75) });
if (GUILayout.Button("Update", new GUILayoutOption[] { GUILayout.Width(100) }))
{
@ -266,12 +298,12 @@ namespace Explorer
GUI.color = Color.white;
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUIUnstrip.Space(10);
Pages.ItemCount = m_cachedMembersFiltered.Length;
// prev/next page buttons
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
Pages.DrawLimitInputArea();
@ -295,11 +327,11 @@ namespace Explorer
scroll = GUIUnstrip.BeginScrollView(scroll);
GUILayout.Space(10);
GUIUnstrip.Space(10);
UIStyles.HorizontalLine(Color.grey);
GUILayout.BeginVertical(GUI.skin.box, null);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
var members = this.m_cachedMembersFiltered;
int start = Pages.CalculateOffsetIndex();
@ -332,19 +364,16 @@ namespace Explorer
{
m_rect = ResizeDrag.ResizeWindow(rect, windowID);
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
}
catch (Il2CppException e)
catch (Exception e) when (e.Message.Contains("in a group with only"))
{
if (!e.Message.Contains("in a group with only"))
{
throw;
}
// suppress
}
catch (Exception e)
{
MelonLogger.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message);
ExplorerCore.LogWarning("Exception drawing ReflectionWindow: " + e.GetType() + ", " + e.Message);
DestroyWindow();
return;
}

View File

@ -1,10 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using UnityEngine;
namespace Explorer
{
@ -56,7 +50,7 @@ namespace Explorer
try
{
GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20));
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red>Close All</color>"))
if (GUIUnstrip.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red>Close All</color>"))
{
foreach (var window in WindowManager.Windows)
{
@ -65,10 +59,10 @@ 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);
GUILayout.BeginVertical(GUIContent.none, GUI.skin.box, null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
int tabPerRow = Mathf.FloorToInt((float)((decimal)m_rect.width / 238));
int rowCount = 0;
@ -78,7 +72,7 @@ namespace Explorer
{
rowCount = 0;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.BeginHorizontal(new GUILayoutOption[0]);
}
rowCount++;
@ -109,7 +103,7 @@ namespace Explorer
}
catch { }
GUILayout.EndArea();
GUIUnstrip.EndArea();
}
catch { }
}

View File

@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
namespace Explorer
@ -17,7 +10,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 +42,11 @@ 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;
}
#if CPP
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, GUIContent.Temp(Title), GUI.skin.window);
#else
m_rect = GUI.Window(windowID, m_rect, WindowFunction, Title);
#endif
}
public void Header()
@ -65,8 +54,12 @@ namespace Explorer
if (!WindowManager.TabView)
{
GUI.DragWindow(new Rect(0, 0, m_rect.width - 90, 20));
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red><b>X</b></color>"))
#if CPP
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), GUIContent.Temp("<color=red><b>X</b></color>"), GUI.skin.button))
#else
if (GUI.Button(new Rect(m_rect.width - 90, 2, 80, 20), "<color=red><b>X</b></color>", GUI.skin.button))
#endif
{
DestroyWindow();
return;

View File

@ -1,14 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Harmony;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
namespace Explorer
{
@ -90,16 +81,20 @@ namespace Explorer
{
createdNew = false;
if (Input.GetKey(KeyCode.LeftShift))
if (InputManager.GetKey(KeyCode.LeftShift))
{
forceReflection = true;
}
#if CPP
Il2CppSystem.Object iObj = null;
if (obj is Il2CppSystem.Object isObj)
{
iObj = isObj;
}
#else
var iObj = obj;
#endif
if (!forceReflection)
{
@ -107,6 +102,7 @@ namespace Explorer
{
bool equals = ReferenceEquals(obj, window.Target);
#if CPP
if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
{
if (iCurrent.GetIl2CppType().FullName != iTarget.GetIl2CppType().FullName)
@ -119,6 +115,7 @@ namespace Explorer
equals = iCurrent.Pointer == iTarget.Pointer;
}
#endif
if (equals)
{
@ -143,7 +140,7 @@ namespace Explorer
{
if (!TabView)
{
GUI.BringWindowToFront(window.windowID);
GUIUnstrip.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
}
else
@ -174,7 +171,7 @@ namespace Explorer
{
get
{
if (!CppExplorer.ShowMenu)
if (!ExplorerCore.ShowMenu)
{
return false;
}
@ -192,7 +189,8 @@ namespace Explorer
private static bool RectContainsMouse(Rect rect)
{
return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
var mousePos = InputManager.MousePosition;
return rect.Contains(new Vector2(mousePos.x, Screen.height - mousePos.y));
}
public static int NextWindowID()

View File

@ -2,19 +2,22 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Explorer;
#if ML
using MelonLoader;
[assembly: MelonInfo(typeof(CppExplorer), CppExplorer.NAME, CppExplorer.VERSION, CppExplorer.AUTHOR)]
[assembly: MelonInfo(typeof(ExplorerMelonMod), "Explorer", ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
#endif
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle(CppExplorer.NAME)]
[assembly: AssemblyTitle(ExplorerCore.NAME)]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany(CppExplorer.AUTHOR)]
[assembly: AssemblyProduct(CppExplorer.NAME)]
[assembly: AssemblyCompany(ExplorerCore.AUTHOR)]
[assembly: AssemblyProduct(ExplorerCore.NAME)]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

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

@ -0,0 +1,182 @@
using System.Collections;
using System.Collections.Generic;
using System;
using UnityEngine;
using System.Reflection;
using System.Collections.Specialized;
// used to test multiple generic constraints
public class TestGeneric : IComparable<string>
{
public TestGeneric() { }
public int CompareTo(string other) => throw new NotImplementedException();
}
[Flags]
public enum TestFlags
{
Red,
Green,
Blue
}
// test non-flags weird enum
public enum WeirdEnum
{
First = 1,
Second,
Third = 2,
Fourth,
Fifth
}
namespace Explorer.Tests
{
public class TestClass
{
public static TestFlags testFlags = TestFlags.Blue | TestFlags.Green;
public static WeirdEnum testWeird = WeirdEnum.First;
public static int testBitmask;
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
private static TestClass m_instance;
#if CPP
public static Il2CppSystem.Collections.Generic.HashSet<string> ILHashSetTest;
#endif
public TestClass()
{
#if CPP
ILHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
ILHashSetTest.Add("1");
ILHashSetTest.Add("2");
ILHashSetTest.Add("3");
#endif
testBitmask = 1 | 2;
}
public static int StaticProperty => 5;
public static int StaticField = 5;
public int NonStaticField;
public static string TestGeneric<C, T>(string arg0) where C : Component where T : TestGeneric, IComparable<string>
{
return $"C: '{typeof(C).FullName}', T: '{typeof(T).FullName}', arg0: '{arg0}'";
}
public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2)
{
arg2 = "this is arg2";
return $"T: '{typeof(T).FullName}', ref arg0: '{arg0}', in arg1: '{arg1}', out arg2: '{arg2}'";
}
//// this type of generic is not supported, due to requiring a non-primitive argument.
//public static T TestDifferentGeneric<T>(T obj) where T : Component
//{
// return obj;
//}
// 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"
};
// 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

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

View File

@ -0,0 +1,680 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngineInternal;
using UnhollowerRuntimeLib;
namespace Explorer.UnstripInternals
{
public class Internal
{
#region Properties
public static int s_ScrollControlId;
public static bool ScrollFailed = false;
public static bool ManualUnstripFailed = false;
public static GenericStack ScrollStack => m_scrollStack ?? GetScrollStack();
public static PropertyInfo m_scrollViewStatesInfo;
public static GenericStack m_scrollStack;
public static Dictionary<int, Il2CppSystem.Object> StateCache => m_stateCacheDict ?? GetStateCacheDict();
public static Dictionary<int, Il2CppSystem.Object> m_stateCacheDict;
public static GUIStyle SpaceStyle => m_spaceStyle ?? GetSpaceStyle();
public static GUIStyle m_spaceStyle;
public static DateTime nextScrollStepTime;
public static MethodInfo ScreenToGuiPointMethod;
public static bool m_screenToGuiAttemped;
public static MethodInfo m_bringWindowToFrontMethod;
public static bool m_bringWindowFrontAttempted;
private static GenericStack GetScrollStack()
{
if (m_scrollViewStatesInfo == null)
{
if (typeof(GUI).GetProperty("scrollViewStates", ReflectionHelpers.CommonFlags) is PropertyInfo scrollStatesInfo)
{
m_scrollViewStatesInfo = scrollStatesInfo;
}
else if (typeof(GUI).GetProperty("s_ScrollViewStates", ReflectionHelpers.CommonFlags) is PropertyInfo s_scrollStatesInfo)
{
m_scrollViewStatesInfo = s_scrollStatesInfo;
}
}
if (m_scrollViewStatesInfo?.GetValue(null, null) is GenericStack stack)
{
m_scrollStack = stack;
}
else
{
m_scrollStack = new GenericStack();
}
return m_scrollStack;
}
private static Dictionary<int, Il2CppSystem.Object> GetStateCacheDict()
{
if (m_stateCacheDict == null)
{
try
{
var type = ReflectionHelpers.GetTypeByName("UnityEngine.GUIStateObjects");
m_stateCacheDict = type.GetProperty("s_StateCache")
.GetValue(null, null)
as Dictionary<int, Il2CppSystem.Object>;
if (m_stateCacheDict == null) throw new Exception();
}
catch
{
m_stateCacheDict = new Dictionary<int, Il2CppSystem.Object>();
}
}
return m_stateCacheDict;
}
private static GUIStyle GetSpaceStyle()
{
try
{
m_spaceStyle = typeof(GUILayoutUtility)
.GetProperty("s_SpaceStyle")
.GetValue(null, null)
.Il2CppCast(typeof(GUIStyle))
as GUIStyle;
if (m_spaceStyle == null) throw new Exception();
}
catch { }
if (m_spaceStyle == null)
{
m_spaceStyle = new GUIStyle();
}
m_spaceStyle.stretchWidth = false;
return m_spaceStyle;
}
#endregion
#region GUILayout Methods
public static string TextField(string text, GUILayoutOption[] options)
{
text = text ?? "";
int controlID = GUIUtility.GetControlID(FocusType.Keyboard);
GUIContent guicontent = GUIContent.Temp(text);
bool flag = GUIUtility.keyboardControl != controlID;
if (flag)
{
guicontent = GUIContent.Temp(text);
}
else
{
guicontent = GUIContent.Temp(text);
// guicontent = GUIContent.Temp(text + GUIUtility.compositionString);
}
Rect rect = Internal_LayoutUtility.GetRect(guicontent, GUI.skin.textField, options);
bool flag2 = GUIUtility.keyboardControl == controlID;
if (flag2)
{
guicontent = GUIContent.Temp(text);
}
DoTextField(rect, controlID, guicontent, false, -1, GUI.skin.textField);
return guicontent.text;
}
internal static void DoTextField(Rect position, int id, GUIContent content, bool multiline, int maxLength, GUIStyle style)
{
if (GetStateObject(Il2CppType.Of<TextEditor>(), id).TryCast<TextEditor>() is TextEditor textEditor)
{
if (maxLength >= 0 && content.text.Length > maxLength)
{
content.text = content.text.Substring(0, maxLength);
}
textEditor.m_Content.text = content.text;
textEditor.SaveBackup();
textEditor.position = position;
textEditor.style = style;
textEditor.multiline = multiline;
textEditor.controlID = id;
textEditor.DetectFocusChange();
GUI.HandleTextFieldEventForDesktop(position, id, content, multiline, maxLength, style, textEditor);
textEditor.UpdateScrollOffsetIfNeeded(Event.current);
}
}
public static bool DoRepeatButton(GUIContent content, GUIStyle style, GUILayoutOption[] options)
{
return GUI.DoRepeatButton(Internal_LayoutUtility.GetRect(content, style, options), content, style, FocusType.Passive);
}
public static void Space(float pixels)
{
if (GUILayoutUtility.current.topLevel.isVertical)
Internal_LayoutUtility.GetRect(0, pixels, SpaceStyle, new GUILayoutOption[] { GUILayout.Height(pixels) });
else
Internal_LayoutUtility.GetRect(pixels, 0, SpaceStyle, new GUILayoutOption[] { GUILayout.Width(pixels) });
if (Event.current.type == EventType.Layout)
{
GUILayoutUtility.current.topLevel.entries[GUILayoutUtility.current.topLevel.entries.Count - 1].consideredForMargin = false;
}
}
public static Vector2 ScreenToGUIPoint(Vector2 screenPoint)
{
if (!m_screenToGuiAttemped)
{
m_screenToGuiAttemped = true;
ScreenToGuiPointMethod = typeof(GUIUtility).GetMethod("ScreenToGUIPoint");
}
if (ScreenToGuiPointMethod == null)
{
throw new Exception("Couldn't get method 'GUIUtility.ScreenToGUIPoint'!");
}
return (Vector2)ScreenToGuiPointMethod.Invoke(null, new object[] { screenPoint });
}
public static void BringWindowToFront(int id)
{
if (!m_bringWindowFrontAttempted)
{
m_bringWindowFrontAttempted = true;
m_bringWindowToFrontMethod = typeof(GUI).GetMethod("BringWindowToFront");
}
if (m_bringWindowToFrontMethod == null)
{
throw new Exception("Couldn't get method 'GUIUtility.BringWindowToFront'!");
}
m_bringWindowToFrontMethod.Invoke(null, new object[] { id });
}
public static void BeginArea(Rect screenRect, GUIContent content, GUIStyle style)
{
var g = BeginLayoutArea(style, typeof(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);
}
BeginGroup(g.rect, content, style);
}
internal static GUILayoutGroup BeginLayoutArea(GUIStyle style, Type layoutType)
{
EventType type = Event.current.type;
GUILayoutGroup guilayoutGroup;
if (type != EventType.Used && type != EventType.Layout)
{
guilayoutGroup = GUILayoutUtility.current.windows.GetNext().TryCast<GUILayoutGroup>();
guilayoutGroup.ResetCursor();
}
else
{
guilayoutGroup = (GUILayoutGroup)Activator.CreateInstance(layoutType);
guilayoutGroup.style = style;
GUILayoutUtility.current.windows.Add(guilayoutGroup);
}
GUILayoutUtility.current.layoutGroups.Push(guilayoutGroup);
GUILayoutUtility.current.topLevel = guilayoutGroup;
return guilayoutGroup;
}
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 EndArea()
{
if (Event.current.type == EventType.Used)
return;
GUILayoutUtility.current.layoutGroups.Pop();
GUILayoutUtility.current.topLevel = GUILayoutUtility.current.layoutGroups.Peek().TryCast<GUILayoutGroup>();
GUI.EndGroup();
}
#endregion
#region Scrolling
private static Il2CppSystem.Object GetStateObject(Il2CppSystem.Type type, int controlID)
{
Il2CppSystem.Object obj;
if (StateCache.ContainsKey(controlID))
{
obj = StateCache[controlID];
}
else
{
obj = Il2CppSystem.Activator.CreateInstance(type);
StateCache.Add(controlID, obj);
}
return obj;
}
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
{
// First, just try normal way, may not have been stripped or was unstripped successfully.
if (!ScrollFailed)
{
try
{
return GUILayout.BeginScrollView(scroll, options);
}
catch
{
ScrollFailed = true;
}
}
// Try manual implementation.
if (!ManualUnstripFailed)
{
try
{
return BeginScrollView_ImplLayout(scroll,
false,
false,
GUI.skin.horizontalScrollbar,
GUI.skin.verticalScrollbar,
GUI.skin.scrollView,
options);
}
catch (Exception e)
{
ExplorerCore.Log("Exception on manual BeginScrollView: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
ManualUnstripFailed = true;
}
}
// Sorry! No scrolling for you.
return scroll;
}
internal static void EndScrollView(bool handleScrollWheel)
{
// Only end the scroll view for the relevant BeginScrollView option, if any.
if (!ScrollFailed)
{
GUILayout.EndScrollView();
}
else if (!ManualUnstripFailed)
{
GUILayoutUtility.EndLayoutGroup();
if (ScrollStack.Count <= 0) return;
var state = ScrollStack.Peek().TryCast<ScrollViewState>();
var scrollExt = Internal_ScrollViewState.FromPointer(state.Pointer);
if (scrollExt == null) throw new Exception("Could not get scrollExt!");
GUIClip.Pop();
ScrollStack.Pop();
var position = scrollExt.position;
if (handleScrollWheel && Event.current.type == EventType.ScrollWheel && position.Contains(Event.current.mousePosition))
{
var pos = scrollExt.scrollPosition;
pos.x = Mathf.Clamp(scrollExt.scrollPosition.x + Event.current.delta.x * 20f, 0f, scrollExt.viewRect.width - scrollExt.visibleRect.width);
pos.y = Mathf.Clamp(scrollExt.scrollPosition.y + Event.current.delta.y * 20f, 0f, scrollExt.viewRect.height - scrollExt.visibleRect.height);
if (scrollExt.scrollPosition.x < 0f)
{
pos.x = 0f;
}
if (pos.y < 0f)
{
pos.y = 0f;
}
scrollExt.apply = true;
Event.current.Use();
}
}
}
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
{
var guiscrollGroup = GUILayoutUtility.BeginLayoutGroup(background, null, Il2CppType.Of<GUIScrollGroup>())
.TryCast<GUIScrollGroup>();
EventType type = Event.current.type;
if (type == EventType.Layout)
{
guiscrollGroup.resetCoords = true;
guiscrollGroup.isVertical = true;
guiscrollGroup.stretchWidth = 1;
guiscrollGroup.stretchHeight = 1;
guiscrollGroup.verticalScrollbar = verticalScrollbar;
guiscrollGroup.horizontalScrollbar = horizontalScrollbar;
guiscrollGroup.needsVerticalScrollbar = alwaysShowVertical;
guiscrollGroup.needsHorizontalScrollbar = alwaysShowHorizontal;
guiscrollGroup.ApplyOptions(options);
}
return BeginScrollView_Impl(guiscrollGroup.rect,
scrollPosition,
new Rect(0f, 0f, guiscrollGroup.clientWidth, guiscrollGroup.clientHeight),
alwaysShowHorizontal,
alwaysShowVertical,
horizontalScrollbar,
verticalScrollbar,
background
);
}
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
{
// GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive);
var scrollViewState = GetStateObject(Il2CppType.Of<ScrollViewState>(), controlID)
.TryCast<ScrollViewState>();
if (scrollViewState == null)
return scrollPosition;
var scrollExt = Internal_ScrollViewState.FromPointer(scrollViewState.Pointer);
if (scrollExt == null)
return scrollPosition;
bool apply = scrollExt.apply;
if (apply)
{
scrollPosition = scrollExt.scrollPosition;
scrollExt.apply = false;
}
scrollExt.position = position;
scrollExt.scrollPosition = scrollPosition;
scrollExt.visibleRect = scrollExt.viewRect = viewRect;
var rect = scrollExt.visibleRect;
rect.width = position.width;
rect.height = position.height;
ScrollStack.Push(scrollViewState);
Rect screenRect = new Rect(position.x, position.y, position.width, position.height);
EventType type = Event.current.type;
if (type != EventType.Layout)
{
if (type != EventType.Used)
{
bool flag = alwaysShowVertical;
bool flag2 = alwaysShowHorizontal;
if (flag2 || viewRect.width > screenRect.width)
{
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
flag2 = true;
}
if (flag || viewRect.height > screenRect.height)
{
rect.width = position.width - verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
screenRect.width -= verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
flag = true;
if (!flag2 && viewRect.width > screenRect.width)
{
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
flag2 = true;
}
}
if (Event.current.type == EventType.Repaint && background != GUIStyle.none)
{
background.Draw(position, position.Contains(Event.current.mousePosition), false, flag2 && flag, false);
}
if (flag2 && horizontalScrollbar != GUIStyle.none)
{
scrollPosition.x = HorizontalScroll(
new Rect(
position.x,
position.yMax - horizontalScrollbar.fixedHeight,
screenRect.width,
horizontalScrollbar.fixedHeight),
scrollPosition.x,
Mathf.Min(screenRect.width, viewRect.width),
0f,
viewRect.width,
horizontalScrollbar
);
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
scrollPosition.x = ((horizontalScrollbar == GUIStyle.none)
? Mathf.Clamp(scrollPosition.x, 0f, Mathf.Max(viewRect.width - position.width, 0f))
: 0f);
}
if (flag && verticalScrollbar != GUIStyle.none)
{
scrollPosition.y = VerticalScroll(
new Rect(
screenRect.xMax + (float)verticalScrollbar.margin.left,
screenRect.y,
verticalScrollbar.fixedWidth,
screenRect.height),
scrollPosition.y,
Mathf.Min(screenRect.height, viewRect.height),
0f,
viewRect.height,
verticalScrollbar
);
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
scrollPosition.y = ((verticalScrollbar == GUIStyle.none)
? Mathf.Clamp(scrollPosition.y, 0f, Mathf.Max(viewRect.height - position.height, 0f))
: 0f);
}
}
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
}
GUIClip.Push(screenRect,
new Vector2(
Mathf.Round(-scrollPosition.x - viewRect.x),
Mathf.Round(-scrollPosition.y - viewRect.y)),
Vector2.zero,
false
);
return scrollPosition;
}
public static float HorizontalScroll(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
{
return Scroller(position, value, size, leftValue, rightValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "leftbutton"),
GUI.skin.GetStyle(style.name + "rightbutton"),
true);
}
public static float VerticalScroll(Rect position, float value, float size, float topValue, float bottomValue, GUIStyle style)
{
return Scroller(position, value, size, topValue, bottomValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "upbutton"),
GUI.skin.GetStyle(style.name + "downbutton"),
false);
}
private static float Scroller(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider,
GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
{
GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
Rect position2;
Rect rect;
Rect rect2;
if (horiz)
{
position2 = new Rect(position.x + leftButton.fixedWidth,
position.y,
position.width - leftButton.fixedWidth - rightButton.fixedWidth,
position.height);
rect = new Rect(position.x, position.y, leftButton.fixedWidth, position.height);
rect2 = new Rect(position.xMax - rightButton.fixedWidth, position.y, rightButton.fixedWidth, position.height);
}
else
{
position2 = new Rect(position.x,
position.y + leftButton.fixedHeight,
position.width,
position.height - leftButton.fixedHeight - rightButton.fixedHeight);
rect = new Rect(position.x, position.y, position.width, leftButton.fixedHeight);
rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight);
}
value = Slider(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
bool flag = Event.current.type == EventType.MouseUp;
if (ScrollerRepeatButton(controlID, rect, leftButton))
{
value -= 10f * ((leftValue >= rightValue) ? -1f : 1f);
}
if (ScrollerRepeatButton(controlID, rect2, rightButton))
{
value += 10f * ((leftValue >= rightValue) ? -1f : 1f);
}
if (flag && Event.current.type == EventType.Used)
{
s_ScrollControlId = 0;
}
if (leftValue < rightValue)
{
value = Mathf.Clamp(value, leftValue, rightValue - size);
}
else
{
value = Mathf.Clamp(value, rightValue, leftValue - size);
}
return value;
}
public static float Slider(Rect position, float value, float size, float start, float end, GUIStyle slider,
GUIStyle thumb, bool horiz, int id)
{
if (id == 0)
{
id = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
}
var sliderHandler = new Internal_SliderHandler(position, value, size, start, end, slider, thumb, horiz, id);
return sliderHandler.Handle();
}
private static bool ScrollerRepeatButton(int scrollerID, Rect rect, GUIStyle style)
{
bool result = false;
if (GUI.DoRepeatButton(rect, GUIContent.none, style, FocusType.Passive))
{
bool flag = s_ScrollControlId != scrollerID;
s_ScrollControlId = scrollerID;
if (flag)
{
result = true;
nextScrollStepTime = DateTime.Now.AddMilliseconds(250.0);
}
else if (DateTime.Now >= nextScrollStepTime)
{
result = true;
nextScrollStepTime = DateTime.Now.AddMilliseconds(30.0);
}
if (Event.current.type == EventType.Repaint)
{
GUI.InternalRepaintEditorWindow();
}
}
return result;
}
#endregion
}
#region Extensions
public static class Extensions
{
public static Rect Unstripped_GetLast(this GUILayoutGroup group)
{
Rect result;
if (group.m_Cursor > 0 && group.m_Cursor <= group.entries.Count)
{
GUILayoutEntry guilayoutEntry = group.entries[group.m_Cursor - 1];
result = guilayoutEntry.rect;
}
else
{
result = GUILayoutEntry.kDummyRect;
}
return result;
}
}
#endregion
}
#endif

View File

@ -0,0 +1,92 @@
#if CPP
using UnityEngine;
using Explorer.UnstripInternals;
namespace Explorer
{
public class Internal_LayoutUtility
{
public static Rect GetRect(float width, float height, GUIStyle style, params GUILayoutOption[] options)
{
switch (Event.current.type)
{
case EventType.Layout:
GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(width, width, height, height, 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, params GUILayoutOption[] options)
{
return DoGetRect(content, style, options);
}
static Rect DoGetRect(GUIContent content, GUIStyle style, GUILayoutOption[] options)
{
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.Unstripped_GetLast();
}
else
{
last = GUILayoutUtility.kDummyRect;
}
return last;
}
}
}
#endif

View File

@ -0,0 +1,29 @@
#if CPP
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Explorer
{
public class Internal_ScrollViewState
{
public Rect position;
public Rect visibleRect;
public Rect viewRect;
public Vector2 scrollPosition;
public bool apply;
public static Dictionary<IntPtr, Internal_ScrollViewState> Dict = new Dictionary<IntPtr, Internal_ScrollViewState>();
public static Internal_ScrollViewState FromPointer(IntPtr ptr)
{
if (!Dict.ContainsKey(ptr))
{
Dict.Add(ptr, new Internal_ScrollViewState());
}
return Dict[ptr];
}
}
}
#endif

View File

@ -1,15 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
#if CPP
using System;
using UnhollowerRuntimeLib;
using UnityEngine;
using Explorer.UnstripInternals;
using Il2CppSystem.Reflection;
namespace Explorer
{
public struct SliderHandlerUnstrip
public struct Internal_SliderHandler
{
public static int ScrollTroughSide
{
get
{
if (!m_getScrollTroughSideFailed)
{
try
{
return GUI.scrollTroughSide;
}
catch
{
m_getScrollTroughSideFailed = true;
}
}
return m_manualScrollTrough;
}
set
{
if (!m_setScrollTroughSideFailed)
{
try
{
GUI.scrollTroughSide = value;
return;
}
catch
{
m_setScrollTroughSideFailed = true;
}
}
m_manualScrollTrough = value;
}
}
private static bool m_getScrollTroughSideFailed;
private static bool m_setScrollTroughSideFailed;
private static int m_manualScrollTrough;
private readonly Rect position;
private readonly float currentValue;
private readonly float size;
@ -20,7 +59,8 @@ namespace Explorer
private readonly bool horiz;
private readonly int id;
public SliderHandlerUnstrip(Rect position, float currentValue, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
public Internal_SliderHandler(Rect position, float currentValue, float size, float start,
float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
{
this.position = position;
this.currentValue = currentValue;
@ -67,7 +107,7 @@ namespace Explorer
}
else
{
GUI.scrollTroughSide = 0;
ScrollTroughSide = 0;
GUIUtility.hotControl = this.id;
this.CurrentEvent().Use();
if (this.ThumbSelectionRect().Contains(this.CurrentEvent().mousePosition))
@ -80,9 +120,10 @@ namespace Explorer
GUI.changed = true;
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(250.0);
GUI.scrollTroughSide = this.CurrentScrollTroughSide();
var ext = Internal_SliderState.FromPointer(GetSliderState().Pointer);
ext.isDragging = false;
Internal.nextScrollStepTime = DateTime.Now.AddMilliseconds(250.0);
ScrollTroughSide = this.CurrentScrollTroughSide();
result = this.PageMovementValue();
}
else
@ -105,8 +146,8 @@ namespace Explorer
}
else
{
SliderState sliderState = this.SliderState();
if (!sliderState.isDragging)
var ext = Internal_SliderState.FromPointer(GetSliderState().Pointer);
if (!ext.isDragging)
{
result = this.currentValue;
}
@ -114,8 +155,8 @@ namespace Explorer
{
GUI.changed = true;
this.CurrentEvent().Use();
float num = this.MousePosition() - sliderState.dragStartPos;
float value = sliderState.dragStartValue + num / this.ValuesPerPixel();
float num = this.MousePosition() - ext.dragStartPos;
float value = ext.dragStartValue + num / this.ValuesPerPixel();
result = this.Clamp(value);
}
}
@ -146,7 +187,7 @@ namespace Explorer
}
else if (this.ThumbRect().Contains(this.CurrentEvent().mousePosition))
{
if (GUI.scrollTroughSide != 0)
if (ScrollTroughSide != 0)
{
GUIUtility.hotControl = 0;
}
@ -155,20 +196,20 @@ namespace Explorer
else
{
GUI.InternalRepaintEditorWindow();
if (SystemClock.now < GUI.nextScrollStepTime)
if (DateTime.Now < Internal.nextScrollStepTime)
{
result = this.currentValue;
}
else if (this.CurrentScrollTroughSide() != GUI.scrollTroughSide)
else if (this.CurrentScrollTroughSide() != ScrollTroughSide)
{
result = this.currentValue;
}
else
{
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(30.0);
Internal.nextScrollStepTime = DateTime.Now.AddMilliseconds(30.0);
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
Internal_SliderState.FromPointer(GetSliderState().Pointer).isDragging = false;
GUI.changed = true;
result = this.PageMovementValue();
}
@ -264,15 +305,15 @@ namespace Explorer
private void StartDraggingWithValue(float dragStartValue)
{
SliderState sliderState = this.SliderState();
sliderState.dragStartPos = this.MousePosition();
sliderState.dragStartValue = dragStartValue;
sliderState.isDragging = true;
var ext = Internal_SliderState.FromPointer(GetSliderState().Pointer);
ext.dragStartPos = this.MousePosition();
ext.dragStartValue = dragStartValue;
ext.isDragging = true;
}
private SliderState SliderState()
private SliderState GetSliderState()
{
return (SliderState)GUIUtility.GetStateObject(Il2CppType.Of<SliderState>(), this.id).TryCast<SliderState>();
return GUIUtility.GetStateObject(Il2CppType.Of<SliderState>(), this.id).TryCast<SliderState>();
}
private Rect ThumbRect()
@ -369,3 +410,4 @@ namespace Explorer
}
}
#endif

View File

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Explorer
{
public class Internal_SliderState
{
public float dragStartPos;
public float dragStartValue;
public bool isDragging;
public static Dictionary<IntPtr, Internal_SliderState> Dict = new Dictionary<IntPtr, Internal_SliderState>();
public static Internal_SliderState FromPointer(IntPtr ptr)
{
if (!Dict.ContainsKey(ptr))
{
Dict.Add(ptr, new Internal_SliderState());
}
return Dict[ptr];
}
}
}

View File

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

View File

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

View File

@ -1,108 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using UnhollowerBaseLib;
namespace Explorer
{
public class ResizeDrag
{
private static bool RESIZE_FAILED = false;
private static readonly GUIContent gcDrag = new GUIContent("<-- Drag to resize -->");
private static bool isResizing = false;
private static Rect m_currentResize;
private static int m_currentWindow;
public static Rect ResizeWindow(Rect _rect, int ID)
{
if (!RESIZE_FAILED)
{
var origRect = _rect;
try
{
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
//var r = GUILayoutUtility.GetLastRect();
var r = GUIUnstrip.GetLastRect();
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
if (r.Contains(mouse) && Input.GetMouseButtonDown(0))
{
isResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
else if (!Input.GetMouseButton(0))
{
isResizing = false;
}
if (isResizing && ID == m_currentWindow)
{
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
}
GUILayout.EndHorizontal();
}
catch (Il2CppException e) when (e.Message.StartsWith("System.ArgumentException"))
{
// suppress
return origRect;
}
catch (Exception e)
{
RESIZE_FAILED = true;
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
return origRect;
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
else
{
GUILayout.BeginHorizontal(GUI.skin.box, null);
GUILayout.Label("Resize window:", new GUILayoutOption[] { GUILayout.Width(100) });
GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>Width:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width -= 5f;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.width += 5f;
}
GUILayout.Label("<color=cyan>Height:</color>", new GUILayoutOption[] { GUILayout.Width(60) });
if (GUILayout.RepeatButton("-", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height -= 5f;
}
if (GUILayout.RepeatButton("+", new GUILayoutOption[] { GUILayout.Width(20) }))
{
_rect.height += 5f;
}
GUILayout.EndHorizontal();
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
return _rect;
}
}
}