mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-22 08:32:51 +08:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
de663f34b2 | |||
8d648fec43 | |||
835a81765e | |||
51ed936e30 | |||
ac16587cdc | |||
1e1eaa6c38 | |||
af17ae82c6 | |||
e23341c2b1 | |||
080bde4a63 | |||
1d739a1936 | |||
a927b5ed21 | |||
642c97812c | |||
e43d3579de | |||
6ea435deee | |||
94f749342d | |||
0769b7ef23 | |||
5086dcc82b | |||
56abd38e92 | |||
a7e6ae87df | |||
b5c584bb02 | |||
c8a3aecdf4 | |||
33c2378f41 | |||
38aafa7e5b |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,6 +9,7 @@
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*/mcs-unity*
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
56
README.md
56
README.md
@ -16,16 +16,9 @@
|
||||
|
||||
### Known issues
|
||||
* CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
|
||||
* Reflection may fail with certain types (eg. `Nullable<T>`, some Dictionary types, etc). Please see [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower#known-issues) for more details.
|
||||
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
|
||||
|
||||
## Features
|
||||
* Scene hierarchy explorer
|
||||
* Search loaded assets with filters
|
||||
* Traverse and manipulate GameObjects
|
||||
* Generic Reflection inspector
|
||||
* C# REPL Console
|
||||
* Inspect-under-mouse
|
||||
|
||||
## How to install
|
||||
|
||||
Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
|
||||
@ -37,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/BzTOCvp.png)
|
||||
|
||||
### Inspectors
|
||||
|
||||
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
|
||||
@ -59,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/DiDvl0Q.png)
|
||||
|
||||
### Reflection Inspector
|
||||
|
||||
* The Reflection Inspector is used for all other supported objects.
|
||||
* Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods.
|
||||
* Can search and filter members for the ones you are interested in.
|
||||
|
||||
[](https://i.imgur.com/Pq127XD.png)
|
||||
|
||||
### 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.
|
||||
@ -97,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 |
@ -19,8 +19,12 @@ namespace Explorer
|
||||
public MemberInfo MemInfo { get; set; }
|
||||
public Type DeclaringType { get; set; }
|
||||
public object DeclaringInstance { get; set; }
|
||||
public int PropertyIndex { get; private set; }
|
||||
private string m_propertyIndexInput = "0";
|
||||
|
||||
public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
|
||||
public bool m_evaluated = false;
|
||||
public bool m_isEvaluating;
|
||||
public ParameterInfo[] m_arguments = new ParameterInfo[0];
|
||||
public string[] m_argumentInput = new string[0];
|
||||
|
||||
public string ReflectionException { get; set; }
|
||||
|
||||
@ -40,14 +44,10 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Abstract/Virtual Methods ===== //
|
||||
|
||||
public virtual void Init() { }
|
||||
|
||||
public abstract void DrawValue(Rect window, float width);
|
||||
|
||||
// ===== Static Methods ===== //
|
||||
|
||||
/// <summary>
|
||||
/// Get CacheObject from only an object instance
|
||||
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
|
||||
@ -114,12 +114,21 @@ namespace Explorer
|
||||
{
|
||||
CacheObjectBase holder;
|
||||
|
||||
var pi = memberInfo as PropertyInfo;
|
||||
var mi = memberInfo as MethodInfo;
|
||||
|
||||
// if PropertyInfo, check if can process args
|
||||
if (pi != null && !CanProcessArgs(pi.GetIndexParameters()))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is pretty ugly, could probably make a cleaner implementation.
|
||||
// However, the only cleaner ways I can think of are slower and probably not worth it.
|
||||
|
||||
// Note: the order is somewhat important.
|
||||
|
||||
if (memberInfo is MethodInfo mi)
|
||||
if (mi != null)
|
||||
{
|
||||
if (CacheMethod.CanEvaluate(mi))
|
||||
{
|
||||
@ -163,7 +172,7 @@ namespace Explorer
|
||||
{
|
||||
holder = new CacheDictionary();
|
||||
}
|
||||
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType))
|
||||
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppEnumerable(valueType))
|
||||
{
|
||||
holder = new CacheList();
|
||||
}
|
||||
@ -181,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)
|
||||
@ -209,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
|
||||
{
|
||||
@ -224,6 +320,8 @@ namespace Explorer
|
||||
}
|
||||
|
||||
ReflectionException = null;
|
||||
m_evaluated = true;
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -244,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
|
||||
{
|
||||
@ -261,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)
|
||||
{
|
||||
@ -282,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);
|
||||
GUIUnstrip.Space(labelWidth);
|
||||
}
|
||||
|
||||
var cm = this as CacheMethod;
|
||||
|
||||
if (HasParameters)
|
||||
{
|
||||
GUILayout.BeginVertical(null);
|
||||
|
||||
if (m_isEvaluating)
|
||||
{
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var name = m_arguments[i].Name;
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType.Name;
|
||||
|
||||
var label = "<color=#2df7b2>" + type + "</color> <color=#a6e9e9>" + name + "</color>";
|
||||
if (m_arguments[i].HasDefaultValue)
|
||||
{
|
||||
label = $"<i>[{label} = {m_arguments[i].DefaultValue}]</i>";
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(20) });
|
||||
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
GUILayout.Label(label, null);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (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);
|
||||
GUIUnstrip.Space(labelWidth);
|
||||
}
|
||||
else if (cm != null)
|
||||
{
|
||||
//GUILayout.BeginHorizontal(null);
|
||||
|
||||
if (GUILayout.Button(EVALUATE_LABEL, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
cm.Evaluate();
|
||||
}
|
||||
|
||||
// new line and space
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUIUnstrip.Space(labelWidth);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ReflectionException))
|
||||
{
|
||||
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
|
||||
}
|
||||
else if (Value == null && MemInfo?.MemberType != MemberTypes.Method)
|
||||
else if ((HasParameters || this is CacheMethod) && !m_evaluated)
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=#2df7b2>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
else if (Value == null && !(this is CacheMethod))
|
||||
{
|
||||
GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MemInfo is PropertyInfo pi && pi.GetIndexParameters().Length > 0)
|
||||
{
|
||||
GUILayout.Label("index:", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
|
||||
m_propertyIndexInput = GUILayout.TextField(m_propertyIndexInput, new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
if (GUILayout.Button("Set", new GUILayoutOption[] { GUILayout.Width(60) }))
|
||||
{
|
||||
if (int.TryParse(m_propertyIndexInput, out int i))
|
||||
{
|
||||
PropertyIndex = i;
|
||||
UpdateValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log($"Could not parse '{m_propertyIndexInput}' to an int!");
|
||||
}
|
||||
}
|
||||
|
||||
// new line and space
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(labelWidth);
|
||||
}
|
||||
|
||||
DrawValue(window, window.width - labelWidth - 90);
|
||||
}
|
||||
}
|
||||
@ -348,15 +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 += ")";
|
||||
|
@ -9,8 +9,6 @@ namespace Explorer
|
||||
interface IExpandHeight
|
||||
{
|
||||
bool IsExpanded { get; set; }
|
||||
|
||||
float WhiteSpace { get; set; }
|
||||
float ButtonWidthOffset { get; set; }
|
||||
float WhiteSpace { get; set; }
|
||||
}
|
||||
}
|
@ -15,7 +15,6 @@ namespace Explorer
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
public float ButtonWidthOffset { get; set; } = 290f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
@ -126,6 +125,10 @@ namespace Explorer
|
||||
m_keysType = type.GetGenericArguments()[0];
|
||||
m_valuesType = type.GetGenericArguments()[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
MelonLogger.Log("TODO? Dictionary is of type: " + Value.GetType().FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +155,6 @@ namespace Explorer
|
||||
foreach (var key in IDict.Keys)
|
||||
{
|
||||
var cache = GetCacheObject(key, TypeOfKeys);
|
||||
cache.UpdateValue();
|
||||
keys.Add(cache);
|
||||
}
|
||||
|
||||
@ -160,7 +162,6 @@ namespace Explorer
|
||||
foreach (var val in IDict.Values)
|
||||
{
|
||||
var cache = GetCacheObject(val, TypeOfValues);
|
||||
cache.UpdateValue();
|
||||
values.Add(cache);
|
||||
}
|
||||
|
||||
@ -170,6 +171,11 @@ namespace Explorer
|
||||
|
||||
private bool EnsureDictionaryIsSupported()
|
||||
{
|
||||
if (typeof(IDictionary).IsAssignableFrom(ValueType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return Check(TypeOfKeys) && Check(TypeOfValues);
|
||||
@ -200,6 +206,8 @@ namespace Explorer
|
||||
return;
|
||||
}
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
int count = m_cachedKeys.Length;
|
||||
|
||||
if (!IsExpanded)
|
||||
@ -217,24 +225,20 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
var negativeWhitespace = window.width - (whitespace + 100f);
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = $"<color=yellow>[{count}] Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
|
||||
string btnLabel = $"[{count}] <color=#2df7b2>Dictionary<{TypeOfKeys.FullName}, {TypeOfValues.FullName}></color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.Width(negativeWhitespace) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUIUnstrip.Space(5);
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
float whitespace = WhiteSpace;
|
||||
if (whitespace > 0)
|
||||
{
|
||||
ClampLabelWidth(window, ref whitespace);
|
||||
}
|
||||
|
||||
Pages.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
@ -242,7 +246,7 @@ namespace Explorer
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
@ -258,7 +262,7 @@ namespace Explorer
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUIUnstrip.Space(5);
|
||||
}
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
@ -272,7 +276,7 @@ namespace Explorer
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
//GUILayout.Space(whitespace);
|
||||
//GUIUnstrip.Space(whitespace);
|
||||
|
||||
if (key == null || val == null)
|
||||
{
|
||||
|
@ -12,7 +12,6 @@ namespace Explorer
|
||||
{
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
public float ButtonWidthOffset { get; set; } = 290f;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
@ -41,7 +40,7 @@ namespace Explorer
|
||||
private Type m_genericTypeDef;
|
||||
|
||||
// Cached ToArray method for Lists
|
||||
public MethodInfo GenericToArrayMethod
|
||||
public MethodInfo CppListToArrayMethod
|
||||
{
|
||||
get => GetGenericToArrayMethod();
|
||||
}
|
||||
@ -61,7 +60,7 @@ namespace Explorer
|
||||
{
|
||||
if (m_enumerable == null && Value != null)
|
||||
{
|
||||
m_enumerable = Value as IEnumerable ?? GetEnumerableFromIl2CppList();
|
||||
m_enumerable = Value as IEnumerable ?? EnumerateWithReflection();
|
||||
}
|
||||
return m_enumerable;
|
||||
}
|
||||
@ -101,21 +100,45 @@ namespace Explorer
|
||||
return m_itemProperty;
|
||||
}
|
||||
|
||||
private IEnumerable GetEnumerableFromIl2CppList()
|
||||
private IEnumerable EnumerateWithReflection()
|
||||
{
|
||||
if (Value == null) return null;
|
||||
|
||||
if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.List<>))
|
||||
{
|
||||
return (IEnumerable)GenericToArrayMethod?.Invoke(Value, new object[0]);
|
||||
return (IEnumerable)CppListToArrayMethod?.Invoke(Value, new object[0]);
|
||||
}
|
||||
else if (GenericTypeDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
|
||||
{
|
||||
return CppHashSetToMono();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ConvertIListToMono();
|
||||
return CppIListToMono();
|
||||
}
|
||||
}
|
||||
|
||||
private IList ConvertIListToMono()
|
||||
private IEnumerable CppHashSetToMono()
|
||||
{
|
||||
var set = new HashSet<object>();
|
||||
|
||||
// invoke GetEnumerator
|
||||
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
|
||||
// get the type of it
|
||||
var enumeratorType = enumerator.GetType();
|
||||
// reflect MoveNext and Current
|
||||
var moveNext = enumeratorType.GetMethod("MoveNext");
|
||||
var current = enumeratorType.GetProperty("Current");
|
||||
// iterate
|
||||
while ((bool)moveNext.Invoke(enumerator, null))
|
||||
{
|
||||
set.Add(current.GetValue(enumerator));
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private IList CppIListToMono()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -219,7 +242,6 @@ namespace Explorer
|
||||
|
||||
if (GetCacheObject(obj, t) is CacheObjectBase cached)
|
||||
{
|
||||
cached.UpdateValue();
|
||||
list.Add(cached);
|
||||
}
|
||||
else
|
||||
@ -246,6 +268,8 @@ namespace Explorer
|
||||
return;
|
||||
}
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
int count = m_cachedEntries.Length;
|
||||
|
||||
if (!IsExpanded)
|
||||
@ -263,24 +287,20 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
var negativeWhitespace = window.width - (whitespace + 100f);
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
string btnLabel = "<color=yellow>[" + count + "] " + EntryType.FullName + "</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
|
||||
string btnLabel = $"[{count}] <color=#2df7b2>{EntryType.FullName}</color>";
|
||||
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(negativeWhitespace) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUIUnstrip.Space(5);
|
||||
|
||||
if (IsExpanded)
|
||||
{
|
||||
float whitespace = WhiteSpace;
|
||||
if (whitespace > 0)
|
||||
{
|
||||
ClampLabelWidth(window, ref whitespace);
|
||||
}
|
||||
|
||||
Pages.ItemCount = count;
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
@ -288,7 +308,7 @@ namespace Explorer
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
@ -304,7 +324,7 @@ namespace Explorer
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUIUnstrip.Space(5);
|
||||
}
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
@ -317,7 +337,7 @@ namespace Explorer
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal(null);
|
||||
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
|
||||
if (entry == null || entry.Value == null)
|
||||
{
|
||||
|
@ -11,15 +11,8 @@ namespace Explorer
|
||||
{
|
||||
public class CacheMethod : CacheObjectBase
|
||||
{
|
||||
private bool m_evaluated = false;
|
||||
private CacheObjectBase m_cachedReturnValue;
|
||||
|
||||
private bool m_isEvaluating;
|
||||
private ParameterInfo[] m_arguments;
|
||||
private string[] m_argumentInput;
|
||||
|
||||
public bool HasParameters => m_arguments != null && m_arguments.Length > 0;
|
||||
|
||||
public static bool CanEvaluate(MethodInfo mi)
|
||||
{
|
||||
// TODO generic args
|
||||
@ -29,25 +22,7 @@ namespace Explorer
|
||||
}
|
||||
|
||||
// primitive and string args supported
|
||||
foreach (var param in mi.GetParameters())
|
||||
{
|
||||
if (!param.ParameterType.IsPrimitive && param.ParameterType != typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
var mi = MemInfo as MethodInfo;
|
||||
|
||||
m_arguments = mi.GetParameters();
|
||||
m_argumentInput = new string[m_arguments.Length];
|
||||
return CanProcessArgs(mi.GetParameters());
|
||||
}
|
||||
|
||||
public override void UpdateValue()
|
||||
@ -55,10 +30,11 @@ namespace Explorer
|
||||
//base.UpdateValue();
|
||||
}
|
||||
|
||||
private void Evaluate()
|
||||
public void Evaluate()
|
||||
{
|
||||
var mi = MemInfo as MethodInfo;
|
||||
m_isEvaluating = false;
|
||||
|
||||
var mi = MemInfo as MethodInfo;
|
||||
object ret = null;
|
||||
|
||||
if (!HasParameters)
|
||||
@ -68,42 +44,9 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
var parsedArgs = new List<object>();
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType;
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
parsedArgs.Add(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type.GetMethod("Parse", new Type[] { typeof(string) }).Invoke(null, new object[] { input }) is object parsed)
|
||||
{
|
||||
parsedArgs.Add(parsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
// try add a null arg i guess
|
||||
parsedArgs.Add(null);
|
||||
}
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log($"Unable to parse '{input}' to type '{type.Name}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, parsedArgs.ToArray());
|
||||
ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
|
||||
m_evaluated = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@ -115,13 +58,6 @@ namespace Explorer
|
||||
if (ret != null)
|
||||
{
|
||||
m_cachedReturnValue = GetCacheObject(ret);
|
||||
|
||||
if (m_cachedReturnValue is IExpandHeight expander)
|
||||
{
|
||||
expander.WhiteSpace = 0f;
|
||||
expander.ButtonWidthOffset += 70f;
|
||||
}
|
||||
|
||||
m_cachedReturnValue.UpdateValue();
|
||||
}
|
||||
else
|
||||
@ -134,58 +70,6 @@ namespace Explorer
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.BeginVertical(null);
|
||||
|
||||
string evaluateLabel = "<color=lime>Evaluate</color>";
|
||||
if (HasParameters)
|
||||
{
|
||||
if (m_isEvaluating)
|
||||
{
|
||||
for (int i = 0; i < m_arguments.Length; i++)
|
||||
{
|
||||
var name = m_arguments[i].Name;
|
||||
var input = m_argumentInput[i];
|
||||
var type = m_arguments[i].ParameterType.Name;
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
GUILayout.Label(i + ": <color=cyan>" + name + "</color> <color=yellow>(" + type + ")</color>", null);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
Evaluate();
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
if (GUILayout.Button("Cancel", new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
m_isEvaluating = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button($"Evaluate ({m_arguments.Length} params)", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
m_isEvaluating = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (GUILayout.Button(evaluateLabel, new GUILayoutOption[] { GUILayout.Width(70) }))
|
||||
{
|
||||
Evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
if (m_evaluated)
|
||||
{
|
||||
if (m_cachedReturnValue != null)
|
||||
@ -194,16 +78,13 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
|
||||
GUILayout.Label($"null (<color=#2df7b2>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueTypeName}</color>)", null);
|
||||
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=#2df7b2>{ValueTypeName}</color>)", null);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,15 +42,20 @@ namespace Explorer
|
||||
|
||||
if (!label.Contains(ValueTypeName))
|
||||
{
|
||||
label += $" ({ValueTypeName})";
|
||||
label += $" (<color=#2df7b2>{ValueTypeName}</color>)";
|
||||
}
|
||||
else
|
||||
{
|
||||
label = label.Replace(ValueTypeName, $"<color=#2df7b2>{ValueTypeName}</color>");
|
||||
}
|
||||
|
||||
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
|
||||
{
|
||||
label = unityObj.name + " | " + label;
|
||||
}
|
||||
|
||||
GUI.skin.button.alignment = TextAnchor.MiddleLeft;
|
||||
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width - 15) }))
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width - 15) }))
|
||||
{
|
||||
WindowManager.InspectObject(Value, out bool _);
|
||||
}
|
||||
|
@ -7,13 +7,16 @@ using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheColor : CacheObjectBase
|
||||
public class CacheColor : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
private string r = "0";
|
||||
private string g = "0";
|
||||
private string b = "0";
|
||||
private string a = "0";
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
@ -28,40 +31,62 @@ namespace Explorer
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Color</color>: {((Color)Value).ToString()}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//var c = (Color)Value;
|
||||
//GUI.color = c;
|
||||
GUILayout.Label($"<color=#2df7b2>Color:</color> {((Color)Value).ToString()}", null);
|
||||
//GUI.color = Color.white;
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
r = GUILayout.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
g = GUILayout.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
b = GUILayout.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
a = GUILayout.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
|
@ -51,7 +51,7 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label(Value.ToString() + "<color=yellow><i> (" + ValueType + ")</i></color>", null);
|
||||
GUILayout.Label(Value.ToString() + "<color=#2df7b2><i> (" + ValueType + ")</i></color>", null);
|
||||
}
|
||||
|
||||
public void SetEnum(ref object value, int change)
|
||||
|
@ -69,10 +69,10 @@ namespace Explorer
|
||||
else
|
||||
{
|
||||
// using ValueType.Name instead of ValueTypeName, because we only want the short name.
|
||||
GUILayout.Label("<color=yellow><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
|
||||
|
||||
int dynSize = 25 + (m_valueToString.Length * 15);
|
||||
var maxwidth = window.width - 300f;
|
||||
var maxwidth = window.width - 310f;
|
||||
if (CanWrite) maxwidth -= 60;
|
||||
|
||||
if (dynSize > maxwidth)
|
||||
@ -92,7 +92,7 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUIUnstrip.Space(10);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,12 +7,15 @@ using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheQuaternion : CacheObjectBase
|
||||
public class CacheQuaternion : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string z = "0";
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
@ -26,34 +29,53 @@ namespace Explorer
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"<color=#2df7b2>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
|
@ -7,13 +7,16 @@ using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheRect : CacheObjectBase
|
||||
public class CacheRect : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
private string x = "0";
|
||||
private string y = "0";
|
||||
private string w = "0";
|
||||
private string h = "0";
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void UpdateValue()
|
||||
{
|
||||
base.UpdateValue();
|
||||
@ -28,40 +31,59 @@ namespace Explorer
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"<color=#2df7b2>Rect</color>: {((Rect)Value).ToString()}", null);
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
h = GUILayout.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
|
@ -8,7 +8,7 @@ using UnityEngine;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CacheVector : CacheObjectBase
|
||||
public class CacheVector : CacheObjectBase, IExpandHeight
|
||||
{
|
||||
public int VectorSize = 2;
|
||||
|
||||
@ -19,6 +19,9 @@ namespace Explorer
|
||||
|
||||
private MethodInfo m_toStringMethod;
|
||||
|
||||
public bool IsExpanded { get; set; }
|
||||
public float WhiteSpace { get; set; } = 215f;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
if (Value is Vector2)
|
||||
@ -63,22 +66,41 @@ namespace Explorer
|
||||
|
||||
public override void DrawValue(Rect window, float width)
|
||||
{
|
||||
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
|
||||
|
||||
if (CanWrite)
|
||||
{
|
||||
if (!IsExpanded)
|
||||
{
|
||||
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
|
||||
{
|
||||
IsExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"<color=#2df7b2>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
|
||||
|
||||
if (CanWrite && IsExpanded)
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
var whitespace = window.width - width - 90;
|
||||
|
||||
var whitespace = CalcWhitespace(window);
|
||||
|
||||
// always draw x and y
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
@ -87,7 +109,7 @@ namespace Explorer
|
||||
{
|
||||
// draw z
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
@ -96,7 +118,7 @@ namespace Explorer
|
||||
{
|
||||
// draw w
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
|
||||
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
GUILayout.EndHorizontal();
|
||||
@ -104,7 +126,7 @@ namespace Explorer
|
||||
|
||||
// draw set value button
|
||||
GUILayout.BeginHorizontal(null);
|
||||
GUILayout.Space(whitespace);
|
||||
GUIUnstrip.Space(whitespace);
|
||||
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
|
||||
{
|
||||
SetValueFromInput();
|
||||
|
69
src/Config/ModConfig.cs
Normal file
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,7 +13,7 @@ namespace Explorer
|
||||
public class CppExplorer : MelonMod
|
||||
{
|
||||
public const string NAME = "CppExplorer";
|
||||
public const string VERSION = "1.6.3";
|
||||
public const string VERSION = "1.7.0";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.cppexplorer";
|
||||
|
||||
@ -24,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.");
|
||||
}
|
||||
@ -74,22 +61,18 @@ namespace Explorer
|
||||
public override void OnUpdate()
|
||||
{
|
||||
// Check main toggle key input
|
||||
if (InputHelper.GetKeyDown(KeyCode.F7))
|
||||
if (InputHelper.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
|
||||
{
|
||||
ShowMenu = !ShowMenu;
|
||||
}
|
||||
|
||||
if (ShowMenu)
|
||||
{
|
||||
// Check Force-Unlock input
|
||||
if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
|
||||
{
|
||||
ForceUnlockMouse = !ForceUnlockMouse;
|
||||
}
|
||||
CursorControl.Update();
|
||||
InspectUnderMouse.Update();
|
||||
|
||||
MainMenu.Instance.Update();
|
||||
WindowManager.Instance.Update();
|
||||
InspectUnderMouse.Update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
@ -11,7 +11,7 @@
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
<TargetFrameworkProfile />
|
||||
<TargetFrameworkProfile />
|
||||
<AssemblyName>CppExplorer</AssemblyName>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<DebugType>none</DebugType>
|
||||
@ -22,12 +22,17 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
</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>True</Private>
|
||||
@ -46,7 +51,6 @@
|
||||
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnhollowerBaseLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<!-- Replace these references with ones from your game (..\MelonLoader\ folder) -->
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\Managed\UnityEngine.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
@ -73,7 +77,7 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Helpers\IExpandHeight.cs" />
|
||||
<Compile Include="CachedObjects\IExpandHeight.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheColor.cs" />
|
||||
<Compile Include="CachedObjects\Object\CacheDictionary.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheEnum.cs" />
|
||||
@ -85,34 +89,38 @@
|
||||
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
|
||||
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
|
||||
<Compile Include="Config\ModConfig.cs" />
|
||||
<Compile Include="CppExplorer.cs" />
|
||||
<Compile Include="Extensions\ReflectionExtensions.cs" />
|
||||
<Compile Include="Helpers\InputHelper.cs" />
|
||||
<Compile Include="Menu\CursorControl.cs" />
|
||||
<Compile Include="Tests\TestClass.cs" />
|
||||
<Compile Include="UnstripFixes\GUIUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\LayoutUtilityUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\ScrollViewStateUnstrip.cs" />
|
||||
<Compile Include="Extensions\UnityExtensions.cs" />
|
||||
<Compile Include="Helpers\PageHelper.cs" />
|
||||
<Compile Include="Helpers\ReflectionHelpers.cs" />
|
||||
<Compile Include="Helpers\UIHelpers.cs" />
|
||||
<Compile Include="Helpers\UnityHelpers.cs" />
|
||||
<Compile Include="MainMenu\InspectUnderMouse.cs" />
|
||||
<Compile Include="Menu\InspectUnderMouse.cs" />
|
||||
<Compile Include="CachedObjects\CacheObjectBase.cs" />
|
||||
<Compile Include="UnstripFixes\SliderHandlerUnstrip.cs" />
|
||||
<Compile Include="UnstripFixes\UnstripExtensions.cs" />
|
||||
<Compile Include="Windows\ResizeDrag.cs" />
|
||||
<Compile Include="Windows\TabViewWindow.cs" />
|
||||
<Compile Include="Windows\UIWindow.cs" />
|
||||
<Compile Include="MainMenu\Pages\ConsolePage.cs" />
|
||||
<Compile Include="MainMenu\Pages\Console\REPL.cs" />
|
||||
<Compile Include="MainMenu\Pages\Console\REPLHelper.cs" />
|
||||
<Compile Include="MainMenu\Pages\WindowPage.cs" />
|
||||
<Compile Include="Windows\WindowManager.cs" />
|
||||
<Compile Include="MainMenu\MainMenu.cs" />
|
||||
<Compile Include="Windows\GameObjectWindow.cs" />
|
||||
<Compile Include="Windows\ReflectionWindow.cs" />
|
||||
<Compile Include="MainMenu\Pages\ScenePage.cs" />
|
||||
<Compile Include="MainMenu\Pages\SearchPage.cs" />
|
||||
<Compile Include="Helpers\UIStyles.cs" />
|
||||
<Compile Include="Menu\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" />
|
||||
|
@ -10,8 +10,8 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release|Any CPU.Build.0 = Release|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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +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
|
||||
/// Version-agnostic UnityEngine Input module using Reflection.
|
||||
/// </summary>
|
||||
public class InputHelper
|
||||
public static class InputHelper
|
||||
{
|
||||
private static readonly Type input = ReflectionHelpers.GetTypeByName("UnityEngine.Input");
|
||||
// If Input module failed to load at all
|
||||
public static bool NO_INPUT;
|
||||
|
||||
private static readonly PropertyInfo mousePositionInfo = input.GetProperty("mousePosition");
|
||||
// Base UnityEngine.Input class
|
||||
private static Type Input => _input ?? (_input = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
|
||||
private static Type _input;
|
||||
|
||||
private static readonly MethodInfo getKey = input.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
|
||||
private static readonly MethodInfo getKeyDown = input.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
|
||||
private static readonly MethodInfo getMouseButtonDown = input.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
|
||||
private static readonly MethodInfo getMouseButton = input.GetMethod("GetMouseButton", new Type[] { typeof(int) });
|
||||
// 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 => (Vector3)mousePositionInfo.GetValue(null);
|
||||
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)
|
||||
{
|
||||
return (bool)getKeyDown.Invoke(null, new object[] { key });
|
||||
if (NO_INPUT) return false;
|
||||
return (bool)_getKeyDown.Invoke(null, new object[] { key });
|
||||
}
|
||||
|
||||
public static bool GetKey(KeyCode key)
|
||||
{
|
||||
return (bool)getKey.Invoke(null, new object[] { 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)
|
||||
{
|
||||
return (bool)getMouseButtonDown.Invoke(null, new object[] { 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)
|
||||
{
|
||||
return (bool)getMouseButton.Invoke(null, new object[] { 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +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 System.Collections;
|
||||
using Mono.CSharp;
|
||||
using ILType = Il2CppSystem.Type;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
@ -18,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");
|
||||
|
||||
@ -35,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))
|
||||
@ -75,100 +181,5 @@ namespace Explorer
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsEnumerable(Type t)
|
||||
{
|
||||
return typeof(IEnumerable).IsAssignableFrom(t);
|
||||
}
|
||||
|
||||
// Only Il2Cpp List needs this check. C# List is IEnumerable.
|
||||
public static bool IsCppList(Type t)
|
||||
{
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsDictionary(Type t)
|
||||
{
|
||||
if (typeof(IDictionary).IsAssignableFrom(t))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|
||||
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
|
||||
}
|
||||
else
|
||||
{
|
||||
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t);
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetTypeByName(string fullName)
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (var type in GetTypesSafe(asm))
|
||||
{
|
||||
if (type.FullName == fullName)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Type GetActualType(object obj)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
|
||||
|
||||
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
|
||||
{
|
||||
return t;
|
||||
}
|
||||
|
||||
return ilObject.GetType();
|
||||
}
|
||||
|
||||
return obj.GetType();
|
||||
}
|
||||
|
||||
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
|
||||
{
|
||||
try { return asm.GetTypes(); }
|
||||
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
|
||||
catch { return Enumerable.Empty<Type>(); }
|
||||
}
|
||||
|
||||
public static Type[] GetAllBaseTypes(object obj)
|
||||
{
|
||||
var list = new List<Type>();
|
||||
|
||||
var type = GetActualType(obj);
|
||||
list.Add(type);
|
||||
|
||||
while (type.BaseType != null)
|
||||
{
|
||||
type = type.BaseType;
|
||||
list.Add(type);
|
||||
}
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
183
src/Menu/CursorControl.cs
Normal file
183
src/Menu/CursorControl.cs
Normal file
@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Harmony;
|
||||
using MelonLoader;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Explorer
|
||||
{
|
||||
public class CursorControl
|
||||
{
|
||||
public static bool ForceUnlockMouse
|
||||
{
|
||||
get => m_forceUnlock;
|
||||
set => SetForceUnlock(value);
|
||||
}
|
||||
private static bool m_forceUnlock;
|
||||
private static CursorLockMode m_lastLockMode;
|
||||
private static bool m_lastVisibleState;
|
||||
private static bool m_currentlySettingCursor = false;
|
||||
|
||||
public static bool ShouldForceMouse => CppExplorer.ShowMenu && ForceUnlockMouse;
|
||||
|
||||
private static Type CursorType => m_cursorType ?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor"));
|
||||
private static Type m_cursorType;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if Cursor class is loaded
|
||||
if (CursorType == null)
|
||||
{
|
||||
MelonLogger.Log("Trying to manually load Cursor module...");
|
||||
|
||||
if (ReflectionHelpers.LoadModule("UnityEngine.CoreModule") && CursorType != null)
|
||||
{
|
||||
MelonLogger.Log("Ok!");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Could not load UnityEngine.Cursor module!");
|
||||
}
|
||||
}
|
||||
|
||||
// Get current cursor state and enable cursor
|
||||
m_lastLockMode = Cursor.lockState;
|
||||
m_lastVisibleState = Cursor.visible;
|
||||
|
||||
// Setup Harmony Patches
|
||||
TryPatch("lockState", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Prefix_set_lockState))), true);
|
||||
TryPatch("lockState", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Postfix_get_lockState))), false);
|
||||
|
||||
TryPatch("visible", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Prefix_set_visible))), true);
|
||||
TryPatch("visible", new HarmonyMethod(typeof(CursorControl).GetMethod(nameof(Postfix_get_visible))), false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Exception on CursorControl.Init! {e.GetType()}, {e.Message}");
|
||||
}
|
||||
|
||||
// Enable ShowMenu and ForceUnlockMouse
|
||||
// (set m_showMenu directly to not call UpdateCursorState twice)
|
||||
CppExplorer.m_showMenu = true;
|
||||
ForceUnlockMouse = true;
|
||||
}
|
||||
|
||||
private static void TryPatch(string property, HarmonyMethod patch, bool setter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var harmony = CppExplorer.Instance.harmonyInstance;
|
||||
var prop = typeof(Cursor).GetProperty(property);
|
||||
|
||||
if (setter)
|
||||
{
|
||||
// setter is prefix
|
||||
harmony.Patch(prop.GetSetMethod(), patch);
|
||||
}
|
||||
else
|
||||
{
|
||||
// getter is postfix
|
||||
harmony.Patch(prop.GetGetMethod(), null, patch);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"[NON-FATAL] Couldn't patch a method: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetForceUnlock(bool unlock)
|
||||
{
|
||||
m_forceUnlock = unlock;
|
||||
UpdateCursorControl();
|
||||
}
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
// Check Force-Unlock input
|
||||
if (InputHelper.GetKeyDown(KeyCode.LeftAlt))
|
||||
{
|
||||
ForceUnlockMouse = !ForceUnlockMouse;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateCursorControl()
|
||||
{
|
||||
try
|
||||
{
|
||||
m_currentlySettingCursor = true;
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Cursor.lockState = m_lastLockMode;
|
||||
Cursor.visible = m_lastVisibleState;
|
||||
}
|
||||
m_currentlySettingCursor = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
MelonLogger.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
|
||||
// Also keep track of when anything else tries to set Cursor state, this will be the
|
||||
// value that we set back to when we close the menu or disable force-unlock.
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
{
|
||||
m_lastLockMode = value;
|
||||
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
value = CursorLockMode.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
{
|
||||
m_lastVisibleState = value;
|
||||
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Postfix_get_lockState(ref CursorLockMode __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastLockMode;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
public static void Postfix_get_visible(ref bool __result)
|
||||
{
|
||||
if (ShouldForceMouse)
|
||||
{
|
||||
__result = m_lastVisibleState;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
@ -107,14 +103,16 @@ namespace Explorer
|
||||
GUI.color = Color.white;
|
||||
InspectUnderMouse.EnableInspect = GUILayout.Toggle(InspectUnderMouse.EnableInspect, "Inspect Under Mouse (Shift + RMB)", null);
|
||||
|
||||
bool mouseState = CppExplorer.ForceUnlockMouse;
|
||||
bool mouseState = CursorControl.ForceUnlockMouse;
|
||||
bool setMouse = GUILayout.Toggle(mouseState, "Force Unlock Mouse (Left Alt)", null);
|
||||
if (setMouse != mouseState) CppExplorer.ForceUnlockMouse = setMouse;
|
||||
if (setMouse != mouseState) CursorControl.ForceUnlockMouse = setMouse;
|
||||
|
||||
WindowManager.TabView = GUILayout.Toggle(WindowManager.TabView, "Tab View", null);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(10);
|
||||
//GUIUnstrip.Space(10);
|
||||
GUIUnstrip.Space(10);
|
||||
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
}
|
@ -192,7 +192,7 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
|
||||
// --------- GUI Draw Function --------- //
|
||||
// --------- GUI Draw Function --------- //
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
@ -215,11 +215,9 @@ namespace Explorer
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
MelonLogger.Log("Exception drawing ScenePage! " + e.GetType() + ", " + e.Message);
|
||||
MelonLogger.Log(e.StackTrace);
|
||||
m_currentTransform = null;
|
||||
// supress
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,39 +227,7 @@ namespace Explorer
|
||||
|
||||
// Current Scene label
|
||||
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
try
|
||||
{
|
||||
// Need to do 'ToList()' so the object isn't cleaned up by Il2Cpp GC.
|
||||
var scenes = SceneManager.GetAllScenes().ToList();
|
||||
|
||||
if (scenes.Count > 1)
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
int index = scenes.IndexOf(SceneManager.GetSceneByName(m_currentScene));
|
||||
index += changeWanted;
|
||||
if (index > scenes.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
SceneChangeButtons();
|
||||
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", null); //new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
@ -269,14 +235,49 @@ 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();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(5);
|
||||
GUIUnstrip.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()
|
@ -385,8 +385,8 @@ namespace Explorer
|
||||
public static IEnumerable<object> GetInstanceClassScanner()
|
||||
{
|
||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(ReflectionHelpers.GetTypesSafe)
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
||||
.SelectMany(t => t.TryGetTypes())
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||
var flatFlags = flags | BindingFlags.FlattenHierarchy;
|
||||
@ -428,7 +428,7 @@ namespace Explorer
|
||||
{
|
||||
var t = ReflectionHelpers.GetActualType(obj);
|
||||
|
||||
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t))
|
||||
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppEnumerable(t))
|
||||
{
|
||||
continue;
|
||||
}
|
@ -385,7 +385,7 @@ namespace Explorer
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Space(26);
|
||||
GUIUnstrip.Space(26);
|
||||
}
|
||||
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
|
||||
{
|
@ -28,20 +28,30 @@ namespace Explorer
|
||||
public MemberTypes m_filter = MemberTypes.Property;
|
||||
private bool m_hideFailedReflection = false;
|
||||
|
||||
// some extra caching
|
||||
// some extra cast-caching
|
||||
private UnityEngine.Object m_uObj;
|
||||
private Component m_component;
|
||||
|
||||
private static readonly HashSet<string> _typeAndMemberBlacklist = new HashSet<string>
|
||||
{
|
||||
// Causes a crash
|
||||
"Type.DeclaringMethod",
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> _methodStartsWithBlacklist = new HashSet<string>
|
||||
{
|
||||
// Pointless (handled by Properties)
|
||||
"get_",
|
||||
"set_"
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
var type = ReflectionHelpers.GetActualType(Target);
|
||||
TargetType = ReflectionHelpers.GetActualType(Target);
|
||||
|
||||
TargetType = type;
|
||||
|
||||
var types = ReflectionHelpers.GetAllBaseTypes(Target);
|
||||
|
||||
CacheMembers(types);
|
||||
CacheMembers(ReflectionHelpers.GetAllBaseTypes(Target));
|
||||
|
||||
// cache the extra cast-caching
|
||||
if (Target is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
var unityObj = ilObject.TryCast<UnityEngine.Object>();
|
||||
@ -56,13 +66,6 @@ namespace Explorer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_filter = MemberTypes.All;
|
||||
m_autoUpdate = true;
|
||||
Update();
|
||||
|
||||
m_autoUpdate = false;
|
||||
m_filter = MemberTypes.Property;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
@ -99,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);
|
||||
@ -132,6 +139,7 @@ namespace Explorer
|
||||
}
|
||||
|
||||
object target = Target;
|
||||
string exception = null;
|
||||
|
||||
if (target is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
@ -147,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +288,7 @@ namespace Explorer
|
||||
GUI.color = Color.white;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUIUnstrip.Space(10);
|
||||
|
||||
Pages.ItemCount = m_cachedMembersFiltered.Length;
|
||||
|
||||
@ -295,7 +317,7 @@ namespace Explorer
|
||||
|
||||
scroll = GUIUnstrip.BeginScrollView(scroll);
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUIUnstrip.Space(10);
|
||||
|
||||
UIStyles.HorizontalLine(Color.grey);
|
||||
|
@ -32,7 +32,7 @@ namespace Explorer
|
||||
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
|
||||
|
||||
//var r = GUILayoutUtility.GetLastRect();
|
||||
var r = GUIUnstrip.GetLastRect();
|
||||
var r = LayoutUtilityUnstrip.GetLastRect();
|
||||
|
||||
var mousePos = InputHelper.mousePosition;
|
||||
|
@ -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()
|
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -42,20 +42,20 @@ namespace Explorer
|
||||
}
|
||||
private static PropertyInfo m_scrollViewStatesInfo;
|
||||
|
||||
|
||||
public static Rect GetLastRect()
|
||||
public static void Space(float pixels)
|
||||
{
|
||||
EventType type = Event.current.type;
|
||||
Rect last;
|
||||
if (type != EventType.Layout && type != EventType.Used)
|
||||
{
|
||||
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
|
||||
}
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
if (GUILayoutUtility.current.topLevel.isVertical)
|
||||
|
||||
LayoutUtilityUnstrip.GetRect(0, pixels, GUILayoutUtility.spaceStyle, new GUILayoutOption[] { GUILayout.Height(pixels) });
|
||||
else
|
||||
LayoutUtilityUnstrip.GetRect(pixels, 0, GUILayoutUtility.spaceStyle, new GUILayoutOption[] { GUILayout.Width(pixels) });
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
last = GUILayoutUtility.kDummyRect;
|
||||
GUILayoutUtility.current.topLevel.entries[GUILayoutUtility.current.topLevel.entries.Count - 1].consideredForMargin = false;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
// Fix for BeginScrollView.
|
||||
|
112
src/UnstripFixes/LayoutUtilityUnstrip.cs
Normal file
112
src/UnstripFixes/LayoutUtilityUnstrip.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 LayoutUtilityUnstrip
|
||||
{
|
||||
public static Rect GetRect(float width, float height) { return DoGetRect(width, width, height, height, GUIStyle.none, null); }
|
||||
public static Rect GetRect(float width, float height, GUIStyle style) { return DoGetRect(width, width, height, height, style, null); }
|
||||
public static Rect GetRect(float width, float height, params GUILayoutOption[] options) { return DoGetRect(width, width, height, height, GUIStyle.none, options); }
|
||||
// Reserve layout space for a rectangle with a fixed content area.
|
||||
public static Rect GetRect(float width, float height, GUIStyle style, params GUILayoutOption[] options)
|
||||
{ return DoGetRect(width, width, height, height, style, options); }
|
||||
|
||||
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight)
|
||||
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, GUIStyle.none, null); }
|
||||
|
||||
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, GUIStyle style)
|
||||
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, style, null); }
|
||||
|
||||
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, params GUILayoutOption[] options)
|
||||
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, GUIStyle.none, options); }
|
||||
// Reserve layout space for a flexible rect.
|
||||
public static Rect GetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, GUIStyle style, params GUILayoutOption[] options)
|
||||
{ return DoGetRect(minWidth, maxWidth, minHeight, maxHeight, style, options); }
|
||||
static Rect DoGetRect(float minWidth, float maxWidth, float minHeight, float maxHeight, GUIStyle style, GUILayoutOption[] options)
|
||||
{
|
||||
switch (Event.current.type)
|
||||
{
|
||||
case EventType.Layout:
|
||||
GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(minWidth, maxWidth, minHeight, maxHeight, style, options));
|
||||
return GUILayoutUtility.kDummyRect;
|
||||
case EventType.Used:
|
||||
return GUILayoutUtility.kDummyRect;
|
||||
default:
|
||||
return GUILayoutUtility.current.topLevel.GetNext().rect;
|
||||
}
|
||||
}
|
||||
public static Rect GetRect(GUIContent content, GUIStyle style) { return DoGetRect(content, style, null); }
|
||||
// Reserve layout space for a rectangle for displaying some contents with a specific style.
|
||||
public static Rect GetRect(GUIContent content, GUIStyle style, params GUILayoutOption[] options) { return DoGetRect(content, style, options); }
|
||||
|
||||
static Rect DoGetRect(GUIContent content, GUIStyle style, GUILayoutOption[] options)
|
||||
{
|
||||
GUIUtility.CheckOnGUI();
|
||||
|
||||
switch (Event.current.type)
|
||||
{
|
||||
case EventType.Layout:
|
||||
if (style.isHeightDependantOnWidth)
|
||||
{
|
||||
GUILayoutUtility.current.topLevel.Add(new GUIWordWrapSizer(style, content, options));
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 sizeConstraints = new Vector2(0, 0);
|
||||
if (options != null)
|
||||
{
|
||||
foreach (var option in options)
|
||||
{
|
||||
if (float.TryParse(option.value.ToString(), out float f))
|
||||
{
|
||||
switch (option.type)
|
||||
{
|
||||
case GUILayoutOption.Type.maxHeight:
|
||||
sizeConstraints.y = f;
|
||||
break;
|
||||
case GUILayoutOption.Type.maxWidth:
|
||||
sizeConstraints.x = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 size = style.CalcSizeWithConstraints(content, sizeConstraints);
|
||||
// This is needed on non-integer scale ratios to avoid errors to accumulate in further layout calculations
|
||||
size.x = Mathf.Ceil(size.x);
|
||||
size.y = Mathf.Ceil(size.y);
|
||||
GUILayoutUtility.current.topLevel.Add(new GUILayoutEntry(size.x, size.x, size.y, size.y, style, options));
|
||||
}
|
||||
return GUILayoutUtility.kDummyRect;
|
||||
|
||||
case EventType.Used:
|
||||
return GUILayoutUtility.kDummyRect;
|
||||
default:
|
||||
var entry = GUILayoutUtility.current.topLevel.GetNext();
|
||||
//GUIDebugger.LogLayoutEntry(entry.rect, entry.marginLeft, entry.marginRight, entry.marginTop, entry.marginBottom, entry.style);
|
||||
return entry.rect;
|
||||
}
|
||||
}
|
||||
|
||||
public static Rect GetLastRect()
|
||||
{
|
||||
EventType type = Event.current.type;
|
||||
Rect last;
|
||||
if (type != EventType.Layout && type != EventType.Used)
|
||||
{
|
||||
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
|
||||
}
|
||||
else
|
||||
{
|
||||
last = GUILayoutUtility.kDummyRect;
|
||||
}
|
||||
return last;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user