mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-22 08:32:51 +08:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
8d648fec43 | |||
835a81765e | |||
51ed936e30 | |||
ac16587cdc | |||
1e1eaa6c38 | |||
af17ae82c6 | |||
e23341c2b1 | |||
080bde4a63 | |||
1d739a1936 | |||
a927b5ed21 | |||
642c97812c | |||
e43d3579de | |||
6ea435deee | |||
94f749342d | |||
0769b7ef23 | |||
5086dcc82b | |||
56abd38e92 | |||
a7e6ae87df | |||
b5c584bb02 | |||
c8a3aecdf4 | |||
33c2378f41 | |||
38aafa7e5b | |||
4bb0811b2c | |||
4aefe1c5a3 | |||
c228d29707 | |||
56d1507aff | |||
72d31eaa64 | |||
4e8b84b67e | |||
5b94e31a12 | |||
692a37635e | |||
9cb1cea025 | |||
e13f198815 | |||
9a059c1056 | |||
ffb6cad8c2 | |||
d0a4863139 | |||
bb8837d58c | |||
a236b272c1 | |||
18de1eaf1c | |||
b1264c6912 | |||
9836566e55 | |||
d20461fa0e | |||
72ec34090d | |||
883a8705c3 | |||
6adaaf5500 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*/mcs-unity*
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
65
README.md
65
README.md
@ -1,4 +1,4 @@
|
||||
# CppExplorer []()
|
||||
# CppExplorer [](https://github.com/HerpDerpinstine/MelonLoader)
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
|
||||
@ -14,18 +14,10 @@
|
||||
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
|
||||
</p>
|
||||
|
||||
### Known issue
|
||||
Some games are experiencing `MissingMethodException`s or exceptions about failed unstripping, which prevent the CppExplorer menu from showing properly or at all. This is a bug with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower) and there isn't much I can do about it myself.
|
||||
|
||||
If you're familiar with C# and Unity, one possibility for now is making a fork of this repo and manually fixing all the broken methods to ones which aren't broken (if possible). There may be another overload of the same method which wasn't stripped or was unstripped successfully, which you can use instead.
|
||||
|
||||
## Features
|
||||
* Scene hierarchy explorer
|
||||
* Search loaded assets with filters
|
||||
* Traverse and manipulate GameObjects
|
||||
* Generic Reflection inspector
|
||||
* C# REPL Console
|
||||
* Inspect-under-mouse
|
||||
### Known issues
|
||||
* CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
|
||||
* Reflection may fail with certain types (eg. `Nullable<T>`, some Dictionary types, etc). Please see [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower#known-issues) for more details.
|
||||
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
|
||||
|
||||
## How to install
|
||||
|
||||
@ -38,15 +30,35 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
|
||||
## 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 CppExplorer, which is generated the first time you run it.
|
||||
|
||||
This config is generated to `Mods\CppExplorer\config.xml`. Edit the config while the game is closed if you wish to change it.
|
||||
|
||||
`Main_Menu_Toggle` (KeyCode)
|
||||
* Sets the keybinding for the Main Menu toggle (show/hide all CppExplorer windows)
|
||||
* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes.
|
||||
* Default: `F7`
|
||||
|
||||
`Default_Window_Size` (Vector2)
|
||||
* Sets the default width and height for all CppExplorer windows when created.
|
||||
* `x` is width, `y` is height.
|
||||
* Default: `<x>550</x> <y>700</y>`
|
||||
|
||||
## Features
|
||||
[](overview.png)
|
||||
|
||||
<i>An overview of the different CppExplorer menus.</i>
|
||||
|
||||
### 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/2b0q0jL.png)
|
||||
|
||||
### Inspectors
|
||||
|
||||
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
|
||||
@ -60,30 +72,22 @@ CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Re
|
||||
* Allows you to see the children and components on a GameObject.
|
||||
* Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
|
||||
|
||||
[](https://i.imgur.com/JTxqlx4.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/iq92m0l.png)
|
||||
|
||||
### Object Search
|
||||
|
||||
* You can search for an `UnityEngine.Object` with the Object Search feature.
|
||||
* Filter by name, type, etc.
|
||||
* For GameObjects and Transforms you can filter which scene they are found in too.
|
||||
|
||||
[](https://i.imgur.com/lK2RthM.png)
|
||||
|
||||
### C# REPL console
|
||||
|
||||
* A simple C# REPL console, allows you to execute a method body on the fly.
|
||||
|
||||
[](https://i.imgur.com/5U4D1a8.png)
|
||||
|
||||
### Inspect-under-mouse
|
||||
|
||||
* Press Shift+RMB (Right Mouse Button) while the CppExplorer menu is open to begin Inspect-Under-Mouse.
|
||||
@ -98,10 +102,21 @@ CppExplorer can force the mouse to be visible and unlocked when the menu is open
|
||||
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
|
||||
* You can create your own plugin using one of the two plugins above as an example. Usually only a few simple Harmony patches are needed to fix the problem.
|
||||
|
||||
## Building
|
||||
|
||||
If you'd like to build this yourself, everything you need (other than MelonLoader) is included with this repository, there is no need for recursive cloning etc.
|
||||
|
||||
1. Install MelonLoader for your game.
|
||||
2. Open the `src\CppExplorer.csproj` file in a text editor.
|
||||
3. Scroll down until you see the `<ItemGroup>` containing the References.
|
||||
4. Fix all of the paths in the `..\Steam\` directory for your game (use the full path if you need to).
|
||||
5. Open the `src\CppExplorer.sln` project and build it.
|
||||
6. The dll is built to the `Release\` folder in the root of the repository.
|
||||
|
||||
## Credits
|
||||
|
||||
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
overview.png
Normal file
BIN
overview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 523 KiB |
@ -1,34 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheDictionary : CacheObjectBase
|
||||
{
|
||||
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
//base.Init();
|
||||
|
||||
Value = "Unsupported";
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
//base.UpdateValue();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label("<color=red>Dictionary (unsupported)</color>", null);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,223 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheMethod : CacheObjectBase
|
||||
{
|
||||
private bool m_evaluated = false;
|
||||
private CacheObjectBase m_cachedReturnValue;
|
||||
|
||||
private bool m_isEvaluating;
|
||||
private ParameterInfo[] m_arguments;
|
||||
private string[] m_argumentInput;
|
||||
|
||||
public bool HasParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_hasParams == null)
|
||||
{
|
||||
m_hasParams = (MemInfo as MethodInfo).GetParameters().Length > 0;
|
||||
}
|
||||
return (bool)m_hasParams;
|
||||
}
|
||||
}
|
||||
private bool? m_hasParams;
|
||||
|
||||
public static bool CanEvaluate(MethodInfo mi)
|
||||
{
|
||||
// generic type args not supported yet
|
||||
if (mi.GetGenericArguments().Length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// only primitive and string args supported
|
||||
foreach (var param in mi.GetParameters())
|
||||
{
|
||||
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
var mi = MemInfo as MethodInfo;
|
||||
|
||||
m_arguments = mi.GetParameters();
|
||||
m_argumentInput = new string[m_arguments.Length];
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
//base.UpdateValue();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.BeginVertical(null);
|
||||
|
||||
string evaluateLabel = "<color=lime>Evaluate</color>";
|
||||
if (HasParameters)
|
||||
{
|
||||
if (m_isEvaluating)
|
||||
{
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var name = m_arguments[i].Name;
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType.Name;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
GUILayout.Label(i + ": <color=cyan>" + name + "</color> <color=yellow>(" + type + ")</color>", null);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
Evaluate();
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
m_isEvaluating = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (m_evaluated)
|
||||
{
|
||||
if (m_cachedReturnValue != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_cachedReturnValue.DrawValue(window, width);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception drawing m_cachedReturnValue!");
|
||||
MelonLogger.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void Evaluate()
|
||||
{
|
||||
var mi = MemInfo as MethodInfo;
|
||||
|
||||
object ret = null;
|
||||
|
||||
if (!HasParameters)
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
|
||||
m_evaluated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var arguments = new List<object>();
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType;
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
arguments.Add(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
|
||||
{
|
||||
arguments.Add(parsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arguments.Count == m_arguments.Length)
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, arguments.ToArray());
|
||||
m_evaluated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log($"Did not invoke because {m_arguments.Length - arguments.Count} arguments could not be parsed!");
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != null)
|
||||
{
|
||||
m_cachedReturnValue = GetCacheObject(ret);
|
||||
if (m_cachedReturnValue is CacheList cacheList)
|
||||
{
|
||||
cacheList.WhiteSpace = 0f;
|
||||
cacheList.ButtonWidthOffset += 70f;
|
||||
}
|
||||
m_cachedReturnValue.UpdateValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedReturnValue = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,15 +14,19 @@ namespace Explorer
|
||||
{
|
||||
public object Value;
|
||||
public string ValueTypeName;
|
||||
public Type ValueType;
|
||||
|
||||
// Reflection Inspector only
|
||||
public MemberInfo MemInfo { get; set; }
|
||||
public Type DeclaringType { get; set; }
|
||||
public object DeclaringInstance { get; set; }
|
||||
public string ReflectionException { get; set; }
|
||||
|
||||
public int PropertyIndex { get; private set; }
|
||||
private string m_propertyIndexInput = "0";
|
||||
public 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;
|
||||
@ -40,14 +44,10 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Abstract/Virtual Methods ===== //
|
||||
|
||||
public virtual void Init() { }
|
||||
|
||||
public abstract void DrawValue(Rect window, float width);
|
||||
|
||||
// ===== Static Methods ===== //
|
||||
|
||||
/// <summary>
|
||||
/// Get CacheObject from only an object instance
|
||||
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
|
||||
@ -79,11 +79,7 @@ namespace Explorer
|
||||
{
|
||||
Type type = null;
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
type = ReflectionHelpers.GetActualType(obj);
|
||||
}
|
||||
else if (memberInfo != null)
|
||||
if (memberInfo != null)
|
||||
{
|
||||
if (memberInfo is FieldInfo fi)
|
||||
{
|
||||
@ -98,6 +94,10 @@ namespace Explorer
|
||||
type = mi.ReturnType;
|
||||
}
|
||||
}
|
||||
else if (obj != null)
|
||||
{
|
||||
type = ReflectionHelpers.GetActualType(obj);
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@ -114,7 +114,21 @@ namespace Explorer
|
||||
{
|
||||
CacheObjectBase holder;
|
||||
|
||||
if (memberInfo is MethodInfo mi)
|
||||
var pi = memberInfo as PropertyInfo;
|
||||
var mi = memberInfo as MethodInfo;
|
||||
|
||||
// if PropertyInfo, check if can process args
|
||||
if (pi != null && !CanProcessArgs(pi.GetIndexParameters()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is pretty ugly, could probably make a cleaner implementation.
|
||||
// However, the only cleaner ways I can think of are slower and probably not worth it.
|
||||
|
||||
// Note: the order is somewhat important.
|
||||
|
||||
if (mi != null)
|
||||
{
|
||||
if (CacheMethod.CanEvaluate(mi))
|
||||
{
|
||||
@ -137,20 +151,38 @@ namespace Explorer
|
||||
{
|
||||
holder = new CacheEnum();
|
||||
}
|
||||
else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType))
|
||||
else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
|
||||
{
|
||||
holder = new CacheList();
|
||||
holder = new CacheVector();
|
||||
}
|
||||
else if (valueType == typeof(Quaternion))
|
||||
{
|
||||
holder = new CacheQuaternion();
|
||||
}
|
||||
else if (valueType == typeof(Color))
|
||||
{
|
||||
holder = new CacheColor();
|
||||
}
|
||||
else if (valueType == typeof(Rect))
|
||||
{
|
||||
holder = new CacheRect();
|
||||
}
|
||||
// must check this before IsEnumerable
|
||||
else if (ReflectionHelpers.IsDictionary(valueType))
|
||||
{
|
||||
holder = new CacheDictionary();
|
||||
}
|
||||
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppEnumerable(valueType))
|
||||
{
|
||||
holder = new CacheList();
|
||||
}
|
||||
else
|
||||
{
|
||||
holder = new CacheOther();
|
||||
}
|
||||
|
||||
holder.Value = obj;
|
||||
holder.ValueType = valueType;
|
||||
holder.ValueTypeName = valueType.FullName;
|
||||
|
||||
if (memberInfo != null)
|
||||
@ -158,24 +190,113 @@ namespace Explorer
|
||||
holder.MemInfo = memberInfo;
|
||||
holder.DeclaringType = memberInfo.DeclaringType;
|
||||
holder.DeclaringInstance = declaringInstance;
|
||||
|
||||
holder.UpdateValue();
|
||||
}
|
||||
|
||||
if (pi != null)
|
||||
{
|
||||
holder.m_arguments = pi.GetIndexParameters();
|
||||
}
|
||||
else if (mi != null)
|
||||
{
|
||||
holder.m_arguments = mi.GetParameters();
|
||||
}
|
||||
|
||||
holder.m_argumentInput = new string[holder.m_arguments.Length];
|
||||
|
||||
holder.UpdateValue();
|
||||
|
||||
holder.Init();
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
// ======== Instance Methods =========
|
||||
public static bool CanProcessArgs(ParameterInfo[] parameters)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public float CalcWhitespace(Rect window)
|
||||
{
|
||||
if (!(this is IExpandHeight)) return 0f;
|
||||
|
||||
float whitespace = (this as IExpandHeight).WhiteSpace;
|
||||
if (whitespace > 0)
|
||||
{
|
||||
ClampLabelWidth(window, ref whitespace);
|
||||
}
|
||||
|
||||
return whitespace;
|
||||
}
|
||||
|
||||
public object[] ParseArguments()
|
||||
{
|
||||
var parsedArgs = new List<object>();
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType;
|
||||
|
||||
// First, try parse the input and use that.
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
// strings can obviously just be used directly
|
||||
if (type == typeof(string))
|
||||
{
|
||||
parsedArgs.Add(input);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// try to invoke the parse method and use that.
|
||||
try
|
||||
{
|
||||
parsedArgs.Add(type.GetMethod("Parse", new Type[] { typeof(string) })
|
||||
.Invoke(null, new object[] { input }));
|
||||
|
||||
continue;
|
||||
}
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't use input, see if there is a default value.
|
||||
if (m_arguments[i].HasDefaultValue)
|
||||
{
|
||||
parsedArgs.Add(m_arguments[i].DefaultValue);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try add a null arg I guess
|
||||
parsedArgs.Add(null);
|
||||
}
|
||||
|
||||
return parsedArgs.ToArray();
|
||||
}
|
||||
|
||||
public virtual void UpdateValue()
|
||||
{
|
||||
if (MemInfo == null || !string.IsNullOrEmpty(ReflectionException))
|
||||
if (MemInfo == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasParameters && !m_isEvaluating)
|
||||
{
|
||||
// Need to enter parameters first
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (MemInfo.MemberType == MemberTypes.Field)
|
||||
@ -186,13 +307,11 @@ namespace Explorer
|
||||
else if (MemInfo.MemberType == MemberTypes.Property)
|
||||
{
|
||||
var pi = MemInfo as PropertyInfo;
|
||||
bool isStatic = pi.GetAccessors()[0].IsStatic;
|
||||
var target = isStatic ? null : DeclaringInstance;
|
||||
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
|
||||
|
||||
if (pi.GetIndexParameters().Length > 0)
|
||||
if (HasParameters)
|
||||
{
|
||||
var indexes = new object[] { PropertyIndex };
|
||||
Value = pi.GetValue(target, indexes);
|
||||
Value = pi.GetValue(target, ParseArguments());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -201,6 +320,8 @@ namespace Explorer
|
||||
}
|
||||
|
||||
ReflectionException = null;
|
||||
m_evaluated = true;
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -221,10 +342,9 @@ namespace Explorer
|
||||
{
|
||||
var pi = MemInfo as PropertyInfo;
|
||||
|
||||
if (pi.GetIndexParameters().Length > 0)
|
||||
if (HasParameters)
|
||||
{
|
||||
var indexes = new object[] { PropertyIndex };
|
||||
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, indexes);
|
||||
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, ParseArguments());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -238,9 +358,10 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
// ========= Instance Gui Draw ==========
|
||||
// ========= Gui Draw ==========
|
||||
|
||||
public const float MAX_LABEL_WIDTH = 400f;
|
||||
public const string EVALUATE_LABEL = "<color=lime>Evaluate</color>";
|
||||
|
||||
public static void ClampLabelWidth(Rect window, ref float labelWidth)
|
||||
{
|
||||
@ -259,53 +380,108 @@ 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);
|
||||
}
|
||||
|
||||
var cm = this as CacheMethod;
|
||||
|
||||
if (HasParameters)
|
||||
{
|
||||
GUILayout.BeginVertical(null);
|
||||
|
||||
if (m_isEvaluating)
|
||||
{
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var name = m_arguments[i].Name;
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType.Name;
|
||||
|
||||
var label = "<color=#2df7b2>" + type + "</color> <color=#a6e9e9>" + name + "</color>";
|
||||
if (m_arguments[i].HasDefaultValue)
|
||||
{
|
||||
label = $"<i>[{label} = {m_arguments[i].DefaultValue}]</i>";
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
GUILayout.Label(label, null);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (cm != null)
|
||||
{
|
||||
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
cm.Evaluate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
UpdateValue();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
m_isEvaluating = true;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
// new line and space
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(labelWidth);
|
||||
}
|
||||
else if (cm != null)
|
||||
{
|
||||
//GUILayout.BeginHorizontal(null);
|
||||
|
||||
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
cm.Evaluate();
|
||||
}
|
||||
|
||||
// new line and space
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(labelWidth);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ReflectionException))
|
||||
{
|
||||
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
|
||||
}
|
||||
else if (Value == null && MemInfo?.MemberType != MemberTypes.Method)
|
||||
else if ((HasParameters || this is CacheMethod) && !m_evaluated)
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=#2df7b2>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
else if (Value == null && !(this is CacheMethod))
|
||||
{
|
||||
GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
|
||||
{
|
||||
GUILayout.Label("index:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
|
||||
m_propertyIndexInput = GUILayout.TextField(m_propertyIndexInput, new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
if (GUILayout.Button("Set", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
if (int.TryParse(m_propertyIndexInput, out int i))
|
||||
{
|
||||
PropertyIndex = i;
|
||||
UpdateValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log($"Could not parse '{m_propertyIndexInput}' to an int!");
|
||||
}
|
||||
}
|
||||
|
||||
// new line and space
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(labelWidth);
|
||||
}
|
||||
|
||||
DrawValue(window, window.width - labelWidth - 90);
|
||||
}
|
||||
}
|
||||
@ -325,15 +501,15 @@ namespace Explorer
|
||||
|
||||
m_richTextName = $"<color=#2df7b2>{MemInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemInfo.Name}</color>";
|
||||
|
||||
if (MemInfo is MethodInfo mi)
|
||||
if (m_arguments.Length > 0 || this is CacheMethod)
|
||||
{
|
||||
m_richTextName += "(";
|
||||
var _params = "";
|
||||
foreach (var param in mi.GetParameters())
|
||||
foreach (var param in m_arguments)
|
||||
{
|
||||
if (_params != "") _params += ", ";
|
||||
|
||||
_params += $"<color=#a6e9e9>{param.Name}</color>";
|
||||
_params += $"<color=#2df7b2>{param.ParameterType.Name}</color> <color=#a6e9e9>{param.Name}</color>";
|
||||
}
|
||||
m_richTextName += _params;
|
||||
m_richTextName += ")";
|
||||
|
@ -1,181 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CachePrimitive : CacheObjectBase
|
||||
{
|
||||
public enum PrimitiveTypes
|
||||
{
|
||||
Bool,
|
||||
Double,
|
||||
Float,
|
||||
Int,
|
||||
String,
|
||||
Char
|
||||
}
|
||||
|
||||
private string m_valueToString;
|
||||
|
||||
public PrimitiveTypes PrimitiveType;
|
||||
|
||||
public MethodInfo ParseMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_parseMethod == null)
|
||||
{
|
||||
m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) });
|
||||
}
|
||||
return m_parseMethod;
|
||||
}
|
||||
}
|
||||
|
||||
private MethodInfo m_parseMethod;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (Value == null)
|
||||
{
|
||||
// this must mean it is a string. No other primitive type should be nullable.
|
||||
PrimitiveType = PrimitiveTypes.String;
|
||||
return;
|
||||
}
|
||||
|
||||
m_valueToString = Value.ToString();
|
||||
var type = Value.GetType();
|
||||
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
PrimitiveType = PrimitiveTypes.Bool;
|
||||
}
|
||||
else if (type == typeof(double))
|
||||
{
|
||||
PrimitiveType = PrimitiveTypes.Double;
|
||||
}
|
||||
else if (type == typeof(float))
|
||||
{
|
||||
PrimitiveType = PrimitiveTypes.Float;
|
||||
}
|
||||
else if (IsInteger(type))
|
||||
{
|
||||
PrimitiveType = PrimitiveTypes.Int;
|
||||
}
|
||||
else if (type == typeof(char))
|
||||
{
|
||||
PrimitiveType = PrimitiveTypes.Char;
|
||||
}
|
||||
else
|
||||
{
|
||||
PrimitiveType = PrimitiveTypes.String;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsInteger(Type type)
|
||||
{
|
||||
// For our purposes, all types of int can be treated the same, including IntPtr.
|
||||
return _integerTypes.Contains(type);
|
||||
}
|
||||
|
||||
private static readonly HashSet<Type> _integerTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(byte),
|
||||
typeof(sbyte),
|
||||
typeof(IntPtr)
|
||||
};
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
m_valueToString = Value?.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (PrimitiveType == PrimitiveTypes.Bool)
|
||||
{
|
||||
var b = (bool)Value;
|
||||
var color = $"<color={(b ? "lime>" : "red>")}";
|
||||
var label = $"{color}{b}</color>";
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
b = GUILayout.Toggle(b, label, null);
|
||||
if (b != (bool)Value)
|
||||
{
|
||||
SetValueFromInput(b.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(label, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</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)
|
||||
{
|
||||
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValueFromInput(string value)
|
||||
{
|
||||
if (MemInfo == null)
|
||||
{
|
||||
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (PrimitiveType == PrimitiveTypes.String)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var val = ParseMethod.Invoke(null, new object[] { value });
|
||||
Value = val;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
14
src/CachedObjects/IExpandHeight.cs
Normal file
14
src/CachedObjects/IExpandHeight.cs
Normal file
@ -0,0 +1,14 @@
|
||||
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; }
|
||||
}
|
||||
}
|
303
src/CachedObjects/Object/CacheDictionary.cs
Normal file
303
src/CachedObjects/Object/CacheDictionary.cs
Normal file
@ -0,0 +1,303 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using System.Reflection;
|
||||
using UnhollowerBaseLib;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheDictionary : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private CacheObjectBase[] m_cachedKeys;
|
||||
private CacheObjectBase[] m_cachedValues;
|
||||
|
||||
public Type TypeOfKeys
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_keysType == null) GetGenericArguments();
|
||||
return m_keysType;
|
||||
}
|
||||
}
|
||||
private Type m_keysType;
|
||||
|
||||
public Type TypeOfValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_valuesType == null) GetGenericArguments();
|
||||
return m_valuesType;
|
||||
}
|
||||
}
|
||||
private Type m_valuesType;
|
||||
|
||||
public IDictionary IDict
|
||||
{
|
||||
get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono();
|
||||
set => m_iDictionary = value;
|
||||
}
|
||||
private IDictionary m_iDictionary;
|
||||
|
||||
// ========== Methods ==========
|
||||
|
||||
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
|
||||
private IDictionary Il2CppDictionaryToMono()
|
||||
{
|
||||
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
|
||||
|
||||
// get keys and values
|
||||
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
|
||||
var values = ValueType.GetProperty("Values").GetValue(Value);
|
||||
|
||||
// create lists to hold them
|
||||
var keyList = new List<object>();
|
||||
var valueList = new List<object>();
|
||||
|
||||
// store entries with reflection
|
||||
EnumerateWithReflection(keys, keyList);
|
||||
EnumerateWithReflection(values, valueList);
|
||||
|
||||
// make actual mono dictionary
|
||||
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
|
||||
.MakeGenericType(TypeOfKeys, TypeOfValues));
|
||||
|
||||
// finally iterate into dictionary
|
||||
for (int i = 0; i < keyList.Count; i++)
|
||||
{
|
||||
dict.Add(keyList[i], valueList[i]);
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private void EnumerateWithReflection(object collection, List<object> list)
|
||||
{
|
||||
// invoke GetEnumerator
|
||||
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
{
|
||||
list.Add(current.GetValue(enumerator));
|
||||
}
|
||||
}
|
||||
|
||||
private void GetGenericArguments()
|
||||
{
|
||||
if (this.MemInfo != null)
|
||||
{
|
||||
Type memberType = null;
|
||||
switch (this.MemInfo.MemberType)
|
||||
{
|
||||
case MemberTypes.Field:
|
||||
memberType = (MemInfo as FieldInfo).FieldType;
|
||||
break;
|
||||
case MemberTypes.Property:
|
||||
memberType = (MemInfo as PropertyInfo).PropertyType;
|
||||
break;
|
||||
}
|
||||
|
||||
if (memberType != null && memberType.IsGenericType)
|
||||
{
|
||||
m_keysType = memberType.GetGenericArguments()[0];
|
||||
m_valuesType = memberType.GetGenericArguments()[1];
|
||||
}
|
||||
}
|
||||
else if (Value != null)
|
||||
{
|
||||
var type = Value.GetType();
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
m_keysType = type.GetGenericArguments()[0];
|
||||
m_valuesType = type.GetGenericArguments()[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log("TODO? Dictionary is of type: " + Value.GetType().FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
// first make sure we won't run into a TypeInitializationException.
|
||||
if (!EnsureDictionaryIsSupported())
|
||||
{
|
||||
ReflectionException = "Dictionary Type not supported with Reflection!";
|
||||
return;
|
||||
}
|
||||
|
||||
base.UpdateValue();
|
||||
|
||||
// reset
|
||||
IDict = null;
|
||||
|
||||
if (Value == null || IDict == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = new List<CacheObjectBase>();
|
||||
foreach (var key in IDict.Keys)
|
||||
{
|
||||
var cache = GetCacheObject(key, TypeOfKeys);
|
||||
keys.Add(cache);
|
||||
}
|
||||
|
||||
var values = new List<CacheObjectBase>();
|
||||
foreach (var val in IDict.Values)
|
||||
{
|
||||
var cache = GetCacheObject(val, TypeOfValues);
|
||||
values.Add(cache);
|
||||
}
|
||||
|
||||
m_cachedKeys = keys.ToArray();
|
||||
m_cachedValues = values.ToArray();
|
||||
}
|
||||
|
||||
private bool EnsureDictionaryIsSupported()
|
||||
{
|
||||
if (typeof(IDictionary).IsAssignableFrom(ValueType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Check(TypeOfKeys) && Check(TypeOfValues);
|
||||
|
||||
bool Check(Type type)
|
||||
{
|
||||
var ptr = (IntPtr)typeof(Il2CppClassPointerStore<>)
|
||||
.MakeGenericType(type)
|
||||
.GetField("NativeClassPtr")
|
||||
.GetValue(null);
|
||||
|
||||
return Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(ptr)) is Il2CppSystem.Type;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ============= GUI Draw =============
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_cachedKeys == null || m_cachedValues == null)
|
||||
{
|
||||
GUILayout.Label("Cached keys or values is null!", null);
|
||||
return;
|
||||
}
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
int count = m_cachedKeys.Length;
|
||||
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
var negativeWhitespace = window.width - (whitespace + 100f);
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
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);
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
Pages.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
// prev/next page buttons
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left);
|
||||
}
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right);
|
||||
}
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
var key = m_cachedKeys[i];
|
||||
var val = m_cachedValues[i];
|
||||
|
||||
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
//GUILayout.Space(whitespace);
|
||||
|
||||
if (key == null || val == null)
|
||||
{
|
||||
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
|
||||
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
key.DrawValue(window, (window.width / 2) - 30f);
|
||||
|
||||
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
val.DrawValue(window, (window.width / 2) - 30f);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ namespace Explorer
|
||||
{
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
UIHelpers.GameobjButton(Value, null, false, width);
|
||||
UIHelpers.GOButton(Value, null, false, width);
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
@ -8,13 +8,12 @@ using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheList : CacheObjectBase
|
||||
public class CacheList : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public PageHelper Pages = new PageHelper();
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public float WhiteSpace = 215f;
|
||||
public float ButtonWidthOffset = 290f;
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private CacheObjectBase[] m_cachedEntries;
|
||||
|
||||
@ -41,7 +40,7 @@ namespace Explorer
|
||||
private Type m_genericTypeDef;
|
||||
|
||||
// Cached ToArray method for Lists
|
||||
public MethodInfo GenericToArrayMethod
|
||||
public MethodInfo CppListToArrayMethod
|
||||
{
|
||||
get => GetGenericToArrayMethod();
|
||||
}
|
||||
@ -52,6 +51,7 @@ namespace Explorer
|
||||
{
|
||||
get => GetItemProperty();
|
||||
}
|
||||
|
||||
private PropertyInfo m_itemProperty;
|
||||
|
||||
// ========== Methods ==========
|
||||
@ -60,7 +60,7 @@ namespace Explorer
|
||||
{
|
||||
if (m_enumerable == null && Value != null)
|
||||
{
|
||||
m_enumerable = Value as IEnumerable ?? GetEnumerableFromIl2CppList();
|
||||
m_enumerable = Value as IEnumerable ?? EnumerateWithReflection();
|
||||
}
|
||||
return m_enumerable;
|
||||
}
|
||||
@ -100,21 +100,45 @@ namespace Explorer
|
||||
return m_itemProperty;
|
||||
}
|
||||
|
||||
private IEnumerable GetEnumerableFromIl2CppList()
|
||||
private IEnumerable EnumerateWithReflection()
|
||||
{
|
||||
if (Value == null) return null;
|
||||
|
||||
if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||
{
|
||||
return (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]);
|
||||
return (IEnumerable)CppListToArrayMethod?.Invoke(Value, new object[0]);
|
||||
}
|
||||
else if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
|
||||
{
|
||||
return CppHashSetToMono();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConvertIListToMono();
|
||||
return CppIListToMono();
|
||||
}
|
||||
}
|
||||
|
||||
private IList ConvertIListToMono()
|
||||
private IEnumerable CppHashSetToMono()
|
||||
{
|
||||
var set = new HashSet<object>();
|
||||
|
||||
// invoke GetEnumerator
|
||||
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
{
|
||||
set.Add(current.GetValue(enumerator));
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private IList CppIListToMono()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -244,6 +268,8 @@ namespace Explorer
|
||||
return;
|
||||
}
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
int count = m_cachedEntries.Length;
|
||||
|
||||
if (!IsExpanded)
|
||||
@ -261,9 +287,11 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
var negativeWhitespace = window.width - (whitespace + 100f);
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
|
||||
string btnLabel = $"[{count}] <color=#2df7b2>{EntryType.FullName}</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(negativeWhitespace) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
@ -273,25 +301,15 @@ namespace Explorer
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
float whitespace = WhiteSpace;
|
||||
if (whitespace > 0)
|
||||
{
|
||||
ClampLabelWidth(window, ref whitespace);
|
||||
}
|
||||
Pages.ItemCount = count;
|
||||
|
||||
Pages.Count = count;
|
||||
|
||||
if (count > Pages.PageLimit)
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
|
||||
//int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
|
||||
Pages.CalculateMaxOffset();
|
||||
|
||||
//GUILayout.Label($"Page {PH.ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
// prev/next page buttons
|
||||
@ -309,15 +327,9 @@ namespace Explorer
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
//int offset = ArrayOffset * ArrayLimit;
|
||||
//if (offset >= count)
|
||||
//{
|
||||
// offset = 0;
|
||||
// ArrayOffset = 0;
|
||||
//}
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.PageLimit && i < count; i++)
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
var entry = m_cachedEntries[i];
|
||||
|
90
src/CachedObjects/Other/CacheMethod.cs
Normal file
90
src/CachedObjects/Other/CacheMethod.cs
Normal file
@ -0,0 +1,90 @@
|
||||
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 CacheObjectBase m_cachedReturnValue;
|
||||
|
||||
public static bool CanEvaluate(MethodInfo mi)
|
||||
{
|
||||
// TODO generic args
|
||||
if (mi.GetGenericArguments().Length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// primitive and string args supported
|
||||
return CanProcessArgs(mi.GetParameters());
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
//base.UpdateValue();
|
||||
}
|
||||
|
||||
public void Evaluate()
|
||||
{
|
||||
m_isEvaluating = false;
|
||||
|
||||
var mi = MemInfo as MethodInfo;
|
||||
object ret = null;
|
||||
|
||||
if (!HasParameters)
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, new object[0]);
|
||||
m_evaluated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
|
||||
m_evaluated = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Exception evaluating: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
if (ret != null)
|
||||
{
|
||||
m_cachedReturnValue = GetCacheObject(ret);
|
||||
m_cachedReturnValue.UpdateValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedReturnValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==== GUI DRAW ====
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_evaluated)
|
||||
{
|
||||
if (m_cachedReturnValue != null)
|
||||
{
|
||||
m_cachedReturnValue.DrawValue(window, width);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"null (<color=#2df7b2>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=#2df7b2>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -42,15 +42,20 @@ namespace Explorer
|
||||
|
||||
if (!label.Contains(ValueTypeName))
|
||||
{
|
||||
label += $" ({ValueTypeName})";
|
||||
label += $" (<color=#2df7b2>{ValueTypeName}</color>)";
|
||||
}
|
||||
else
|
||||
{
|
||||
label = label.Replace(ValueTypeName, $"<color=#2df7b2>{ValueTypeName}</color>");
|
||||
}
|
||||
|
||||
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
|
||||
{
|
||||
label = unityObj.name + " | " + label;
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width - 15) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
112
src/CachedObjects/Struct/CacheColor.cs
Normal file
112
src/CachedObjects/Struct/CacheColor.cs
Normal file
@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class 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();
|
||||
|
||||
var color = (Color)Value;
|
||||
|
||||
r = color.r.ToString();
|
||||
g = color.g.ToString();
|
||||
b = color.b.ToString();
|
||||
a = color.a.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//var c = (Color)Value;
|
||||
//GUI.color = c;
|
||||
GUILayout.Label($"<color=#2df7b2>Color:</color> {((Color)Value).ToString()}", null);
|
||||
//GUI.color = Color.white;
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
r = GUILayout.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
g = GUILayout.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
b = GUILayout.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
a = GUILayout.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(r, out float fR)
|
||||
&& float.TryParse(g, out float fG)
|
||||
&& float.TryParse(b, out float fB)
|
||||
&& float.TryParse(a, out float fA))
|
||||
{
|
||||
Value = new Color(fR, fB, fG, fA);
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label(Value.ToString(), null);// + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
|
||||
GUILayout.Label(Value.ToString() + "<color=#2df7b2><i> (" + ValueType + ")</i></color>", null);
|
||||
}
|
||||
|
||||
public void SetEnum(ref object value, int change)
|
126
src/CachedObjects/Struct/CachePrimitive.cs
Normal file
126
src/CachedObjects/Struct/CachePrimitive.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CachePrimitive : CacheObjectBase
|
||||
{
|
||||
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;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (ValueType == null)
|
||||
{
|
||||
ValueType = Value?.GetType();
|
||||
|
||||
// has to be a string at this point
|
||||
if (ValueType == null)
|
||||
{
|
||||
ValueType = typeof(string);
|
||||
}
|
||||
}
|
||||
|
||||
if (ValueType == typeof(string))
|
||||
{
|
||||
m_isString = true;
|
||||
}
|
||||
else if (ValueType == typeof(bool))
|
||||
{
|
||||
m_isBool = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
m_valueToString = Value?.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
if (m_isBool)
|
||||
{
|
||||
var b = (bool)Value;
|
||||
var label = $"<color={(b ? "lime" : "red")}>{b}</color>";
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
b = GUILayout.Toggle(b, label, null);
|
||||
if (b != (bool)Value)
|
||||
{
|
||||
SetValueFromInput(b.ToString());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label(label, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
|
||||
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
|
||||
int dynSize = 25 + (m_valueToString.Length * 15);
|
||||
var maxwidth = window.width - 310f;
|
||||
if (CanWrite) maxwidth -= 60;
|
||||
|
||||
if (dynSize > maxwidth)
|
||||
{
|
||||
m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) });
|
||||
}
|
||||
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(10);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValueFromInput(string valueString)
|
||||
{
|
||||
if (MemInfo == null)
|
||||
{
|
||||
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_isString)
|
||||
{
|
||||
Value = valueString;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
Value = ParseMethod.Invoke(null, new object[] { valueString });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception parsing value: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
100
src/CachedObjects/Struct/CacheQuaternion.cs
Normal file
100
src/CachedObjects/Struct/CacheQuaternion.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
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();
|
||||
|
||||
var euler = ((Quaternion)Value).eulerAngles;
|
||||
|
||||
x = euler.x.ToString();
|
||||
y = euler.y.ToString();
|
||||
z = euler.z.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
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>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(z, out float fZ))
|
||||
{
|
||||
Value = Quaternion.Euler(new Vector3(fX, fY, fZ));
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
109
src/CachedObjects/Struct/CacheRect.cs
Normal file
109
src/CachedObjects/Struct/CacheRect.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public 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();
|
||||
|
||||
var rect = (Rect)Value;
|
||||
|
||||
x = rect.x.ToString();
|
||||
y = rect.y.ToString();
|
||||
w = rect.width.ToString();
|
||||
h = rect.height.ToString();
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
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>Rect</color>: {((Rect)Value).ToString()}", null);
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
h = GUILayout.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(w, out float fW)
|
||||
&& float.TryParse(h, out float fH))
|
||||
{
|
||||
Value = new Rect(fX, fY, fW, fH);
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
164
src/CachedObjects/Struct/CacheVector.cs
Normal file
164
src/CachedObjects/Struct/CacheVector.cs
Normal file
@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheVector : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
public int VectorSize = 2;
|
||||
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string z = "0";
|
||||
private string w = "0";
|
||||
|
||||
private MethodInfo m_toStringMethod;
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (Value is Vector2)
|
||||
{
|
||||
VectorSize = 2;
|
||||
}
|
||||
else if (Value is Vector3)
|
||||
{
|
||||
VectorSize = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
VectorSize = 4;
|
||||
}
|
||||
|
||||
m_toStringMethod = Value.GetType().GetMethod("ToString", new Type[0]);
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
|
||||
if (Value is Vector2 vec2)
|
||||
{
|
||||
x = vec2.x.ToString();
|
||||
y = vec2.y.ToString();
|
||||
}
|
||||
else if (Value is Vector3 vec3)
|
||||
{
|
||||
x = vec3.x.ToString();
|
||||
y = vec3.y.ToString();
|
||||
z = vec3.z.ToString();
|
||||
}
|
||||
else if (Value is Vector4 vec4)
|
||||
{
|
||||
x = vec4.x.ToString();
|
||||
y = vec4.y.ToString();
|
||||
z = vec4.z.ToString();
|
||||
w = vec4.w.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
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])}", null);
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
// always draw x and y
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (VectorSize > 2)
|
||||
{
|
||||
// draw z
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
if (VectorSize > 3)
|
||||
{
|
||||
// draw w
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueFromInput()
|
||||
{
|
||||
if (float.TryParse(x, out float fX)
|
||||
&& float.TryParse(y, out float fY)
|
||||
&& float.TryParse(z, out float fZ)
|
||||
&& float.TryParse(w, out float fW))
|
||||
{
|
||||
object vector = null;
|
||||
|
||||
switch (VectorSize)
|
||||
{
|
||||
case 2: vector = new Vector2(fX, fY); break;
|
||||
case 3: vector = new Vector3(fX, fY, fZ); break;
|
||||
case 4: vector = new Vector4(fX, fY, fZ, fW); break;
|
||||
}
|
||||
|
||||
if (vector != null)
|
||||
{
|
||||
Value = vector;
|
||||
SetValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
src/Config/ModConfig.cs
Normal file
69
src/Config/ModConfig.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Serialization;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ModConfig
|
||||
{
|
||||
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig));
|
||||
|
||||
[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\CppExplorer";
|
||||
[XmlIgnore] private const string SETTINGS_PATH = EXPLORER_FOLDER + @"\config.xml";
|
||||
|
||||
[XmlIgnore] public static ModConfig Instance;
|
||||
|
||||
public KeyCode Main_Menu_Toggle = KeyCode.F7;
|
||||
public Vector2 Default_Window_Size = new Vector2(550, 700);
|
||||
|
||||
public static void OnLoad()
|
||||
{
|
||||
if (!Directory.Exists(EXPLORER_FOLDER))
|
||||
{
|
||||
Directory.CreateDirectory(EXPLORER_FOLDER);
|
||||
}
|
||||
|
||||
if (LoadSettings()) return;
|
||||
|
||||
Instance = new ModConfig();
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
// returns true if settings successfully loaded
|
||||
public static bool LoadSettings(bool checkExist = true)
|
||||
{
|
||||
if (checkExist && !File.Exists(SETTINGS_PATH))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var file = File.OpenRead(SETTINGS_PATH))
|
||||
{
|
||||
Instance = (ModConfig)Serializer.Deserialize(file);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Instance != null;
|
||||
}
|
||||
|
||||
public static void SaveSettings(bool checkExist = true)
|
||||
{
|
||||
if (checkExist && File.Exists(SETTINGS_PATH))
|
||||
File.Delete(SETTINGS_PATH);
|
||||
|
||||
using (var file = File.Create(SETTINGS_PATH))
|
||||
{
|
||||
Serializer.Serialize(file, Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@ -11,15 +12,10 @@ namespace Explorer
|
||||
{
|
||||
public class CppExplorer : MelonMod
|
||||
{
|
||||
public const string GUID = "com.sinai.cppexplorer";
|
||||
public const string VERSION = "1.5.3";
|
||||
public const string AUTHOR = "Sinai";
|
||||
|
||||
public const string NAME = "CppExplorer"
|
||||
#if Release_Unity2018
|
||||
+ " (Unity 2018)"
|
||||
#endif
|
||||
;
|
||||
public const string NAME = "CppExplorer";
|
||||
public const string VERSION = "1.6.9";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.cppexplorer";
|
||||
|
||||
public static CppExplorer Instance { get; private set; }
|
||||
|
||||
@ -28,43 +24,30 @@ namespace Explorer
|
||||
get => m_showMenu;
|
||||
set => SetShowMenu(value);
|
||||
}
|
||||
private static bool m_showMenu;
|
||||
|
||||
public static bool ForceUnlockMouse
|
||||
{
|
||||
get => m_forceUnlock;
|
||||
set => SetForceUnlock(value);
|
||||
}
|
||||
private static bool m_forceUnlock;
|
||||
private static CursorLockMode m_lastLockMode;
|
||||
private static bool m_lastVisibleState;
|
||||
private static bool m_currentlySettingCursor = false;
|
||||
|
||||
public static bool ShouldForceMouse => ShowMenu && ForceUnlockMouse;
|
||||
public static bool m_showMenu;
|
||||
|
||||
private static void SetShowMenu(bool show)
|
||||
{
|
||||
m_showMenu = show;
|
||||
UpdateCursorControl();
|
||||
CursorControl.UpdateCursorControl();
|
||||
}
|
||||
|
||||
// ========== MonoBehaviour methods ==========
|
||||
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
// First, load config
|
||||
ModConfig.OnLoad();
|
||||
|
||||
// Setup InputHelper class (UnityEngine.Input)
|
||||
InputHelper.Init();
|
||||
|
||||
// Create CppExplorer modules
|
||||
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);
|
||||
// Init cursor control
|
||||
CursorControl.Init();
|
||||
|
||||
MelonLogger.Log($"CppExplorer {VERSION} initialized.");
|
||||
}
|
||||
@ -78,22 +61,18 @@ namespace Explorer
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// Check main toggle key input
|
||||
if (Input.GetKeyDown(KeyCode.F7))
|
||||
if (InputHelper.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
|
||||
{
|
||||
ShowMenu = !ShowMenu;
|
||||
}
|
||||
|
||||
if (ShowMenu)
|
||||
{
|
||||
// Check Force-Unlock input
|
||||
if (Input.GetKeyDown(KeyCode.LeftAlt))
|
||||
{
|
||||
ForceUnlockMouse = !ForceUnlockMouse;
|
||||
}
|
||||
CursorControl.Update();
|
||||
InspectUnderMouse.Update();
|
||||
|
||||
MainMenu.Instance.Update();
|
||||
WindowManager.Instance.Update();
|
||||
InspectUnderMouse.Update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,101 +80,14 @@ namespace Explorer
|
||||
{
|
||||
if (!ShowMenu) return;
|
||||
|
||||
var origSkin = GUI.skin;
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
|
||||
MainMenu.Instance.OnGUI();
|
||||
WindowManager.Instance.OnGUI();
|
||||
InspectUnderMouse.OnGUI();
|
||||
}
|
||||
|
||||
// =========== Cursor control ===========
|
||||
|
||||
private static void SetForceUnlock(bool unlock)
|
||||
{
|
||||
m_forceUnlock = unlock;
|
||||
UpdateCursorControl();
|
||||
}
|
||||
|
||||
private static void UpdateCursorControl()
|
||||
{
|
||||
m_currentlySettingCursor = true;
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cursor.lockState = m_lastLockMode;
|
||||
Cursor.visible = m_lastVisibleState;
|
||||
}
|
||||
m_currentlySettingCursor = false;
|
||||
}
|
||||
|
||||
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
|
||||
// Also keep track of when anything else tries to set Cursor state, this will be the
|
||||
// value that we set back to when we close the menu or disable force-unlock.
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)]
|
||||
public class Cursor_set_lockState
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix(ref CursorLockMode value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
{
|
||||
m_lastLockMode = value;
|
||||
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
value = CursorLockMode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Setter)]
|
||||
public class Cursor_set_visible
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix(ref bool value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
{
|
||||
m_lastVisibleState = value;
|
||||
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make it appear as though UnlockMouse is disabled to the rest of the application.
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Getter)]
|
||||
public class Cursor_get_lockState
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
public static void Postfix(ref CursorLockMode __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastLockMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Getter)]
|
||||
public class Cursor_get_visible
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
public static void Postfix(ref bool __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastVisibleState;
|
||||
}
|
||||
}
|
||||
GUI.skin = origSkin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,39 +12,30 @@
|
||||
<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\</OutputPath>
|
||||
<DefineConstants>DEBUG</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_Unity2018|AnyCPU' ">
|
||||
<AssemblyName>CppExplorer_Unity2018</AssemblyName>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>..\Release\</OutputPath>
|
||||
<DefineConstants>Release_Unity2018</DefineConstants>
|
||||
<DefineConstants />
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- Replace the '..\Steam\..` references with ones from your game (make sure to use the 'MelonLoader\' folder) -->
|
||||
<Reference Include="Il2Cppmscorlib">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2Cppmscorlib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Il2CppSystem.Core">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\Il2CppSystem.Core.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="mcs">
|
||||
<HintPath>..\lib\mcs.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="MelonLoader.ModHandler">
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
|
||||
@ -60,96 +51,75 @@
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<!-- Unity 2019 build (InputLegacyModule.dll) -->
|
||||
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Debug'">
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<Reference Include="UnityEngine.IMGUIModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<Reference Include="UnityEngine.PhysicsModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Debug'">
|
||||
<Reference Include="UnityEngine.TextRenderingModule">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Debug'">
|
||||
<Reference Include="UnityEngine.UI">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<!-- Unity 2018 build (InputModule.dll) -->
|
||||
<Reference Include="UnityEngine" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.CoreModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.InputModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextRenderingModule" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI" Condition="'$(Configuration)'=='Release_Unity2018'">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\VRChat\MelonLoader\Managed\UnityEngine.UI.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="CachedObjects\CacheDictionary.cs" />
|
||||
<Compile Include="CachedObjects\CacheEnum.cs" />
|
||||
<Compile Include="CachedObjects\CacheGameObject.cs" />
|
||||
<Compile Include="CachedObjects\CacheList.cs" />
|
||||
<Compile Include="CachedObjects\CachePrimitive.cs" />
|
||||
<Compile Include="CachedObjects\CacheOther.cs" />
|
||||
<Compile Include="CachedObjects\CacheMethod.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\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="CppExplorer.cs" />
|
||||
<Compile Include="Extensions\ReflectionExtensions.cs" />
|
||||
<Compile Include="Helpers\InputHelper.cs" />
|
||||
<Compile Include="Menu\CursorControl.cs" />
|
||||
<Compile Include="Tests\TestClass.cs" />
|
||||
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\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="Menu\MainMenu\InspectUnderMouse.cs" />
|
||||
<Compile Include="CachedObjects\CacheObjectBase.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="UnstripFixes\SliderHandlerUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\UnstripExtensions.cs" />
|
||||
<Compile Include="Menu\Windows\ResizeDrag.cs" />
|
||||
<Compile Include="Menu\Windows\TabViewWindow.cs" />
|
||||
<Compile Include="Menu\Windows\UIWindow.cs" />
|
||||
<Compile Include="Menu\MainMenu\Pages\ConsolePage.cs" />
|
||||
<Compile Include="Menu\MainMenu\Pages\Console\REPL.cs" />
|
||||
<Compile Include="Menu\MainMenu\Pages\Console\REPLHelper.cs" />
|
||||
<Compile Include="Menu\MainMenu\Pages\WindowPage.cs" />
|
||||
<Compile Include="Menu\Windows\WindowManager.cs" />
|
||||
<Compile Include="Menu\MainMenu\MainMenu.cs" />
|
||||
<Compile Include="Menu\Windows\GameObjectWindow.cs" />
|
||||
<Compile Include="Menu\Windows\ReflectionWindow.cs" />
|
||||
<Compile Include="Menu\MainMenu\Pages\ScenePage.cs" />
|
||||
<Compile Include="Menu\MainMenu\Pages\SearchPage.cs" />
|
||||
<Compile Include="Menu\UIStyles.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
|
@ -7,14 +7,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CppExplorer", "CppExplorer.
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release_Unity2018|Any CPU = Release_Unity2018|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.ActiveCfg = Release_Unity2018|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_Unity2018|Any CPU.Build.0 = Release_Unity2018|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -3,14 +3,38 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class ReflectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension to allow for easy, non-generic Il2Cpp casting.
|
||||
/// The extension is on System.Object, but only Il2Cpp objects would be a valid target.
|
||||
/// </summary>
|
||||
public static object Il2CppCast(this object obj, Type castTo)
|
||||
{
|
||||
return ReflectionHelpers.Il2CppCast(obj, castTo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension to safely try to get all Types from an Assembly, with a fallback for ReflectionTypeLoadException.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
|
||||
{
|
||||
try
|
||||
{
|
||||
return asm.GetTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
return e.Types.Where(t => t != null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Enumerable.Empty<Type>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
|
104
src/Helpers/InputHelper.cs
Normal file
104
src/Helpers/InputHelper.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Version-agnostic UnityEngine Input module using Reflection.
|
||||
/// </summary>
|
||||
public static class InputHelper
|
||||
{
|
||||
// If Input module failed to load at all
|
||||
public static bool NO_INPUT;
|
||||
|
||||
// Base UnityEngine.Input class
|
||||
private static Type Input => _input ?? (_input = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
|
||||
private static Type _input;
|
||||
|
||||
// Cached member infos
|
||||
private static PropertyInfo _mousePosition;
|
||||
private static MethodInfo _getKey;
|
||||
private static MethodInfo _getKeyDown;
|
||||
private static MethodInfo _getMouseButton;
|
||||
private static MethodInfo _getMouseButtonDown;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (Input == null && !TryManuallyLoadInput())
|
||||
{
|
||||
NO_INPUT = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache reflection now that we know Input is loaded
|
||||
|
||||
_mousePosition = Input.GetProperty("mousePosition");
|
||||
|
||||
_getKey = Input.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
|
||||
_getKeyDown = Input.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
|
||||
_getMouseButton = Input.GetMethod("GetMouseButton", new Type[] { typeof(int) });
|
||||
_getMouseButtonDown = Input.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // Camel-case property (Unity style)
|
||||
public static Vector3 mousePosition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (NO_INPUT) return Vector3.zero;
|
||||
return (Vector3)_mousePosition.GetValue(null);
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE1006
|
||||
|
||||
public static bool GetKeyDown(KeyCode key)
|
||||
{
|
||||
if (NO_INPUT) return false;
|
||||
return (bool)_getKeyDown.Invoke(null, new object[] { key });
|
||||
}
|
||||
|
||||
public static bool GetKey(KeyCode key)
|
||||
{
|
||||
if (NO_INPUT) return false;
|
||||
return (bool)_getKey.Invoke(null, new object[] { key });
|
||||
}
|
||||
|
||||
/// <param name="btn">1 = left, 2 = middle, 3 = right, etc</param>
|
||||
public static bool GetMouseButtonDown(int btn)
|
||||
{
|
||||
if (NO_INPUT) return false;
|
||||
return (bool)_getMouseButtonDown.Invoke(null, new object[] { btn });
|
||||
}
|
||||
|
||||
/// <param name="btn">1 = left, 2 = middle, 3 = right, etc</param>
|
||||
public static bool GetMouseButton(int btn)
|
||||
{
|
||||
if (NO_INPUT) return false;
|
||||
return (bool)_getMouseButton.Invoke(null, new object[] { btn });
|
||||
}
|
||||
|
||||
private static bool TryManuallyLoadInput()
|
||||
{
|
||||
MelonLogger.Log("UnityEngine.Input is null, trying to load manually....");
|
||||
|
||||
if ((ReflectionHelpers.LoadModule("UnityEngine.InputLegacyModule.dll") || ReflectionHelpers.LoadModule("UnityEngine.CoreModule.dll"))
|
||||
&& Input != null)
|
||||
{
|
||||
MelonLogger.Log("Ok!");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log("Could not load Input module!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,13 +16,34 @@ namespace Explorer
|
||||
public class PageHelper
|
||||
{
|
||||
public int PageOffset { get; set; }
|
||||
public int PageLimit { get; set; } = 20;
|
||||
public int Count { get; set; }
|
||||
public int MaxOffset { get; set; } = -1;
|
||||
|
||||
public int CalculateMaxOffset()
|
||||
public int ItemsPerPage
|
||||
{
|
||||
return MaxOffset = (int)Mathf.Ceil((float)(Count / (decimal)PageLimit)) - 1;
|
||||
get => m_itemsPerPage;
|
||||
set
|
||||
{
|
||||
m_itemsPerPage = value;
|
||||
CalculateMaxOffset();
|
||||
}
|
||||
}
|
||||
private int m_itemsPerPage = 20;
|
||||
|
||||
public int ItemCount
|
||||
{
|
||||
get => m_count;
|
||||
set
|
||||
{
|
||||
m_count = value;
|
||||
CalculateMaxOffset();
|
||||
}
|
||||
}
|
||||
private int m_count;
|
||||
|
||||
public int MaxPageOffset { get; private set; } = -1;
|
||||
|
||||
private int CalculateMaxOffset()
|
||||
{
|
||||
return MaxPageOffset = (int)Mathf.Ceil((float)(ItemCount / (decimal)ItemsPerPage)) - 1;
|
||||
}
|
||||
|
||||
public void CurrentPageLabel()
|
||||
@ -30,7 +51,7 @@ namespace Explorer
|
||||
var orig = GUI.skin.label.alignment;
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUILayout.Label($"Page {PageOffset + 1}/{MaxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
GUILayout.Label($"Page {PageOffset + 1}/{MaxPageOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
|
||||
|
||||
GUI.skin.label.alignment = orig;
|
||||
}
|
||||
@ -53,7 +74,7 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PageOffset < MaxOffset)
|
||||
if (PageOffset < MaxPageOffset)
|
||||
{
|
||||
PageOffset++;
|
||||
scroll = Vector2.zero;
|
||||
@ -63,9 +84,9 @@ namespace Explorer
|
||||
|
||||
public int CalculateOffsetIndex()
|
||||
{
|
||||
int offset = PageOffset * PageLimit;
|
||||
int offset = PageOffset * ItemsPerPage;
|
||||
|
||||
if (offset >= Count)
|
||||
if (offset >= ItemCount)
|
||||
{
|
||||
offset = 0;
|
||||
PageOffset = 0;
|
||||
@ -77,11 +98,11 @@ namespace Explorer
|
||||
public void DrawLimitInputArea()
|
||||
{
|
||||
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
var limit = this.PageLimit.ToString();
|
||||
var limit = this.ItemsPerPage.ToString();
|
||||
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
if (limit != PageLimit.ToString() && int.TryParse(limit, out int i))
|
||||
if (limit != ItemsPerPage.ToString() && int.TryParse(limit, out int i))
|
||||
{
|
||||
PageLimit = i;
|
||||
ItemsPerPage = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using MelonLoader;
|
||||
using ILType = Il2CppSystem.Type;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
@ -16,11 +17,11 @@ namespace Explorer
|
||||
{
|
||||
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
||||
|
||||
public static Il2CppSystem.Type GameObjectType => Il2CppType.Of<GameObject>();
|
||||
public static Il2CppSystem.Type TransformType => Il2CppType.Of<Transform>();
|
||||
public static Il2CppSystem.Type ObjectType => Il2CppType.Of<UnityEngine.Object>();
|
||||
public static Il2CppSystem.Type ComponentType => Il2CppType.Of<Component>();
|
||||
public static Il2CppSystem.Type BehaviourType => Il2CppType.Of<Behaviour>();
|
||||
public static ILType GameObjectType => Il2CppType.Of<GameObject>();
|
||||
public static ILType TransformType => Il2CppType.Of<Transform>();
|
||||
public static ILType ObjectType => Il2CppType.Of<UnityEngine.Object>();
|
||||
public static ILType ComponentType => Il2CppType.Of<Component>();
|
||||
public static ILType BehaviourType => Il2CppType.Of<Behaviour>();
|
||||
|
||||
private static readonly MethodInfo m_tryCastMethodInfo = typeof(Il2CppObjectBase).GetMethod("TryCast");
|
||||
|
||||
@ -33,6 +34,113 @@ namespace Explorer
|
||||
.Invoke(obj, null);
|
||||
}
|
||||
|
||||
public static bool IsEnumerable(Type t)
|
||||
{
|
||||
return typeof(IEnumerable).IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
// Checks for Il2Cpp List or HashSet.
|
||||
public static bool IsCppEnumerable(Type t)
|
||||
{
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDictionary(Type t)
|
||||
{
|
||||
if (typeof(IDictionary).IsAssignableFrom(t))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetTypeByName(string fullName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
{
|
||||
if (type.FullName == fullName)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type GetActualType(object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Get the System.Type using the qualified name, or fallback to GetType.
|
||||
return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType();
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
var path = $@"MelonLoader\Managed\{module}";
|
||||
if (!File.Exists(path)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
Assembly.Load(File.ReadAllBytes(path));
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log(e.GetType() + ", " + e.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExceptionToString(Exception e)
|
||||
{
|
||||
if (IsFailedGeneric(e))
|
||||
@ -73,84 +181,5 @@ namespace Explorer
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsArray(Type t)
|
||||
{
|
||||
return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
public static bool IsList(Type t)
|
||||
{
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
var generic = t.GetGenericTypeDefinition();
|
||||
|
||||
return generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||
|| generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.IList<>));
|
||||
}
|
||||
else
|
||||
{
|
||||
return t.IsAssignableFrom(typeof(Il2CppSystem.Collections.IList));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDictionary(Type t)
|
||||
{
|
||||
return t.IsGenericType
|
||||
&& t.GetGenericTypeDefinition() is Type typeDef
|
||||
&& typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>));
|
||||
}
|
||||
|
||||
public static Type GetTypeByName(string typeName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (asm.GetType(typeName) is Type type)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type GetActualType(object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
|
||||
|
||||
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
return ilObject.GetType();
|
||||
}
|
||||
|
||||
return obj.GetType();
|
||||
}
|
||||
|
||||
public static Type[] GetAllBaseTypes(object obj)
|
||||
{
|
||||
var list = new List<Type>();
|
||||
|
||||
var type = GetActualType(obj);
|
||||
list.Add(type);
|
||||
|
||||
while (type.BaseType != null)
|
||||
{
|
||||
type = type.BaseType;
|
||||
list.Add(type);
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ 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;
|
||||
|
||||
@ -10,35 +12,6 @@ namespace Explorer
|
||||
{
|
||||
public class UIHelpers
|
||||
{
|
||||
private static bool ScrollUnstrippingFailed = false;
|
||||
|
||||
public static Vector2 BeginScrollView(Vector2 scroll) => BeginScrollView(scroll, null);
|
||||
|
||||
public static Vector2 BeginScrollView(Vector2 scroll, GUIStyle style, params GUILayoutOption[] layoutOptions)
|
||||
{
|
||||
if (ScrollUnstrippingFailed) return scroll;
|
||||
|
||||
try
|
||||
{
|
||||
if (style != null)
|
||||
return GUILayout.BeginScrollView(scroll, style, layoutOptions);
|
||||
else
|
||||
return GUILayout.BeginScrollView(scroll, layoutOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ScrollUnstrippingFailed = true;
|
||||
return scroll;
|
||||
}
|
||||
}
|
||||
|
||||
public static void EndScrollView()
|
||||
{
|
||||
if (ScrollUnstrippingFailed) return;
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
// helper for "Instantiate" button on UnityEngine.Objects
|
||||
public static void InstantiateButton(Object obj, float width = 100)
|
||||
{
|
||||
@ -51,13 +24,13 @@ namespace Explorer
|
||||
}
|
||||
|
||||
// helper for drawing a styled button for a GameObject or Transform
|
||||
public static void GameobjButton(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||
public static void GOButton(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||
{
|
||||
var obj = (_obj as GameObject) ?? (_obj as Transform).gameObject;
|
||||
|
||||
bool children = obj.transform.childCount > 0;
|
||||
bool hasChild = obj.transform.childCount > 0;
|
||||
|
||||
string label = children ? "[" + obj.transform.childCount + " children] " : "";
|
||||
string label = hasChild ? $"[{obj.transform.childCount} children] " : "";
|
||||
label += obj.name;
|
||||
|
||||
bool enabled = obj.activeSelf;
|
||||
@ -80,10 +53,10 @@ namespace Explorer
|
||||
color = Color.red;
|
||||
}
|
||||
|
||||
FastGameobjButton(_obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
|
||||
GOButton_Impl(_obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
|
||||
}
|
||||
|
||||
public static void FastGameobjButton(object _obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||
public static void GOButton_Impl(object _obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
|
||||
{
|
||||
var obj = _obj as GameObject ?? (_obj as Transform).gameObject;
|
||||
|
||||
|
@ -15,7 +15,7 @@ namespace Explorer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_mainCamera == null)
|
||||
if (!m_mainCamera)
|
||||
{
|
||||
m_mainCamera = Camera.main;
|
||||
}
|
||||
|
238
src/Menu/CursorControl.cs
Normal file
238
src/Menu/CursorControl.cs
Normal file
@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Harmony;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CursorControl
|
||||
{
|
||||
public static bool ForceUnlockMouse
|
||||
{
|
||||
get => m_forceUnlock;
|
||||
set => SetForceUnlock(value);
|
||||
}
|
||||
private static bool m_forceUnlock;
|
||||
private static CursorLockMode m_lastLockMode;
|
||||
private static bool m_lastVisibleState;
|
||||
private static bool m_currentlySettingCursor = false;
|
||||
|
||||
public static bool ShouldForceMouse => CppExplorer.ShowMenu && ForceUnlockMouse;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if Cursor class is loaded
|
||||
if (ReflectionHelpers.GetTypeByName("UnityEngine.Cursor") == null)
|
||||
{
|
||||
MelonLogger.Log("Trying to manually load Cursor module...");
|
||||
|
||||
if (ReflectionHelpers.LoadModule("UnityEngine.CoreModule"))
|
||||
{
|
||||
MelonLogger.Log("Ok!");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not load UnityEngine.Cursor module!");
|
||||
}
|
||||
}
|
||||
|
||||
// Get current cursor state and enable cursor
|
||||
m_lastLockMode = Cursor.lockState;
|
||||
m_lastVisibleState = Cursor.visible;
|
||||
|
||||
TryPatch("lockState", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Prefix_set_lockState))), false, false);
|
||||
TryPatch("visible", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Prefix_set_visible))), false, false);
|
||||
|
||||
TryPatch("lockState", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Postfix_get_lockState))), true, true);
|
||||
TryPatch("visible", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Postfix_get_visible))), true, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Exception on CursorControl.Init! {e.GetType()}, {e.Message}");
|
||||
}
|
||||
|
||||
// Enable ShowMenu and ForceUnlockMouse
|
||||
// (set m_showMenu directly to not call UpdateCursorState twice)
|
||||
CppExplorer.m_showMenu = true;
|
||||
ForceUnlockMouse = true;
|
||||
}
|
||||
|
||||
private static void TryPatch(string property, HarmonyMethod patch, bool getter = true, bool postfix = false)
|
||||
{
|
||||
// Setup Harmony Patches
|
||||
try
|
||||
{
|
||||
var harmony = CppExplorer.Instance.harmonyInstance;
|
||||
|
||||
var prop = typeof(Cursor).GetProperty(property);
|
||||
|
||||
harmony.Patch(getter ? prop.GetGetMethod() : prop.GetSetMethod(),
|
||||
postfix ? null : patch,
|
||||
postfix ? patch : null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"[NON-FATAL] Couldn't patch a method: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetForceUnlock(bool unlock)
|
||||
{
|
||||
m_forceUnlock = unlock;
|
||||
UpdateCursorControl();
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
// Check Force-Unlock input
|
||||
if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
|
||||
{
|
||||
ForceUnlockMouse = !ForceUnlockMouse;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateCursorControl()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_currentlySettingCursor = true;
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cursor.lockState = m_lastLockMode;
|
||||
Cursor.visible = m_lastVisibleState;
|
||||
}
|
||||
m_currentlySettingCursor = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
|
||||
// Also keep track of when anything else tries to set Cursor state, this will be the
|
||||
// value that we set back to when we close the menu or disable force-unlock.
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
{
|
||||
m_lastLockMode = value;
|
||||
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
value = CursorLockMode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
{
|
||||
m_lastVisibleState = value;
|
||||
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Postfix_get_lockState(ref CursorLockMode __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastLockMode;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Postfix_get_visible(ref bool __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastVisibleState;
|
||||
}
|
||||
}
|
||||
|
||||
//[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;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ namespace Explorer
|
||||
{
|
||||
if (CppExplorer.ShowMenu)
|
||||
{
|
||||
if (Input.GetKey(KeyCode.LeftShift) && Input.GetMouseButtonDown(1))
|
||||
if (InputHelper.GetKey(KeyCode.LeftShift) && InputHelper.GetMouseButtonDown(1))
|
||||
{
|
||||
EnableInspect = !EnableInspect;
|
||||
}
|
||||
@ -35,7 +35,10 @@ namespace Explorer
|
||||
|
||||
public static void InspectRaycast()
|
||||
{
|
||||
Ray ray = UnityHelpers.MainCamera.ScreenPointToRay(Input.mousePosition);
|
||||
if (!UnityHelpers.MainCamera)
|
||||
return;
|
||||
|
||||
var ray = UnityHelpers.MainCamera.ScreenPointToRay(InputHelper.mousePosition);
|
||||
|
||||
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
|
||||
{
|
||||
@ -43,7 +46,7 @@ namespace Explorer
|
||||
|
||||
m_objUnderMouseName = obj.transform.GetGameObjectPath();
|
||||
|
||||
if (Input.GetMouseButtonDown(0))
|
||||
if (InputHelper.GetMouseButtonDown(0))
|
||||
{
|
||||
EnableInspect = false;
|
||||
m_objUnderMouseName = "";
|
||||
@ -63,7 +66,7 @@ namespace Explorer
|
||||
{
|
||||
if (m_objUnderMouseName != "")
|
||||
{
|
||||
var pos = Input.mousePosition;
|
||||
var pos = InputHelper.mousePosition;
|
||||
var rect = new Rect(
|
||||
pos.x - (Screen.width / 2), // x
|
||||
Screen.height - pos.y - 50, // y
|
@ -27,8 +27,9 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
public const int MainWindowID = 10;
|
||||
public static Rect MainRect = new Rect(5, 5, 550, 700);
|
||||
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);
|
||||
|
||||
private static readonly List<WindowPage> Pages = new List<WindowPage>();
|
||||
private static int m_currentPage = 0;
|
||||
|
||||
@ -51,19 +52,14 @@ namespace Explorer
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
var origSkin = GUI.skin;
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
|
||||
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, CppExplorer.NAME);
|
||||
|
||||
GUI.skin = origSkin;
|
||||
}
|
||||
|
||||
private void MainWindow(int id)
|
||||
{
|
||||
GUI.DragWindow(new Rect(0, 0, MainRect.width - 90, 20));
|
||||
|
||||
if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), "Hide (F7)"))
|
||||
if (GUI.Button(new Rect(MainRect.width - 90, 2, 80, 20), $"Hide ({ModConfig.Instance.Main_Menu_Toggle})"))
|
||||
{
|
||||
CppExplorer.ShowMenu = false;
|
||||
return;
|
||||
@ -75,11 +71,11 @@ namespace Explorer
|
||||
|
||||
var page = Pages[m_currentPage];
|
||||
|
||||
page.scroll = UIHelpers.BeginScrollView(page.scroll);
|
||||
page.scroll = GUIUnstrip.BeginScrollView(page.scroll);
|
||||
|
||||
page.DrawWindow();
|
||||
|
||||
UIHelpers.EndScrollView();
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);
|
||||
|
||||
@ -107,9 +103,9 @@ namespace Explorer
|
||||
GUI.color = Color.white;
|
||||
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
||||
|
||||
bool mouseState = CppExplorer.ForceUnlockMouse;
|
||||
bool mouseState = CursorControl.ForceUnlockMouse;
|
||||
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", null);
|
||||
if (setMouse != mouseState) CppExplorer.ForceUnlockMouse = setMouse;
|
||||
if (setMouse != mouseState) CursorControl.ForceUnlockMouse = setMouse;
|
||||
|
||||
WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", null);
|
||||
GUILayout.EndHorizontal();
|
@ -14,11 +14,13 @@ namespace Explorer
|
||||
{
|
||||
public class ConsolePage : WindowPage
|
||||
{
|
||||
public override string Name { get => "C# Console"; set => base.Name = value; }
|
||||
public override string Name { get => "C# Console"; }
|
||||
|
||||
private ScriptEvaluator _evaluator;
|
||||
private readonly StringBuilder _sb = new StringBuilder();
|
||||
|
||||
private Vector2 inputAreaScroll;
|
||||
|
||||
private string MethodInput = "";
|
||||
private string UsingInput = "";
|
||||
|
||||
@ -124,7 +126,12 @@ MelonLogger.Log(""hello world"");";
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUILayout.Label("Enter code here as though it is a method body:", null);
|
||||
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.Height(250) });
|
||||
|
||||
inputAreaScroll = GUIUnstrip.BeginScrollView(inputAreaScroll, new GUILayoutOption[] { GUILayout.Height(250) });
|
||||
|
||||
MethodInput = GUILayout.TextArea(MethodInput, new GUILayoutOption[] { GUILayout.ExpandHeight(true) });
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", null))
|
||||
{
|
@ -12,27 +12,26 @@ namespace Explorer
|
||||
{
|
||||
public static ScenePage Instance;
|
||||
|
||||
public override string Name { get => "Scene Explorer"; set => base.Name = value; }
|
||||
public override string Name { get => "Scene Explorer"; }
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private float m_timeOfLastUpdate = -1f;
|
||||
private const int PASSIVE_UPDATE_INTERVAL = 1;
|
||||
|
||||
// ----- Holders for GUI elements ----- //
|
||||
private static bool m_getRootObjectsFailed;
|
||||
|
||||
private string m_currentScene = "";
|
||||
private static string m_currentScene = "";
|
||||
|
||||
// gameobject list
|
||||
private Transform m_currentTransform;
|
||||
private List<GameObjectCache> m_objectList = new List<GameObjectCache>();
|
||||
private readonly List<GameObjectCache> m_objectList = new List<GameObjectCache>();
|
||||
|
||||
// search bar
|
||||
private bool m_searching = false;
|
||||
private string m_searchInput = "";
|
||||
private List<GameObjectCache> m_searchResults = new List<GameObjectCache>();
|
||||
|
||||
// ------------ Init and Update ------------ //
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
@ -44,59 +43,6 @@ namespace Explorer
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
|
||||
//public void CheckOffset(ref int offset, int childCount)
|
||||
//{
|
||||
// if (offset >= childCount)
|
||||
// {
|
||||
// offset = 0;
|
||||
// m_pageOffset = 0;
|
||||
// }
|
||||
//}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_searching) return;
|
||||
|
||||
if (Time.time - m_timeOfLastUpdate < 1f) return;
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
m_objectList = new List<GameObjectCache>();
|
||||
|
||||
var allTransforms = new List<Transform>();
|
||||
|
||||
// get current list of all transforms (either scene root or our current transform children)
|
||||
if (m_currentTransform)
|
||||
{
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
allTransforms.Add(m_currentTransform.GetChild(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var scene = SceneManager.GetSceneByName(m_currentScene);
|
||||
var rootObjects = scene.GetRootGameObjects();
|
||||
|
||||
foreach (var obj in rootObjects)
|
||||
{
|
||||
allTransforms.Add(obj.transform);
|
||||
}
|
||||
}
|
||||
|
||||
Pages.Count = allTransforms.Count;
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
// sort by childcount
|
||||
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
|
||||
for (int i = offset; i < offset + Pages.PageLimit && i < Pages.Count; i++)
|
||||
{
|
||||
var child = allTransforms[i];
|
||||
m_objectList.Add(new GameObjectCache(child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTransformTarget(Transform t)
|
||||
{
|
||||
m_currentTransform = t;
|
||||
@ -104,8 +50,7 @@ namespace Explorer
|
||||
if (m_searching)
|
||||
CancelSearch();
|
||||
|
||||
m_timeOfLastUpdate = -1f;
|
||||
Update();
|
||||
Update_Impl(true);
|
||||
}
|
||||
|
||||
public void TraverseUp()
|
||||
@ -124,30 +69,130 @@ namespace Explorer
|
||||
{
|
||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
||||
m_searching = true;
|
||||
Pages.Count = m_searchResults.Count;
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
}
|
||||
|
||||
public void CancelSearch()
|
||||
{
|
||||
m_searching = false;
|
||||
|
||||
if (m_getRootObjectsFailed && !m_currentTransform)
|
||||
{
|
||||
GetRootObjectsManual_Impl();
|
||||
}
|
||||
}
|
||||
|
||||
public List<GameObjectCache> SearchSceneObjects(string _search)
|
||||
{
|
||||
var matches = new List<GameObjectCache>();
|
||||
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll<GameObject>())
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
|
||||
{
|
||||
if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == m_currentScene)
|
||||
var go = obj.TryCast<GameObject>();
|
||||
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
|
||||
{
|
||||
matches.Add(new GameObjectCache(obj));
|
||||
matches.Add(new GameObjectCache(go));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
// --------- GUI Draw Function --------- //
|
||||
public override void Update()
|
||||
{
|
||||
if (m_searching) return;
|
||||
|
||||
if (Time.time - m_timeOfLastUpdate < PASSIVE_UPDATE_INTERVAL) return;
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
|
||||
private void Update_Impl(bool manual = false)
|
||||
{
|
||||
List<Transform> allTransforms = new List<Transform>();
|
||||
|
||||
// get current list of all transforms (either scene root or our current transform children)
|
||||
if (m_currentTransform)
|
||||
{
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
allTransforms.Add(m_currentTransform.GetChild(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_getRootObjectsFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var scene = SceneManager.GetSceneByName(m_currentScene);
|
||||
|
||||
allTransforms.AddRange(scene.GetRootGameObjects()
|
||||
.Select(it => it.transform));
|
||||
}
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log("Exception getting root scene objects, falling back to backup method...");
|
||||
|
||||
m_getRootObjectsFailed = true;
|
||||
allTransforms.AddRange(GetRootObjectsManual_Impl());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!manual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allTransforms.AddRange(GetRootObjectsManual_Impl());
|
||||
}
|
||||
}
|
||||
|
||||
Pages.ItemCount = allTransforms.Count;
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
// sort by childcount
|
||||
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
|
||||
m_objectList.Clear();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
|
||||
{
|
||||
var child = allTransforms[i];
|
||||
m_objectList.Add(new GameObjectCache(child.gameObject));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Transform> GetRootObjectsManual_Impl()
|
||||
{
|
||||
try
|
||||
{
|
||||
var array = Resources.FindObjectsOfTypeAll(ReflectionHelpers.TransformType);
|
||||
|
||||
var list = new List<Transform>();
|
||||
foreach (var obj in array)
|
||||
{
|
||||
var transform = obj.TryCast<Transform>();
|
||||
if (transform.parent == null && transform.gameObject.scene.name == m_currentScene)
|
||||
{
|
||||
list.Add(transform);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception getting root scene objects (manual): "
|
||||
+ e.GetType() + ", " + e.Message + "\r\n"
|
||||
+ e.StackTrace);
|
||||
return new Transform[0];
|
||||
}
|
||||
}
|
||||
|
||||
// --------- GUI Draw Function --------- //
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
@ -170,11 +215,9 @@ namespace Explorer
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message);
|
||||
MelonLogger.Log(e.StackTrace);
|
||||
m_currentTransform = null;
|
||||
// supress
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,39 +227,7 @@ namespace Explorer
|
||||
|
||||
// Current Scene label
|
||||
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
try
|
||||
{
|
||||
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
|
||||
var scenes = SceneManager.GetAllScenes().ToList();
|
||||
|
||||
if (scenes.Count > 1)
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||
index += changeWanted;
|
||||
if (index > scenes.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
SceneChangeButtons();
|
||||
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
@ -224,7 +235,9 @@ namespace Explorer
|
||||
// ----- GameObject Search -----
|
||||
GUILayout.BeginHorizontal(GUI.skin.box, null);
|
||||
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
|
||||
m_searchInput = GUILayout.TextField(m_searchInput, null);
|
||||
|
||||
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Search();
|
||||
@ -234,18 +247,52 @@ namespace Explorer
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
private void SceneChangeButtons()
|
||||
{
|
||||
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
|
||||
var scenes = SceneManager.GetAllScenes().ToList();
|
||||
|
||||
if (scenes.Count > 1)
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||
index += changeWanted;
|
||||
if (index > scenes.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPageButtons()
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (Pages.Count > Pages.PageLimit)
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||
Update();
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
@ -253,7 +300,8 @@ namespace Explorer
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||
Update();
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -283,12 +331,24 @@ namespace Explorer
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Scene Root GameObjects:", null);
|
||||
|
||||
if (m_getRootObjectsFailed)
|
||||
{
|
||||
if (GUILayout.Button("Update Root Object List (auto-update failed!)", null))
|
||||
{
|
||||
Update_Impl(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_objectList.Count > 0)
|
||||
{
|
||||
foreach (var obj in m_objectList)
|
||||
for (int i = 0; i < m_objectList.Count; i++)
|
||||
{
|
||||
var obj = m_objectList[i];
|
||||
|
||||
if (obj == null) continue;
|
||||
|
||||
if (!obj.RefGameObject)
|
||||
{
|
||||
string label = "<color=red><i>null";
|
||||
@ -303,7 +363,7 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
UIHelpers.FastGameobjButton(obj.RefGameObject,
|
||||
UIHelpers.GOButton_Impl(obj.RefGameObject,
|
||||
obj.EnabledColor,
|
||||
obj.Label,
|
||||
obj.RefGameObject.activeSelf,
|
||||
@ -328,13 +388,13 @@ namespace Explorer
|
||||
{
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.PageLimit && i < m_searchResults.Count; i++)
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < m_searchResults.Count; i++)
|
||||
{
|
||||
var obj = m_searchResults[i];
|
||||
|
||||
if (obj.RefGameObject)
|
||||
{
|
||||
UIHelpers.FastGameobjButton(obj.RefGameObject,
|
||||
UIHelpers.GOButton_Impl(obj.RefGameObject,
|
||||
obj.EnabledColor,
|
||||
obj.Label,
|
||||
obj.RefGameObject.activeSelf,
|
@ -13,7 +13,7 @@ namespace Explorer
|
||||
{
|
||||
public static SearchPage Instance;
|
||||
|
||||
public override string Name { get => "Object Search"; set => base.Name = value; }
|
||||
public override string Name { get => "Object Search"; }
|
||||
|
||||
private string m_searchInput = "";
|
||||
private string m_typeInput = "";
|
||||
@ -75,7 +75,7 @@ namespace Explorer
|
||||
m_searchResults.Add(cache);
|
||||
}
|
||||
|
||||
Pages.Count = m_searchResults.Count;
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
Pages.PageOffset = 0;
|
||||
}
|
||||
|
||||
@ -104,17 +104,16 @@ namespace Explorer
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
int count = m_searchResults.Count;
|
||||
Pages.CalculateMaxOffset();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (count > Pages.PageLimit)
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
// prev/next page buttons
|
||||
|
||||
if (Pages.Count > Pages.PageLimit)
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
@ -132,20 +131,17 @@ namespace Explorer
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
resultsScroll = UIHelpers.BeginScrollView(resultsScroll);
|
||||
resultsScroll = GUIUnstrip.BeginScrollView(resultsScroll);
|
||||
|
||||
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
//int offset = m_pageOffset * this.m_limit;
|
||||
//if (offset >= count) m_pageOffset = 0;
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.PageLimit && i < count; i++)
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||
//m_searchResults[i].DrawValue(MainMenu.MainRect);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -153,7 +149,7 @@ namespace Explorer
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", null);
|
||||
}
|
||||
|
||||
UIHelpers.EndScrollView();
|
||||
GUIUnstrip.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch
|
||||
@ -176,16 +172,6 @@ namespace Explorer
|
||||
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
|
||||
//GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
//GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
//var resultinput = m_limit.ToString();
|
||||
//resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
|
||||
//if (int.TryParse(resultinput, out int _i) && _i > 0)
|
||||
//{
|
||||
// m_limit = _i;
|
||||
//}
|
||||
//GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
@ -267,7 +253,7 @@ namespace Explorer
|
||||
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string _search, string _type)
|
||||
private List<object> FindAllObjectsOfType(string searchQuery, string typeName)
|
||||
{
|
||||
Il2CppSystem.Type searchType = null;
|
||||
|
||||
@ -275,13 +261,18 @@ namespace Explorer
|
||||
{
|
||||
try
|
||||
{
|
||||
var findType = ReflectionHelpers.GetTypeByName(_type);
|
||||
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName);
|
||||
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName);
|
||||
if (ReflectionHelpers.GetTypeByName(typeName) is Type t)
|
||||
{
|
||||
searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Could not find a Type by the name of '{typeName}'!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
||||
MelonLogger.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
@ -299,7 +290,10 @@ namespace Explorer
|
||||
|
||||
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
|
||||
{
|
||||
MelonLogger.LogError("Your Custom Class Type must inherit from UnityEngine.Object!");
|
||||
if (searchType != null)
|
||||
{
|
||||
MelonLogger.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
|
||||
}
|
||||
return new List<object>();
|
||||
}
|
||||
|
||||
@ -314,12 +308,13 @@ namespace Explorer
|
||||
{
|
||||
if (i >= 2000) break;
|
||||
|
||||
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower()))
|
||||
if (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchType == ReflectionHelpers.ComponentType && ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
|
||||
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
{
|
||||
// Transforms shouldn't really be counted as Components, skip them.
|
||||
// They're more akin to GameObjects.
|
||||
@ -377,43 +372,70 @@ namespace Explorer
|
||||
|
||||
// ====== other ========
|
||||
|
||||
private static bool FilterName(string name)
|
||||
{
|
||||
// Don't really want these instances.
|
||||
return !name.StartsWith("Mono")
|
||||
&& !name.StartsWith("System")
|
||||
&& !name.StartsWith("Il2CppSystem")
|
||||
&& !name.StartsWith("Iced");
|
||||
}
|
||||
|
||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
||||
public static IEnumerable<object> GetInstanceClassScanner()
|
||||
{
|
||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(x => !x.FullName.StartsWith("Mono"))
|
||||
.SelectMany(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;
|
||||
|
||||
foreach (var type in query)
|
||||
{
|
||||
object obj = null;
|
||||
try
|
||||
{
|
||||
obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
var pi = type.GetProperty("Instance", flags);
|
||||
|
||||
if (pi == null)
|
||||
{
|
||||
obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null);
|
||||
pi = type.GetProperty("Instance", flatFlags);
|
||||
}
|
||||
catch
|
||||
|
||||
if (pi != null)
|
||||
{
|
||||
obj = pi.GetValue(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fi = type.GetField("Instance", flags);
|
||||
|
||||
if (fi == null)
|
||||
{
|
||||
fi = type.GetField("Instance", flatFlags);
|
||||
}
|
||||
|
||||
if (fi != null)
|
||||
{
|
||||
obj = fi.GetValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (obj != null && !obj.ToString().StartsWith("Mono"))
|
||||
catch { }
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
var t = ReflectionHelpers.GetActualType(obj);
|
||||
|
||||
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppEnumerable(t))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
|
||||
{
|
||||
try { return asm.GetTypes(); }
|
||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
||||
catch { return Enumerable.Empty<Type>(); }
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ namespace Explorer
|
||||
{
|
||||
public abstract class WindowPage
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
public virtual string Name { get; }
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
@ -56,7 +56,10 @@ namespace Explorer
|
||||
{
|
||||
_horizBarStyle = new GUIStyle();
|
||||
_horizBarStyle.normal.background = Texture2D.whiteTexture;
|
||||
_horizBarStyle.margin = new RectOffset(0, 0, 4, 4);
|
||||
var rectOffset = new RectOffset();
|
||||
rectOffset.top = 4;
|
||||
rectOffset.bottom = 4;
|
||||
_horizBarStyle.margin = rectOffset;
|
||||
_horizBarStyle.fixedHeight = 2;
|
||||
}
|
||||
|
||||
@ -73,7 +76,10 @@ namespace Explorer
|
||||
{
|
||||
_horizBarSmallStyle = new GUIStyle();
|
||||
_horizBarSmallStyle.normal.background = Texture2D.whiteTexture;
|
||||
_horizBarSmallStyle.margin = new RectOffset(0, 0, 2, 2);
|
||||
var rectOffset = new RectOffset();
|
||||
rectOffset.top = 2;
|
||||
rectOffset.bottom = 2;
|
||||
_horizBarSmallStyle.margin = rectOffset;
|
||||
_horizBarSmallStyle.fixedHeight = 1;
|
||||
}
|
||||
|
@ -29,9 +29,17 @@ namespace Explorer
|
||||
private Vector2 m_compScroll = Vector2.zero;
|
||||
private PageHelper CompPages = new PageHelper();
|
||||
|
||||
private readonly Vector3[] m_cachedInput = new Vector3[3];
|
||||
private float m_translateAmount = 0.3f;
|
||||
private float m_rotateAmount = 50f;
|
||||
private float m_scaleAmount = 0.1f;
|
||||
private bool m_freeze;
|
||||
private Vector3 m_frozenPosition;
|
||||
private Quaternion m_frozenRotation;
|
||||
private Vector3 m_frozenScale;
|
||||
private bool m_autoApplyTransform;
|
||||
private bool m_autoUpdateTransform;
|
||||
private bool m_localContext;
|
||||
|
||||
private readonly List<Component> m_cachedDestroyList = new List<Component>();
|
||||
//private string m_addComponentInput = "";
|
||||
@ -40,12 +48,6 @@ namespace Explorer
|
||||
|
||||
public bool GetObjectAsGameObject()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
MelonLogger.Log("Target is null!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetType = Target.GetType();
|
||||
|
||||
if (targetType == typeof(GameObject))
|
||||
@ -73,45 +75,87 @@ namespace Explorer
|
||||
|
||||
m_name = m_object.name;
|
||||
m_scene = string.IsNullOrEmpty(m_object.scene.name)
|
||||
? "None"
|
||||
? "None (Asset/Resource)"
|
||||
: m_object.scene.name;
|
||||
|
||||
CacheTransformValues();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
private void CacheTransformValues()
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_cachedInput[0] = m_object.transform.localPosition;
|
||||
m_cachedInput[1] = m_object.transform.localEulerAngles;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cachedInput[0] = m_object.transform.position;
|
||||
m_cachedInput[1] = m_object.transform.eulerAngles;
|
||||
}
|
||||
m_cachedInput[2] = m_object.transform.localScale;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
MelonLogger.Log("Target is null!");
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
else if (Target is UnityEngine.Object uObj)
|
||||
{
|
||||
if (!uObj)
|
||||
{
|
||||
MelonLogger.Log("Target was destroyed!");
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_object && !GetObjectAsGameObject())
|
||||
{
|
||||
throw new Exception("Object is null!");
|
||||
}
|
||||
|
||||
var list = new List<Transform>();
|
||||
if (m_freeze)
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_object.transform.localPosition = m_frozenPosition;
|
||||
m_object.transform.localRotation = m_frozenRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_object.transform.position = m_frozenPosition;
|
||||
m_object.transform.rotation = m_frozenRotation;
|
||||
}
|
||||
m_object.transform.localScale = m_frozenScale;
|
||||
}
|
||||
|
||||
// update child objects
|
||||
var childList = new List<Transform>();
|
||||
for (int i = 0; i < m_object.transform.childCount; i++)
|
||||
{
|
||||
list.Add(m_object.transform.GetChild(i));
|
||||
childList.Add(m_object.transform.GetChild(i));
|
||||
}
|
||||
list.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
m_children = list.ToArray();
|
||||
childList.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
m_children = childList.ToArray();
|
||||
|
||||
ChildPages.Count = m_children.Length;
|
||||
ChildPages.ItemCount = m_children.Length;
|
||||
|
||||
var list2 = new List<Component>();
|
||||
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
|
||||
{
|
||||
var ilType = comp.GetIl2CppType();
|
||||
if (ilType == ReflectionHelpers.TransformType)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// update components
|
||||
var compList = new Il2CppSystem.Collections.Generic.List<Component>();
|
||||
m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
|
||||
|
||||
list2.Add(comp);
|
||||
}
|
||||
m_components = list2.ToArray();
|
||||
m_components = compList.ToArray();
|
||||
|
||||
CompPages.Count = m_components.Length;
|
||||
CompPages.ItemCount = m_components.Length;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -169,7 +213,7 @@ namespace Explorer
|
||||
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
|
||||
}
|
||||
|
||||
scroll = UIHelpers.BeginScrollView(scroll);
|
||||
scroll = GUIUnstrip.BeginScrollView(scroll);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
|
||||
@ -220,7 +264,7 @@ namespace Explorer
|
||||
|
||||
GameObjectControls();
|
||||
|
||||
UIHelpers.EndScrollView();
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
@ -238,17 +282,15 @@ namespace Explorer
|
||||
private void TransformList(Rect m_rect)
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
m_transformScroll = UIHelpers.BeginScrollView(m_transformScroll);
|
||||
m_transformScroll = GUIUnstrip.BeginScrollView(m_transformScroll);
|
||||
|
||||
GUILayout.Label("<b><size=15>Children</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
ChildPages.DrawLimitInputArea();
|
||||
|
||||
if (ChildPages.Count > ChildPages.PageLimit)
|
||||
if (ChildPages.ItemCount > ChildPages.ItemsPerPage)
|
||||
{
|
||||
ChildPages.CalculateMaxOffset();
|
||||
|
||||
ChildPages.CurrentPageLabel();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
@ -269,7 +311,7 @@ namespace Explorer
|
||||
{
|
||||
int start = ChildPages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + ChildPages.PageLimit && j < ChildPages.Count); j++)
|
||||
for (int j = start; (j < start + ChildPages.ItemsPerPage && j < ChildPages.ItemCount); j++)
|
||||
{
|
||||
var obj = m_children[j];
|
||||
|
||||
@ -279,7 +321,7 @@ namespace Explorer
|
||||
continue;
|
||||
}
|
||||
|
||||
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
|
||||
UIHelpers.GOButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -287,23 +329,21 @@ namespace Explorer
|
||||
GUILayout.Label("<i>None</i>", null);
|
||||
}
|
||||
|
||||
UIHelpers.EndScrollView();
|
||||
GUIUnstrip.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ComponentList(Rect m_rect)
|
||||
{
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
m_compScroll = UIHelpers.BeginScrollView(m_compScroll);
|
||||
m_compScroll = GUIUnstrip.BeginScrollView(m_compScroll);
|
||||
GUILayout.Label("<b><size=15>Components</size></b>", null);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
CompPages.DrawLimitInputArea();
|
||||
|
||||
if (CompPages.Count > CompPages.PageLimit)
|
||||
if (CompPages.ItemCount > CompPages.ItemsPerPage)
|
||||
{
|
||||
CompPages.CalculateMaxOffset();
|
||||
|
||||
CompPages.CurrentPageLabel();
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
@ -330,7 +370,7 @@ namespace Explorer
|
||||
{
|
||||
int start = CompPages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + CompPages.PageLimit && j < CompPages.Count); j++)
|
||||
for (int j = start; (j < start + CompPages.ItemsPerPage && j < CompPages.ItemCount); j++)
|
||||
{
|
||||
var component = m_components[j];
|
||||
|
||||
@ -369,7 +409,7 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
UIHelpers.EndScrollView();
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
@ -416,6 +456,16 @@ namespace Explorer
|
||||
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
|
||||
}
|
||||
|
||||
var lbl = m_freeze ? "<color=lime>Unfreeze</color>" : "<color=orange>Freeze Pos/Rot</color>";
|
||||
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(110) }))
|
||||
{
|
||||
m_freeze = !m_freeze;
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
@ -440,10 +490,52 @@ namespace Explorer
|
||||
|
||||
GUILayout.BeginVertical(GUI.skin.box, null);
|
||||
|
||||
var t = m_object.transform;
|
||||
TranslateControl(t, TranslateType.Position, ref m_translateAmount, false);
|
||||
TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true);
|
||||
TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false);
|
||||
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)
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_object.transform.localPosition = m_cachedInput[0];
|
||||
m_object.transform.localEulerAngles = m_cachedInput[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
m_object.transform.position = m_cachedInput[0];
|
||||
m_object.transform.eulerAngles = m_cachedInput[1];
|
||||
}
|
||||
m_object.transform.localScale = m_cachedInput[2];
|
||||
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
if (GUILayout.Button("<color=lime>Update from Transform</color>", null) || m_autoUpdateTransform)
|
||||
{
|
||||
CacheTransformValues();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?");
|
||||
BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?");
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
bool b = m_localContext;
|
||||
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "red") + ">Use local transform values?</color>", null);
|
||||
if (b != m_localContext)
|
||||
{
|
||||
m_localContext = b;
|
||||
CacheTransformValues();
|
||||
if (m_freeze)
|
||||
{
|
||||
UpdateFreeze();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
@ -457,6 +549,30 @@ namespace Explorer
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void UpdateFreeze()
|
||||
{
|
||||
if (m_localContext)
|
||||
{
|
||||
m_frozenPosition = m_object.transform.localPosition;
|
||||
m_frozenRotation = m_object.transform.localRotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_frozenPosition = m_object.transform.position;
|
||||
m_frozenRotation = m_object.transform.rotation;
|
||||
}
|
||||
m_frozenScale = m_object.transform.localScale;
|
||||
}
|
||||
|
||||
private void BoolToggle(ref bool value, string message)
|
||||
{
|
||||
string lbl = "<color=";
|
||||
lbl += value ? "lime" : "red";
|
||||
lbl += $">{message}</color>";
|
||||
|
||||
value = GUILayout.Toggle(value, lbl, null);
|
||||
}
|
||||
|
||||
public enum TranslateType
|
||||
{
|
||||
Position,
|
||||
@ -464,50 +580,55 @@ namespace Explorer
|
||||
Scale
|
||||
}
|
||||
|
||||
private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime)
|
||||
private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime)
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Label("<color=cyan><b>" + mode + "</b></color>:", new GUILayoutOption[] { GUILayout.Width(65) });
|
||||
GUILayout.Label($"<color=cyan><b>{(m_localContext ? "Local " : "")}{mode}</b></color>:",
|
||||
new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) });
|
||||
|
||||
Vector3 vector = Vector3.zero;
|
||||
var transform = m_object.transform;
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position: vector = transform.localPosition; break;
|
||||
case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break;
|
||||
case TranslateType.Scale: vector = transform.localScale; break;
|
||||
case TranslateType.Position:
|
||||
var pos = m_localContext ? transform.localPosition : transform.position;
|
||||
GUILayout.Label(pos.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
case TranslateType.Rotation:
|
||||
var rot = m_localContext ? transform.localEulerAngles : transform.eulerAngles;
|
||||
GUILayout.Label(rot.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
case TranslateType.Scale:
|
||||
GUILayout.Label(transform.localScale.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
break;
|
||||
}
|
||||
GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
Vector3 input = m_cachedInput[(int)mode];
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
|
||||
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.x, amount, multByTime);
|
||||
PlusMinusFloat(ref input.x, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.y, amount, multByTime);
|
||||
PlusMinusFloat(ref input.y, amount, multByTime);
|
||||
|
||||
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
PlusMinusFloat(ref vector.z, amount, multByTime);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case TranslateType.Position: transform.localPosition = vector; break;
|
||||
case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break;
|
||||
case TranslateType.Scale: transform.localScale = vector; break;
|
||||
}
|
||||
PlusMinusFloat(ref input.z, amount, multByTime);
|
||||
|
||||
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
var input = amount.ToString("F3");
|
||||
input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) });
|
||||
if (float.TryParse(input, out float f))
|
||||
var amountInput = amount.ToString("F3");
|
||||
amountInput = GUILayout.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
|
||||
if (float.TryParse(amountInput, out float f))
|
||||
{
|
||||
amount = f;
|
||||
}
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private void PlusMinusFloat(ref float f, float amount, bool multByTime)
|
||||
@ -527,7 +648,5 @@ namespace Explorer
|
||||
f += multByTime ? amount * Time.deltaTime : amount;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -22,28 +22,36 @@ namespace Explorer
|
||||
private CacheObjectBase[] m_cachedMembersFiltered;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
//private int m_pageOffset;
|
||||
//private int m_limitPerPage = 20;
|
||||
|
||||
private bool m_autoUpdate = false;
|
||||
private string m_search = "";
|
||||
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",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _methodStartsWithBlacklist = new HashSet<string>
|
||||
{
|
||||
// Pointless (handled by Properties)
|
||||
"get_",
|
||||
"set_"
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
var type = ReflectionHelpers.GetActualType(Target);
|
||||
TargetType = ReflectionHelpers.GetActualType(Target);
|
||||
|
||||
TargetType = type;
|
||||
|
||||
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
||||
|
||||
CacheMembers(types);
|
||||
CacheMembers(ReflectionHelpers.GetAllBaseTypes(Target));
|
||||
|
||||
// cache the extra cast-caching
|
||||
if (Target is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var unityObj = ilObject.TryCast<UnityEngine.Object>();
|
||||
@ -58,17 +66,24 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_filter = MemberTypes.All;
|
||||
m_autoUpdate = true;
|
||||
Update();
|
||||
|
||||
m_autoUpdate = false;
|
||||
m_filter = MemberTypes.Property;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (Target == null)
|
||||
{
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
else if (Target is UnityEngine.Object uObj)
|
||||
{
|
||||
if (!uObj)
|
||||
{
|
||||
DestroyWindow();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_cachedMembersFiltered = m_allCachedMembers.Where(x => ShouldProcessMember(x)).ToArray();
|
||||
|
||||
if (m_autoUpdate)
|
||||
@ -87,28 +102,32 @@ namespace Explorer
|
||||
|
||||
private bool ShouldProcessMember(CacheObjectBase holder)
|
||||
{
|
||||
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType) return false;
|
||||
// check MemberTypes filter
|
||||
if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType)
|
||||
return false;
|
||||
|
||||
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
|
||||
// hide failed reflection
|
||||
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection)
|
||||
return false;
|
||||
|
||||
if (m_search == "" || holder.MemInfo == null) return true;
|
||||
// see if we should do name search
|
||||
if (m_search == "" || holder.MemInfo == null)
|
||||
return true;
|
||||
|
||||
var name = holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name;
|
||||
|
||||
return name.ToLower().Contains(m_search.ToLower());
|
||||
// ok do name search
|
||||
return (holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name)
|
||||
.ToLower()
|
||||
.Contains(m_search.ToLower());
|
||||
}
|
||||
|
||||
private void CacheMembers(Type[] types)
|
||||
{
|
||||
var list = new List<CacheObjectBase>();
|
||||
|
||||
var names = new List<string>();
|
||||
var cachedSigs = new List<string>();
|
||||
|
||||
foreach (var declaringType in types)
|
||||
{
|
||||
MemberInfo[] infos;
|
||||
string exception = null;
|
||||
|
||||
try
|
||||
{
|
||||
infos = declaringType.GetMembers(ReflectionHelpers.CommonFlags);
|
||||
@ -120,6 +139,7 @@ namespace Explorer
|
||||
}
|
||||
|
||||
object target = Target;
|
||||
string exception = null;
|
||||
|
||||
if (target is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
@ -135,46 +155,60 @@ namespace Explorer
|
||||
|
||||
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 name = member.DeclaringType.Name + "." + member.Name;
|
||||
if (_typeAndMemberBlacklist.Any(it => it == name))
|
||||
continue;
|
||||
|
||||
if (_methodStartsWithBlacklist.Any(it => member.Name.StartsWith(it)))
|
||||
continue;
|
||||
|
||||
// compare signature to already cached members
|
||||
var signature = $"{member.DeclaringType.Name}.{member.Name}";
|
||||
if (member is MethodInfo mi)
|
||||
{
|
||||
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)
|
||||
{
|
||||
signature += " (";
|
||||
foreach (var param in _args)
|
||||
{
|
||||
continue;
|
||||
signature += $"{param.ParameterType.Name} {param.Name}, ";
|
||||
}
|
||||
signature += ")";
|
||||
}
|
||||
|
||||
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(signature))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
var cached = CacheObjectBase.GetCacheObject(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(signature);
|
||||
list.Add(cached);
|
||||
cached.ReflectionException = exception;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.LogWarning($"Exception caching member {signature}!");
|
||||
MelonLogger.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -256,17 +290,15 @@ namespace Explorer
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
Pages.Count = m_cachedMembersFiltered.Length;
|
||||
Pages.ItemCount = m_cachedMembersFiltered.Length;
|
||||
|
||||
// prev/next page buttons
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (Pages.Count > Pages.PageLimit)
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
Pages.CalculateMaxOffset();
|
||||
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||
@ -283,7 +315,7 @@ namespace Explorer
|
||||
|
||||
// ====== BODY ======
|
||||
|
||||
scroll = UIHelpers.BeginScrollView(scroll);
|
||||
scroll = GUIUnstrip.BeginScrollView(scroll);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
@ -294,7 +326,7 @@ namespace Explorer
|
||||
var members = this.m_cachedMembersFiltered;
|
||||
int start = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int j = start; (j < start + Pages.PageLimit && j < members.Length); j++)
|
||||
for (int j = start; (j < start + Pages.ItemsPerPage && j < members.Length); j++)
|
||||
{
|
||||
var holder = members[j];
|
||||
|
||||
@ -311,12 +343,12 @@ namespace Explorer
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// if not last element
|
||||
if (!(j == (start + Pages.PageLimit - 1) || j == (members.Length - 1)))
|
||||
if (!(j == (start + Pages.ItemsPerPage - 1) || j == (members.Length - 1)))
|
||||
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
UIHelpers.EndScrollView();
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
if (!WindowManager.TabView)
|
||||
{
|
@ -31,17 +31,20 @@ namespace Explorer
|
||||
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 r = GUIUnstrip.GetLastRect();
|
||||
|
||||
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
|
||||
var mousePos = InputHelper.mousePosition;
|
||||
|
||||
if (r.Contains(mouse) && Input.GetMouseButtonDown(0))
|
||||
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
|
||||
|
||||
if (r.Contains(mouse) && InputHelper.GetMouseButtonDown(0))
|
||||
{
|
||||
isResizing = true;
|
||||
m_currentWindow = ID;
|
||||
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
|
||||
}
|
||||
else if (!Input.GetMouseButton(0))
|
||||
else if (!InputHelper.GetMouseButton(0))
|
||||
{
|
||||
isResizing = false;
|
||||
}
|
||||
@ -65,6 +68,7 @@ namespace Explorer
|
||||
{
|
||||
RESIZE_FAILED = true;
|
||||
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
|
||||
MelonLogger.Log(e.StackTrace);
|
||||
return origRect;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ namespace Explorer
|
||||
}
|
||||
|
||||
public override void Init() { }
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
while (TargetTabID >= WindowManager.Windows.Count)
|
@ -17,7 +17,7 @@ namespace Explorer
|
||||
public object Target;
|
||||
|
||||
public int windowID;
|
||||
public Rect m_rect = new Rect(0, 0, 550, 700);
|
||||
public Rect m_rect = new Rect(0,0, ModConfig.Instance.Default_Window_Size.x,ModConfig.Instance.Default_Window_Size.y);
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
@ -49,15 +49,7 @@ namespace Explorer
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
if (CppExplorer.ShowMenu)
|
||||
{
|
||||
var origSkin = GUI.skin;
|
||||
|
||||
GUI.skin = UIStyles.WindowSkin;
|
||||
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Title);
|
||||
|
||||
GUI.skin = origSkin;
|
||||
}
|
||||
m_rect = GUI.Window(windowID, m_rect, (GUI.WindowFunction)WindowFunction, Title);
|
||||
}
|
||||
|
||||
public void Header()
|
@ -90,7 +90,7 @@ namespace Explorer
|
||||
{
|
||||
createdNew = false;
|
||||
|
||||
if (Input.GetKey(KeyCode.LeftShift))
|
||||
if (InputHelper.GetKey(KeyCode.LeftShift))
|
||||
{
|
||||
forceReflection = true;
|
||||
}
|
||||
@ -109,7 +109,7 @@ namespace Explorer
|
||||
|
||||
if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
|
||||
{
|
||||
if (iCurrent.GetIl2CppType() != iTarget.GetIl2CppType())
|
||||
if (iCurrent.GetIl2CppType().FullName != iTarget.GetIl2CppType().FullName)
|
||||
{
|
||||
if (iCurrent is Transform transform)
|
||||
{
|
||||
@ -192,7 +192,8 @@ namespace Explorer
|
||||
|
||||
private static bool RectContainsMouse(Rect rect)
|
||||
{
|
||||
return rect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
|
||||
var mousePos = InputHelper.mousePosition;
|
||||
return rect.Contains(new Vector2(mousePos.x, Screen.height - mousePos.y));
|
||||
}
|
||||
|
||||
public static int NextWindowID()
|
100
src/Tests/TestClass.cs
Normal file
100
src/Tests/TestClass.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.Tests
|
||||
{
|
||||
public class TestClass
|
||||
{
|
||||
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
|
||||
private static TestClass m_instance;
|
||||
|
||||
public TestClass()
|
||||
{
|
||||
ILHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
|
||||
ILHashSetTest.Add("1");
|
||||
ILHashSetTest.Add("2");
|
||||
ILHashSetTest.Add("3");
|
||||
}
|
||||
|
||||
// test HashSets
|
||||
|
||||
public static HashSet<string> HashSetTest = new HashSet<string>
|
||||
{
|
||||
"One",
|
||||
"Two",
|
||||
"Three"
|
||||
};
|
||||
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> ILHashSetTest;
|
||||
|
||||
// Test indexed parameter
|
||||
|
||||
public string this[int arg0, string arg1]
|
||||
{
|
||||
get
|
||||
{
|
||||
return $"arg0: {arg0}, arg1: {arg1}";
|
||||
}
|
||||
}
|
||||
|
||||
// Test basic list
|
||||
|
||||
public static List<string> TestList = new List<string>
|
||||
{
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"etc..."
|
||||
};
|
||||
|
||||
// Test a nested dictionary
|
||||
|
||||
public static Dictionary<int, Dictionary<string, int>> NestedDictionary = new Dictionary<int, Dictionary<string, int>>
|
||||
{
|
||||
{
|
||||
1,
|
||||
new Dictionary<string, int>
|
||||
{
|
||||
{
|
||||
"Sub 1", 123
|
||||
},
|
||||
{
|
||||
"Sub 2", 456
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
2,
|
||||
new Dictionary<string, int>
|
||||
{
|
||||
{
|
||||
"Sub 3", 789
|
||||
},
|
||||
{
|
||||
"Sub 4", 000
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Test a basic method
|
||||
|
||||
public static Color TestMethod(float r, float g, float b, float a)
|
||||
{
|
||||
return new Color(r, g, b, a);
|
||||
}
|
||||
|
||||
// A method with default arguments
|
||||
|
||||
public static Vector3 TestDefaultArgs(float arg0, float arg1, float arg2 = 5.0f)
|
||||
{
|
||||
return new Vector3(arg0, arg1, arg2);
|
||||
}
|
||||
}
|
||||
}
|
406
src/UnstripFixes/GUIUnstrip.cs
Normal file
406
src/UnstripFixes/GUIUnstrip.cs
Normal file
@ -0,0 +1,406 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using MelonLoader;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using System.Reflection;
|
||||
using UnityEngineInternal;
|
||||
using Harmony;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class GUIUnstrip
|
||||
{
|
||||
public static int s_ScrollControlId;
|
||||
|
||||
public static bool ScrollFailed = false;
|
||||
public static bool ManualUnstripFailed = false;
|
||||
|
||||
private static GenericStack ScrollStack
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_scrollViewStatesInfo == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_scrollViewStatesInfo = typeof(GUI).GetProperty("scrollViewStates");
|
||||
if (m_scrollViewStatesInfo == null) throw new Exception();
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_scrollViewStatesInfo = typeof(GUI).GetProperty("s_scrollViewStates");
|
||||
}
|
||||
}
|
||||
|
||||
return (GenericStack)m_scrollViewStatesInfo?.GetValue(null, null);
|
||||
}
|
||||
}
|
||||
private static PropertyInfo m_scrollViewStatesInfo;
|
||||
|
||||
|
||||
public static Rect GetLastRect()
|
||||
{
|
||||
EventType type = Event.current.type;
|
||||
Rect last;
|
||||
if (type != EventType.Layout && type != EventType.Used)
|
||||
{
|
||||
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
|
||||
}
|
||||
else
|
||||
{
|
||||
last = GUILayoutUtility.kDummyRect;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
// Fix for BeginScrollView.
|
||||
|
||||
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
|
||||
{
|
||||
// First, just try normal way, may not have been stripped or was unstripped successfully.
|
||||
if (!ScrollFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GUILayout.BeginScrollView(scroll, options);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ScrollFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Try manual implementation.
|
||||
if (!ManualUnstripFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
return BeginScrollView_ImplLayout(scroll, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log("Exception on manual BeginScrollView: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
|
||||
ManualUnstripFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Sorry! No scrolling for you.
|
||||
return scroll;
|
||||
}
|
||||
|
||||
public static void EndScrollView(bool handleScrollWheel = true)
|
||||
{
|
||||
// Only end the scroll view for the relevant BeginScrollView option, if any.
|
||||
|
||||
if (!ScrollFailed)
|
||||
{
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
else if (!ManualUnstripFailed)
|
||||
{
|
||||
GUILayoutUtility.EndLayoutGroup();
|
||||
|
||||
EndScrollView_Impl(handleScrollWheel);
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
|
||||
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
var guiscrollGroup = GUILayoutUtility.BeginLayoutGroup(background, null, Il2CppType.Of<GUIScrollGroup>())
|
||||
.TryCast<GUIScrollGroup>();
|
||||
|
||||
EventType type = Event.current.type;
|
||||
if (type == EventType.Layout)
|
||||
{
|
||||
guiscrollGroup.resetCoords = true;
|
||||
guiscrollGroup.isVertical = true;
|
||||
guiscrollGroup.stretchWidth = 1;
|
||||
guiscrollGroup.stretchHeight = 1;
|
||||
guiscrollGroup.verticalScrollbar = verticalScrollbar;
|
||||
guiscrollGroup.horizontalScrollbar = horizontalScrollbar;
|
||||
guiscrollGroup.needsVerticalScrollbar = alwaysShowVertical;
|
||||
guiscrollGroup.needsHorizontalScrollbar = alwaysShowHorizontal;
|
||||
guiscrollGroup.ApplyOptions(options);
|
||||
}
|
||||
|
||||
return BeginScrollView_Impl(guiscrollGroup.rect,
|
||||
scrollPosition,
|
||||
new Rect(0f, 0f, guiscrollGroup.clientWidth, guiscrollGroup.clientHeight),
|
||||
alwaysShowHorizontal,
|
||||
alwaysShowVertical,
|
||||
horizontalScrollbar,
|
||||
verticalScrollbar,
|
||||
background
|
||||
);
|
||||
}
|
||||
|
||||
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
|
||||
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive);
|
||||
|
||||
var scrollViewState = GUIUtility.GetStateObject(Il2CppType.Of<ScrollViewState>(), controlID).TryCast<ScrollViewState>();
|
||||
|
||||
var scrollExt = ScrollViewStateUnstrip.FromPointer(scrollViewState.Pointer);
|
||||
|
||||
if (scrollExt == null) throw new Exception($"Could not get scrollExt for pointer '{scrollViewState.Pointer}'!");
|
||||
|
||||
bool apply = scrollExt.apply;
|
||||
if (apply)
|
||||
{
|
||||
scrollPosition = scrollExt.scrollPosition;
|
||||
scrollExt.apply = false;
|
||||
}
|
||||
|
||||
scrollExt.position = position;
|
||||
|
||||
scrollExt.scrollPosition = scrollPosition;
|
||||
scrollExt.visibleRect = scrollExt.viewRect = viewRect;
|
||||
|
||||
var rect = scrollExt.visibleRect;
|
||||
rect.width = position.width;
|
||||
rect.height = position.height;
|
||||
|
||||
ScrollStack.Push(scrollViewState);
|
||||
|
||||
Rect screenRect = new Rect(position.x, position.y, position.width, position.height);
|
||||
EventType type = Event.current.type;
|
||||
if (type != EventType.Layout)
|
||||
{
|
||||
if (type != EventType.Used)
|
||||
{
|
||||
bool flag = alwaysShowVertical;
|
||||
bool flag2 = alwaysShowHorizontal;
|
||||
if (flag2 || viewRect.width > screenRect.width)
|
||||
{
|
||||
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
|
||||
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
flag2 = true;
|
||||
}
|
||||
if (flag || viewRect.height > screenRect.height)
|
||||
{
|
||||
rect.width = position.width - verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
|
||||
|
||||
screenRect.width -= verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
|
||||
flag = true;
|
||||
if (!flag2 && viewRect.width > screenRect.width)
|
||||
{
|
||||
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
|
||||
flag2 = true;
|
||||
}
|
||||
}
|
||||
if (Event.current.type == EventType.Repaint && background != GUIStyle.none)
|
||||
{
|
||||
background.Draw(position, position.Contains(Event.current.mousePosition), false, flag2 && flag, false);
|
||||
}
|
||||
if (flag2 && horizontalScrollbar != GUIStyle.none)
|
||||
{
|
||||
scrollPosition.x = HorizBar_Impl(
|
||||
new Rect(
|
||||
position.x,
|
||||
position.yMax - horizontalScrollbar.fixedHeight,
|
||||
screenRect.width,
|
||||
horizontalScrollbar.fixedHeight),
|
||||
scrollPosition.x,
|
||||
Mathf.Min(screenRect.width, viewRect.width),
|
||||
0f,
|
||||
viewRect.width,
|
||||
horizontalScrollbar
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
scrollPosition.x = ((horizontalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.x, 0f, Mathf.Max(viewRect.width - position.width, 0f)) : 0f);
|
||||
}
|
||||
if (flag && verticalScrollbar != GUIStyle.none)
|
||||
{
|
||||
scrollPosition.y = VertBar_Impl(
|
||||
new Rect(
|
||||
screenRect.xMax + (float)verticalScrollbar.margin.left,
|
||||
screenRect.y,
|
||||
verticalScrollbar.fixedWidth,
|
||||
screenRect.height),
|
||||
scrollPosition.y,
|
||||
Mathf.Min(screenRect.height, viewRect.height),
|
||||
0f,
|
||||
viewRect.height,
|
||||
verticalScrollbar
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
scrollPosition.y = ((verticalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.y, 0f, Mathf.Max(viewRect.height - position.height, 0f)) : 0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
|
||||
}
|
||||
GUIClip.Push(screenRect, new Vector2(Mathf.Round(-scrollPosition.x - viewRect.x), Mathf.Round(-scrollPosition.y - viewRect.y)), Vector2.zero, false);
|
||||
|
||||
return scrollPosition;
|
||||
}
|
||||
|
||||
public static float HorizBar_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
|
||||
{
|
||||
return Scroller_Impl(position, value, size, leftValue, rightValue, style,
|
||||
GUI.skin.GetStyle(style.name + "thumb"),
|
||||
GUI.skin.GetStyle(style.name + "leftbutton"),
|
||||
GUI.skin.GetStyle(style.name + "rightbutton"),
|
||||
true);
|
||||
}
|
||||
|
||||
public static float VertBar_Impl(Rect position, float value, float size, float topValue, float bottomValue, GUIStyle style)
|
||||
{
|
||||
return Scroller_Impl(position, value, size, topValue, bottomValue, style,
|
||||
GUI.skin.GetStyle(style.name + "thumb"),
|
||||
GUI.skin.GetStyle(style.name + "upbutton"),
|
||||
GUI.skin.GetStyle(style.name + "downbutton"),
|
||||
false);
|
||||
}
|
||||
|
||||
private static void EndScrollView_Impl(bool handleScrollWheel)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
if (ScrollStack.Count <= 0) return;
|
||||
|
||||
var state = ScrollStack.Peek().TryCast<ScrollViewState>();
|
||||
var scrollExt = ScrollViewStateUnstrip.FromPointer(state.Pointer);
|
||||
|
||||
if (scrollExt == null) throw new Exception("Could not get scrollExt!");
|
||||
|
||||
GUIClip.Pop();
|
||||
|
||||
ScrollStack.Pop();
|
||||
|
||||
var position = scrollExt.position;
|
||||
|
||||
if (handleScrollWheel && Event.current.type == EventType.ScrollWheel && position.Contains(Event.current.mousePosition))
|
||||
{
|
||||
var pos = scrollExt.scrollPosition;
|
||||
pos.x = Mathf.Clamp(scrollExt.scrollPosition.x + Event.current.delta.x * 20f, 0f, scrollExt.viewRect.width - scrollExt.visibleRect.width);
|
||||
pos.y = Mathf.Clamp(scrollExt.scrollPosition.y + Event.current.delta.y * 20f, 0f, scrollExt.viewRect.height - scrollExt.visibleRect.height);
|
||||
|
||||
if (scrollExt.scrollPosition.x < 0f)
|
||||
{
|
||||
pos.x = 0f;
|
||||
}
|
||||
if (pos.y < 0f)
|
||||
{
|
||||
pos.y = 0f;
|
||||
}
|
||||
|
||||
scrollExt.apply = true;
|
||||
|
||||
Event.current.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private static float Scroller_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
int controlID = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
|
||||
Rect position2;
|
||||
Rect rect;
|
||||
Rect rect2;
|
||||
if (horiz)
|
||||
{
|
||||
position2 = new Rect(position.x + leftButton.fixedWidth, position.y, position.width - leftButton.fixedWidth - rightButton.fixedWidth, position.height);
|
||||
rect = new Rect(position.x, position.y, leftButton.fixedWidth, position.height);
|
||||
rect2 = new Rect(position.xMax - rightButton.fixedWidth, position.y, rightButton.fixedWidth, position.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
position2 = new Rect(position.x, position.y + leftButton.fixedHeight, position.width, position.height - leftButton.fixedHeight - rightButton.fixedHeight);
|
||||
rect = new Rect(position.x, position.y, position.width, leftButton.fixedHeight);
|
||||
rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight);
|
||||
}
|
||||
|
||||
value = Slider_Impl(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
|
||||
|
||||
bool flag = Event.current.type == EventType.MouseUp;
|
||||
if (ScrollerRepeatButton_Impl(controlID, rect, leftButton))
|
||||
{
|
||||
value -= 10f * ((leftValue >= rightValue) ? -1f : 1f);
|
||||
}
|
||||
if (ScrollerRepeatButton_Impl(controlID, rect2, rightButton))
|
||||
{
|
||||
value += 10f * ((leftValue >= rightValue) ? -1f : 1f);
|
||||
}
|
||||
if (flag && Event.current.type == EventType.Used)
|
||||
{
|
||||
s_ScrollControlId = 0;
|
||||
}
|
||||
if (leftValue < rightValue)
|
||||
{
|
||||
value = Mathf.Clamp(value, leftValue, rightValue - size);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = Mathf.Clamp(value, rightValue, leftValue - size);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static float Slider_Impl(Rect position, float value, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
|
||||
{
|
||||
if (id == 0)
|
||||
{
|
||||
id = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
|
||||
}
|
||||
var sliderHandler = new SliderHandlerUnstrip(position, value, size, start, end, slider, thumb, horiz, id);
|
||||
return sliderHandler.Handle();
|
||||
}
|
||||
|
||||
private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style)
|
||||
{
|
||||
bool result = false;
|
||||
if (GUI.DoRepeatButton(rect, GUIContent.none, style, FocusType.Passive))
|
||||
{
|
||||
bool flag = s_ScrollControlId != scrollerID;
|
||||
s_ScrollControlId = scrollerID;
|
||||
|
||||
if (flag)
|
||||
{
|
||||
result = true;
|
||||
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(250.0);
|
||||
}
|
||||
else if (Il2CppSystem.DateTime.Now >= GUI.nextScrollStepTime)
|
||||
{
|
||||
result = true;
|
||||
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(30.0);
|
||||
}
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
GUI.InternalRepaintEditorWindow();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
32
src/UnstripFixes/ScrollViewStateUnstrip.cs
Normal file
32
src/UnstripFixes/ScrollViewStateUnstrip.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Harmony;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class ScrollViewStateUnstrip
|
||||
{
|
||||
public Rect position;
|
||||
public Rect visibleRect;
|
||||
public Rect viewRect;
|
||||
public Vector2 scrollPosition;
|
||||
public bool apply;
|
||||
|
||||
public static Dictionary<IntPtr, ScrollViewStateUnstrip> Dict = new Dictionary<IntPtr, ScrollViewStateUnstrip>();
|
||||
|
||||
public static ScrollViewStateUnstrip FromPointer(IntPtr ptr)
|
||||
{
|
||||
if (!Dict.ContainsKey(ptr))
|
||||
{
|
||||
Dict.Add(ptr, new ScrollViewStateUnstrip());
|
||||
}
|
||||
|
||||
return Dict[ptr];
|
||||
}
|
||||
}
|
||||
}
|
371
src/UnstripFixes/SliderHandlerUnstrip.cs
Normal file
371
src/UnstripFixes/SliderHandlerUnstrip.cs
Normal file
@ -0,0 +1,371 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnhollowerRuntimeLib;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public struct SliderHandlerUnstrip
|
||||
{
|
||||
private readonly Rect position;
|
||||
private readonly float currentValue;
|
||||
private readonly float size;
|
||||
private readonly float start;
|
||||
private readonly float end;
|
||||
private readonly GUIStyle slider;
|
||||
private readonly GUIStyle thumb;
|
||||
private readonly bool horiz;
|
||||
private readonly int id;
|
||||
|
||||
public SliderHandlerUnstrip(Rect position, float currentValue, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
|
||||
{
|
||||
this.position = position;
|
||||
this.currentValue = currentValue;
|
||||
this.size = size;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.slider = slider;
|
||||
this.thumb = thumb;
|
||||
this.horiz = horiz;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public float Handle()
|
||||
{
|
||||
float result;
|
||||
if (this.slider == null || this.thumb == null)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (this.CurrentEventType())
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
return this.OnMouseDown();
|
||||
case EventType.MouseUp:
|
||||
return this.OnMouseUp();
|
||||
case EventType.MouseDrag:
|
||||
return this.OnMouseDrag();
|
||||
case EventType.Repaint:
|
||||
return this.OnRepaint();
|
||||
}
|
||||
result = this.currentValue;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float OnMouseDown()
|
||||
{
|
||||
float result;
|
||||
if (!this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.scrollTroughSide = 0;
|
||||
GUIUtility.hotControl = this.id;
|
||||
this.CurrentEvent().Use();
|
||||
if (this.ThumbSelectionRect().Contains(this.CurrentEvent().mousePosition))
|
||||
{
|
||||
this.StartDraggingWithValue(this.ClampedCurrentValue());
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.changed = true;
|
||||
if (this.SupportsPageMovements())
|
||||
{
|
||||
this.SliderState().isDragging = false;
|
||||
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(250.0);
|
||||
GUI.scrollTroughSide = this.CurrentScrollTroughSide();
|
||||
result = this.PageMovementValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
float num = this.ValueForCurrentMousePosition();
|
||||
this.StartDraggingWithValue(num);
|
||||
result = this.Clamp(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float OnMouseDrag()
|
||||
{
|
||||
float result;
|
||||
if (GUIUtility.hotControl != this.id)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
SliderState sliderState = this.SliderState();
|
||||
if (!sliderState.isDragging)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.changed = true;
|
||||
this.CurrentEvent().Use();
|
||||
float num = this.MousePosition() - sliderState.dragStartPos;
|
||||
float value = sliderState.dragStartValue + num / this.ValuesPerPixel();
|
||||
result = this.Clamp(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float OnMouseUp()
|
||||
{
|
||||
if (GUIUtility.hotControl == this.id)
|
||||
{
|
||||
this.CurrentEvent().Use();
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
return this.currentValue;
|
||||
}
|
||||
|
||||
private float OnRepaint()
|
||||
{
|
||||
this.slider.Draw(this.position, GUIContent.none, this.id);
|
||||
if (!this.IsEmptySlider() && this.currentValue >= this.MinValue() && this.currentValue <= this.MaxValue())
|
||||
{
|
||||
this.thumb.Draw(this.ThumbRect(), GUIContent.none, this.id);
|
||||
}
|
||||
float result;
|
||||
if (GUIUtility.hotControl != this.id || !this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else if (this.ThumbRect().Contains(this.CurrentEvent().mousePosition))
|
||||
{
|
||||
if (GUI.scrollTroughSide != 0)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
}
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.InternalRepaintEditorWindow();
|
||||
if (SystemClock.now < GUI.nextScrollStepTime)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else if (this.CurrentScrollTroughSide() != GUI.scrollTroughSide)
|
||||
{
|
||||
result = this.currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(30.0);
|
||||
if (this.SupportsPageMovements())
|
||||
{
|
||||
this.SliderState().isDragging = false;
|
||||
GUI.changed = true;
|
||||
result = this.PageMovementValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this.ClampedCurrentValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private EventType CurrentEventType()
|
||||
{
|
||||
return this.CurrentEvent().GetTypeForControl(this.id);
|
||||
}
|
||||
|
||||
|
||||
private int CurrentScrollTroughSide()
|
||||
{
|
||||
float num = (!this.horiz) ? this.CurrentEvent().mousePosition.y : this.CurrentEvent().mousePosition.x;
|
||||
float num2 = (!this.horiz) ? this.ThumbRect().y : this.ThumbRect().x;
|
||||
return (num <= num2) ? -1 : 1;
|
||||
}
|
||||
|
||||
private bool IsEmptySlider()
|
||||
{
|
||||
return this.start == this.end;
|
||||
}
|
||||
|
||||
private bool SupportsPageMovements()
|
||||
{
|
||||
return this.size != 0f && GUI.usePageScrollbars;
|
||||
}
|
||||
|
||||
private float PageMovementValue()
|
||||
{
|
||||
float num = this.currentValue;
|
||||
int num2 = (this.start <= this.end) ? 1 : -1;
|
||||
if (this.MousePosition() > this.PageUpMovementBound())
|
||||
{
|
||||
num += this.size * (float)num2 * 0.9f;
|
||||
}
|
||||
else
|
||||
{
|
||||
num -= this.size * (float)num2 * 0.9f;
|
||||
}
|
||||
return this.Clamp(num);
|
||||
}
|
||||
|
||||
private float PageUpMovementBound()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = this.ThumbRect().xMax - this.position.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this.ThumbRect().yMax - this.position.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Event CurrentEvent()
|
||||
{
|
||||
return Event.current;
|
||||
}
|
||||
|
||||
private float ValueForCurrentMousePosition()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = (this.MousePosition() - this.ThumbRect().width * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (this.MousePosition() - this.ThumbRect().height * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float Clamp(float value)
|
||||
{
|
||||
return Mathf.Clamp(value, this.MinValue(), this.MaxValue());
|
||||
}
|
||||
|
||||
private Rect ThumbSelectionRect()
|
||||
{
|
||||
return this.ThumbRect();
|
||||
}
|
||||
|
||||
private void StartDraggingWithValue(float dragStartValue)
|
||||
{
|
||||
SliderState sliderState = this.SliderState();
|
||||
sliderState.dragStartPos = this.MousePosition();
|
||||
sliderState.dragStartValue = dragStartValue;
|
||||
sliderState.isDragging = true;
|
||||
}
|
||||
|
||||
private SliderState SliderState()
|
||||
{
|
||||
return (SliderState)GUIUtility.GetStateObject(Il2CppType.Of<SliderState>(), this.id).TryCast<SliderState>();
|
||||
}
|
||||
|
||||
private Rect ThumbRect()
|
||||
{
|
||||
return (!this.horiz) ? this.VerticalThumbRect() : this.HorizontalThumbRect();
|
||||
}
|
||||
|
||||
private Rect VerticalThumbRect()
|
||||
{
|
||||
float num = this.ValuesPerPixel();
|
||||
Rect result;
|
||||
if (this.start < this.end)
|
||||
{
|
||||
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * num + this.ThumbSize());
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() + this.size - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * -num + this.ThumbSize());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Rect HorizontalThumbRect()
|
||||
{
|
||||
float num = this.ValuesPerPixel();
|
||||
Rect result;
|
||||
if (this.start < this.end)
|
||||
{
|
||||
result = new Rect((this.ClampedCurrentValue() - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y + (float)this.slider.padding.top, this.size * num + this.ThumbSize(), this.position.height - (float)this.slider.padding.vertical);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Rect((this.ClampedCurrentValue() + this.size - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y, this.size * -num + this.ThumbSize(), this.position.height);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float ClampedCurrentValue()
|
||||
{
|
||||
return this.Clamp(this.currentValue);
|
||||
}
|
||||
|
||||
private float MousePosition()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = this.CurrentEvent().mousePosition.x - this.position.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = this.CurrentEvent().mousePosition.y - this.position.y;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float ValuesPerPixel()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = (this.position.width - (float)this.slider.padding.horizontal - this.ThumbSize()) / (this.end - this.start);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = (this.position.height - (float)this.slider.padding.vertical - this.ThumbSize()) / (this.end - this.start);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float ThumbSize()
|
||||
{
|
||||
float result;
|
||||
if (this.horiz)
|
||||
{
|
||||
result = ((this.thumb.fixedWidth == 0f) ? ((float)this.thumb.padding.horizontal) : this.thumb.fixedWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ((this.thumb.fixedHeight == 0f) ? ((float)this.thumb.padding.vertical) : this.thumb.fixedHeight);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private float MaxValue()
|
||||
{
|
||||
return Mathf.Max(this.start, this.end) - this.size;
|
||||
}
|
||||
|
||||
private float MinValue()
|
||||
{
|
||||
return Mathf.Min(this.start, this.end);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
27
src/UnstripFixes/UnstripExtensions.cs
Normal file
27
src/UnstripFixes/UnstripExtensions.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public static class UnstripExtensions
|
||||
{
|
||||
public static Rect GetLastUnstripped(this GUILayoutGroup group)
|
||||
{
|
||||
Rect result;
|
||||
if (group.m_Cursor > 0 && group.m_Cursor <= group.entries.Count)
|
||||
{
|
||||
GUILayoutEntry guilayoutEntry = group.entries[group.m_Cursor - 1];
|
||||
result = guilayoutEntry.rect;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = GUILayoutEntry.kDummyRect;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user