Compare commits

..

13 Commits
1.5.5 ... 1.6.0

Author SHA1 Message Date
4e8b84b67e Update CppExplorer.cs 2020-09-07 03:26:10 +10:00
5b94e31a12 1.6.0
* Fix for failed unstrip with RectOffset(int, int, int, int) ctor
* Cleanups
2020-09-07 03:25:43 +10:00
692a37635e 1.5.9
* Added beta support for Dictionaries. Should work fine for simple dictionaries, may be janky or broken for more complex ones (eg. Dicts nested inside a Dict).
* Fixed a bug with Lists of primitive values.
2020-09-06 21:33:09 +10:00
9cb1cea025 Update README.md 2020-09-06 17:48:40 +10:00
e13f198815 1.5.8
* Fixed a bug where the Page Helper would not update the total page count after changing the limit per page
* Cleaned up the "Find Instances" helper, it will now filter out all types in the `System`, `Mono`, `Il2CppSystem` and `Iced` namespaces.
* Improved the Find Instances helper so that it will avoid exceptions and get more results.
* Enums now display their value type name
* Changed the Scroll View unstrip so that it is less hard-coded for different unity versions and more dynamic.
2020-09-06 16:55:39 +10:00
9a059c1056 Update ScenePage.cs 2020-09-06 03:19:39 +10:00
ffb6cad8c2 1.5.7
* More fixes for failed unstripping, should fix most issues in Audica and other games
* If `GetRootSceneObjects` fails and we fall back to the manual implementation, the auto-update for root scene objects will be disabled. Instead, there will be a button to press to update the list.
* Transforms are now listed on the Components list in the GameObject inspector
* Various cleanups
2020-09-06 03:19:21 +10:00
d0a4863139 Update ScenePage.cs 2020-09-05 23:18:58 +10:00
bb8837d58c 1.5.6 hotfix
* Fix for setting CacheColor value
* Cleanup
2020-09-05 23:10:50 +10:00
a236b272c1 Update README.md 2020-09-05 20:41:48 +10:00
18de1eaf1c 1.5.6
Cleanup
2020-09-05 20:38:46 +10:00
b1264c6912 1.5.6
* Added a fallback method for GetRootSceneObjects for games where this fails.
* Fixed an issue where the `new Rect(Rect source)` constructor was failing in some games, using the normal ctor now.
* Added special support for `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Color` and `Rect` structs in the reflection inspector to allow for easier editing.
* Several improvements to GameObject Inspector, such as position/rotation freezing, local/global context, and an improved way to edit the transform values.
2020-09-05 20:27:00 +10:00
9836566e55 tidy up 2020-09-05 01:30:50 +10:00
31 changed files with 1203 additions and 436 deletions

View File

@ -1,4 +1,4 @@
# CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.7.1-green.svg)]() # CppExplorer [![Version](https://img.shields.io/badge/MelonLoader-0.2.7.1-green.svg)](https://github.com/HerpDerpinstine/MelonLoader)
<p align="center"> <p align="center">
<img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png"> <img align="center" src="https://sinai-dev.github.io/images/thumbs/02.png">
@ -14,10 +14,9 @@
<img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" /> <img src="https://img.shields.io/github/downloads/sinai-dev/CppExplorer/total.svg" />
</p> </p>
### Known issue ### Known issues
Due to limitations with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower)'s Unstripping, CppExplorer may encounter a `MissingMethodException` when trying to use certain UnityEngine methods. * CppExplorer may experience a `MissingMethodException` when trying to use certain UnityEngine methods. If you experience this, please open an issue and I will do my best to fix it.
* Scrolling with mouse wheel in the CppExplorer menu may not work on all games at the moment.
Since version [1.5.4](https://github.com/sinai-dev/CppExplorer/releases/tag/1.5.4), CppExplorer manually unstrips most of these methods itself. If you encounter more methods which failed unstripping, please let me know by opening an issue and I will do my best to fix it.
## Features ## Features
* Scene hierarchy explorer * Scene hierarchy explorer
@ -45,7 +44,7 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
* A simple menu which allows you to traverse the Transform heirarchy of the scene. * 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. * Click on a GameObject to set it as the current path, or <b>Inspect</b> it to send it to an Inspector Window.
[![](https://i.imgur.com/2b0q0jL.png)](https://i.imgur.com/2b0q0jL.png) [![](https://i.imgur.com/BzTOCvp.png)](https://i.imgur.com/BzTOCvp.png)
### Inspectors ### Inspectors
@ -60,7 +59,7 @@ CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Re
* Allows you to see the children and components on a GameObject. * 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. * Can use some basic GameObject Controls such as translating and rotating the object, destroy it, clone it, etc.
[![](https://i.imgur.com/JTxqlx4.png)](https://i.imgur.com/JTxqlx4.png) [![](https://i.imgur.com/DiDvl0Q.png)](https://i.imgur.com/DiDvl0Q.png)
### Reflection Inspector ### Reflection Inspector
@ -68,7 +67,7 @@ CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Re
* Allows you to inspect Properties, Fields and basic Methods, as well as set primitive values and evaluate primitive methods. * 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. * Can search and filter members for the ones you are interested in.
[![](https://i.imgur.com/iq92m0l.png)](https://i.imgur.com/iq92m0l.png) [![](https://i.imgur.com/Pq127XD.png)](https://i.imgur.com/Pq127XD.png)
### Object Search ### Object Search

View File

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
public class CacheDictionary : CacheObjectBase
{
public override void Init()
{
//base.Init();
Value = "Unsupported";
}
public override void UpdateValue()
{
//base.UpdateValue();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label("<color=red>Dictionary (unsupported)</color>", null);
}
}
}

View File

@ -14,6 +14,7 @@ namespace Explorer
{ {
public object Value; public object Value;
public string ValueTypeName; public string ValueTypeName;
public Type ValueType;
// Reflection Inspector only // Reflection Inspector only
public MemberInfo MemInfo { get; set; } public MemberInfo MemInfo { get; set; }
@ -79,11 +80,7 @@ namespace Explorer
{ {
Type type = null; Type type = null;
if (obj != null) if (memberInfo != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
else if (memberInfo != null)
{ {
if (memberInfo is FieldInfo fi) if (memberInfo is FieldInfo fi)
{ {
@ -98,6 +95,10 @@ namespace Explorer
type = mi.ReturnType; type = mi.ReturnType;
} }
} }
else if (obj != null)
{
type = ReflectionHelpers.GetActualType(obj);
}
if (type == null) if (type == null)
{ {
@ -114,6 +115,11 @@ namespace Explorer
{ {
CacheObjectBase holder; CacheObjectBase holder;
// This is pretty ugly, could probably make a cleaner implementation.
// However, the only cleaner ways I can think of are slower and probably not worth it.
// Note: the order is somewhat important.
if (memberInfo is MethodInfo mi) if (memberInfo is MethodInfo mi)
{ {
if (CacheMethod.CanEvaluate(mi)) if (CacheMethod.CanEvaluate(mi))
@ -137,20 +143,38 @@ namespace Explorer
{ {
holder = new CacheEnum(); holder = new CacheEnum();
} }
else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType)) else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
{ {
holder = new CacheList(); holder = new CacheVector();
} }
else if (valueType == typeof(Quaternion))
{
holder = new CacheQuaternion();
}
else if (valueType == typeof(Color))
{
holder = new CacheColor();
}
else if (valueType == typeof(Rect))
{
holder = new CacheRect();
}
// must check this before IsEnumerable
else if (ReflectionHelpers.IsDictionary(valueType)) else if (ReflectionHelpers.IsDictionary(valueType))
{ {
holder = new CacheDictionary(); holder = new CacheDictionary();
} }
else if (ReflectionHelpers.IsEnumerable(valueType) || ReflectionHelpers.IsCppList(valueType))
{
holder = new CacheList();
}
else else
{ {
holder = new CacheOther(); holder = new CacheOther();
} }
holder.Value = obj; holder.Value = obj;
holder.ValueType = valueType;
holder.ValueTypeName = valueType.FullName; holder.ValueTypeName = valueType.FullName;
if (memberInfo != null) if (memberInfo != null)

View File

@ -0,0 +1,278 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MelonLoader;
using UnityEngine;
using System.Reflection;
namespace Explorer
{
public class CacheDictionary : CacheObjectBase
{
public bool IsExpanded { get; set; }
public PageHelper Pages = new PageHelper();
public float WhiteSpace = 215f;
public float ButtonWidthOffset = 290f;
private CacheObjectBase[] m_cachedKeys;
private CacheObjectBase[] m_cachedValues;
public Type TypeOfKeys
{
get
{
if (m_keysType == null) GetGenericArguments();
return m_keysType;
}
}
private Type m_keysType;
public Type TypeOfValues
{
get
{
if (m_valuesType == null) GetGenericArguments();
return m_valuesType;
}
}
private Type m_valuesType;
public IDictionary IDict
{
get => m_iDictionary ?? (m_iDictionary = Value as IDictionary) ?? Il2CppDictionaryToMono();
set => m_iDictionary = value;
}
private IDictionary m_iDictionary;
// This is a bit janky due to Il2Cpp Dictionary not implementing IDictionary.
private IDictionary Il2CppDictionaryToMono()
{
// note: "ValueType" is the Dictionary itself, TypeOfValues is the 'Dictionary.Values' type.
// make generic dictionary from key and value type
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
.MakeGenericType(TypeOfKeys, TypeOfValues));
// get keys and values
var keys = ValueType.GetProperty("Keys") .GetValue(Value);
var values = ValueType.GetProperty("Values").GetValue(Value);
// create lists to hold them
var keyList = new List<object>();
var valueList = new List<object>();
// get keys enumerator and store keys
var keyEnumerator = keys.GetType().GetMethod("GetEnumerator").Invoke(keys, null);
var keyCollectionType = keyEnumerator.GetType();
var keyMoveNext = keyCollectionType.GetMethod("MoveNext");
var keyCurrent = keyCollectionType.GetProperty("Current");
while ((bool)keyMoveNext.Invoke(keyEnumerator, null))
{
keyList.Add(keyCurrent.GetValue(keyEnumerator));
}
// get values enumerator and store values
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
var valueCollectionType = valueEnumerator.GetType();
var valueMoveNext = valueCollectionType.GetMethod("MoveNext");
var valueCurrent = valueCollectionType.GetProperty("Current");
while ((bool)valueMoveNext.Invoke(valueEnumerator, null))
{
valueList.Add(valueCurrent.GetValue(valueEnumerator));
}
// finally iterate into actual dictionary
for (int i = 0; i < keyList.Count; i++)
{
dict.Add(keyList[i], valueList[i]);
}
return dict;
}
// ========== Methods ==========
private void GetGenericArguments()
{
if (m_keysType == null || m_valuesType == null)
{
if (this.MemInfo != null)
{
Type memberType = null;
switch (this.MemInfo.MemberType)
{
case MemberTypes.Field:
memberType = (MemInfo as FieldInfo).FieldType;
break;
case MemberTypes.Property:
memberType = (MemInfo as PropertyInfo).PropertyType;
break;
}
if (memberType != null && memberType.IsGenericType)
{
m_keysType = memberType.GetGenericArguments()[0];
m_valuesType = memberType.GetGenericArguments()[1];
}
}
else if (Value != null)
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_keysType = type.GetGenericArguments()[0];
m_valuesType = type.GetGenericArguments()[1];
}
}
}
return;
}
public override void UpdateValue()
{
base.UpdateValue();
// reset
IDict = null;
if (Value == null || IDict == null)
{
return;
}
var keys = new List<CacheObjectBase>();
foreach (var key in IDict.Keys)
{
var cache = GetCacheObject(key, TypeOfKeys);
cache.UpdateValue();
keys.Add(cache);
}
var values = new List<CacheObjectBase>();
foreach (var val in IDict.Values)
{
var cache = GetCacheObject(val, TypeOfValues);
cache.UpdateValue();
values.Add(cache);
}
m_cachedKeys = keys.ToArray();
m_cachedValues = values.ToArray();
}
// ============= GUI Draw =============
public override void DrawValue(Rect window, float width)
{
if (m_cachedKeys == null || m_cachedValues == null)
{
GUILayout.Label("Cached keys or values is null!", null);
return;
}
int count = m_cachedKeys.Length;
if (!IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
}
}
else
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
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) }))
{
WindowManager.InspectObject(Value, out bool _);
}
GUI.skin.button.alignment = TextAnchor.MiddleCenter;
GUILayout.Space(5);
if (IsExpanded)
{
float whitespace = WhiteSpace;
if (whitespace > 0)
{
ClampLabelWidth(window, ref whitespace);
}
Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
{
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
Pages.CurrentPageLabel();
// prev/next page buttons
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Left);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{
Pages.TurnPage(Turn.Right);
}
Pages.DrawLimitInputArea();
GUILayout.Space(5);
}
int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{
var key = m_cachedKeys[i];
var val = m_cachedValues[i];
//collapsing the BeginHorizontal called from ReflectionWindow.WindowFunction or previous array entry
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
//GUILayout.Space(whitespace);
if (key == null || val == null)
{
GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
}
else
{
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.MinWidth((window.width / 3) - 60f) });
GUILayout.Label("Key:", new GUILayoutOption[] { GUILayout.Width(40) });
key.DrawValue(window, (window.width / 2) - 30f);
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Label("Value:", new GUILayoutOption[] { GUILayout.Width(40) });
val.DrawValue(window, (window.width / 2) - 30f);
GUILayout.EndHorizontal();
}
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
}
}
}

View File

@ -218,6 +218,7 @@ namespace Explorer
if (GetCacheObject(obj, t) is CacheObjectBase cached) if (GetCacheObject(obj, t) is CacheObjectBase cached)
{ {
cached.UpdateValue();
list.Add(cached); list.Add(cached);
} }
else else
@ -262,7 +263,7 @@ namespace Explorer
} }
GUI.skin.button.alignment = TextAnchor.MiddleLeft; GUI.skin.button.alignment = TextAnchor.MiddleLeft;
string btnLabel = "<color=yellow>[" + count + "] " + EntryType + "</color>"; string btnLabel = "<color=yellow>[" + count + "] " + EntryType.FullName + "</color>";
if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) })) if (GUILayout.Button(btnLabel, new GUILayoutOption[] { GUILayout.MaxWidth(window.width - ButtonWidthOffset) }))
{ {
WindowManager.InspectObject(Value, out bool _); WindowManager.InspectObject(Value, out bool _);
@ -305,12 +306,6 @@ namespace Explorer
GUILayout.Space(5); GUILayout.Space(5);
} }
//int offset = ArrayOffset * ArrayLimit;
//if (offset >= count)
//{
// offset = 0;
// ArrayOffset = 0;
//}
int offset = Pages.CalculateOffsetIndex(); int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++) for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)

View File

@ -212,6 +212,11 @@ namespace Explorer
cacheList.WhiteSpace = 0f; cacheList.WhiteSpace = 0f;
cacheList.ButtonWidthOffset += 70f; cacheList.ButtonWidthOffset += 70f;
} }
else if (m_cachedReturnValue is CacheDictionary cacheDict)
{
cacheDict.WhiteSpace = 0f;
cacheDict.ButtonWidthOffset += 70f;
}
m_cachedReturnValue.UpdateValue(); m_cachedReturnValue.UpdateValue();
} }
else else

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheColor : CacheObjectBase
{
private string r = "0";
private string g = "0";
private string b = "0";
private string a = "0";
public override void UpdateValue()
{
base.UpdateValue();
var color = (Color)Value;
r = color.r.ToString();
g = color.g.ToString();
b = color.b.ToString();
a = color.a.ToString();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Color</color>: {((Color)Value).ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("R:", new GUILayoutOption[] { GUILayout.Width(30) });
r = GUILayout.TextField(r, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("G:", new GUILayoutOption[] { GUILayout.Width(30) });
g = GUILayout.TextField(g, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("B:", new GUILayoutOption[] { GUILayout.Width(30) });
b = GUILayout.TextField(b, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("A:", new GUILayoutOption[] { GUILayout.Width(30) });
a = GUILayout.TextField(a, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(r, out float fR)
&& float.TryParse(g, out float fG)
&& float.TryParse(b, out float fB)
&& float.TryParse(a, out float fA))
{
Value = new Color(fR, fB, fG, fA);
SetValue();
}
}
}
}

View File

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

View File

@ -8,90 +8,36 @@ namespace Explorer
{ {
public class CachePrimitive : CacheObjectBase public class CachePrimitive : CacheObjectBase
{ {
public enum PrimitiveTypes private bool m_isBool;
{ private bool m_isString;
Bool,
Double,
Float,
Int,
String,
Char
}
private string m_valueToString; private string m_valueToString;
public PrimitiveTypes PrimitiveType; public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
public MethodInfo ParseMethod
{
get
{
if (m_parseMethod == null)
{
m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) });
}
return m_parseMethod;
}
}
private MethodInfo m_parseMethod; private MethodInfo m_parseMethod;
public override void Init() public override void Init()
{ {
if (Value == null) if (ValueType == null)
{ {
// this must mean it is a string. No other primitive type should be nullable. ValueType = Value?.GetType();
PrimitiveType = PrimitiveTypes.String;
return;
}
m_valueToString = Value.ToString(); // has to be a string at this point
var type = Value.GetType(); if (ValueType == null)
if (type == typeof(bool))
{ {
PrimitiveType = PrimitiveTypes.Bool; ValueType = typeof(string);
}
else if (type == typeof(double))
{
PrimitiveType = PrimitiveTypes.Double;
}
else if (type == typeof(float))
{
PrimitiveType = PrimitiveTypes.Float;
}
else if (IsInteger(type))
{
PrimitiveType = PrimitiveTypes.Int;
}
else if (type == typeof(char))
{
PrimitiveType = PrimitiveTypes.Char;
}
else
{
PrimitiveType = PrimitiveTypes.String;
} }
} }
private static bool IsInteger(Type type) if (ValueType == typeof(string))
{ {
// For our purposes, all types of int can be treated the same, including IntPtr. m_isString = true;
return _integerTypes.Contains(type);
} }
else if (ValueType == typeof(bool))
private static readonly HashSet<Type> _integerTypes = new HashSet<Type>
{ {
typeof(int), m_isBool = true;
typeof(uint), }
typeof(short), }
typeof(ushort),
typeof(long),
typeof(ulong),
typeof(byte),
typeof(sbyte),
typeof(IntPtr)
};
public override void UpdateValue() public override void UpdateValue()
{ {
@ -102,11 +48,10 @@ namespace Explorer
public override void DrawValue(Rect window, float width) public override void DrawValue(Rect window, float width)
{ {
if (PrimitiveType == PrimitiveTypes.Bool) if (m_isBool)
{ {
var b = (bool)Value; var b = (bool)Value;
var color = $"<color={(b ? "lime>" : "red>")}"; var label = $"<color={(b ? "lime" : "red")}>{b}</color>";
var label = $"{color}{b}</color>";
if (CanWrite) if (CanWrite)
{ {
@ -123,7 +68,8 @@ namespace Explorer
} }
else else
{ {
GUILayout.Label("<color=yellow><i>" + PrimitiveType + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) }); // using ValueType.Name instead of ValueTypeName, because we only want the short name.
GUILayout.Label("<color=yellow><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
int dynSize = 25 + (m_valueToString.Length * 15); int dynSize = 25 + (m_valueToString.Length * 15);
var maxwidth = window.width - 300f; var maxwidth = window.width - 300f;
@ -150,7 +96,7 @@ namespace Explorer
} }
} }
public void SetValueFromInput(string value) public void SetValueFromInput(string valueString)
{ {
if (MemInfo == null) if (MemInfo == null)
{ {
@ -158,16 +104,15 @@ namespace Explorer
return; return;
} }
if (PrimitiveType == PrimitiveTypes.String) if (m_isString)
{ {
Value = value; Value = valueString;
} }
else else
{ {
try try
{ {
var val = ParseMethod.Invoke(null, new object[] { value }); Value = ParseMethod.Invoke(null, new object[] { valueString });
Value = val;
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheQuaternion : CacheObjectBase
{
private string x = "0";
private string y = "0";
private string z = "0";
public override void UpdateValue()
{
base.UpdateValue();
var euler = ((Quaternion)Value).eulerAngles;
x = euler.x.ToString();
y = euler.y.ToString();
z = euler.z.ToString();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Quaternion</color>: {((Quaternion)Value).eulerAngles.ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(x, out float fX)
&& float.TryParse(y, out float fY)
&& float.TryParse(z, out float fZ))
{
Value = Quaternion.Euler(new Vector3(fX, fY, fZ));
SetValue();
}
}
}
}

View File

@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public class CacheRect : CacheObjectBase
{
private string x = "0";
private string y = "0";
private string w = "0";
private string h = "0";
public override void UpdateValue()
{
base.UpdateValue();
var rect = (Rect)Value;
x = rect.x.ToString();
y = rect.y.ToString();
w = rect.width.ToString();
h = rect.height.ToString();
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Rect</color>: {((Rect)Value).ToString()}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("H:", new GUILayoutOption[] { GUILayout.Width(30) });
h = GUILayout.TextField(h, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(x, out float fX)
&& float.TryParse(y, out float fY)
&& float.TryParse(w, out float fW)
&& float.TryParse(h, out float fH))
{
Value = new Rect(fX, fY, fW, fH);
SetValue();
}
}
}
}

View File

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;
namespace Explorer
{
public class CacheVector : CacheObjectBase
{
public int VectorSize = 2;
private string x = "0";
private string y = "0";
private string z = "0";
private string w = "0";
private MethodInfo m_toStringMethod;
public override void Init()
{
if (Value is Vector2)
{
VectorSize = 2;
}
else if (Value is Vector3)
{
VectorSize = 3;
}
else
{
VectorSize = 4;
}
m_toStringMethod = Value.GetType().GetMethod("ToString", new Type[0]);
}
public override void UpdateValue()
{
base.UpdateValue();
if (Value is Vector2 vec2)
{
x = vec2.x.ToString();
y = vec2.y.ToString();
}
else if (Value is Vector3 vec3)
{
x = vec3.x.ToString();
y = vec3.y.ToString();
z = vec3.z.ToString();
}
else if (Value is Vector4 vec4)
{
x = vec4.x.ToString();
y = vec4.y.ToString();
z = vec4.z.ToString();
w = vec4.w.ToString();
}
}
public override void DrawValue(Rect window, float width)
{
GUILayout.Label($"<color=yellow>Vector{VectorSize}</color>: {(string)m_toStringMethod.Invoke(Value, new object[0])}", null);
if (CanWrite)
{
GUILayout.EndHorizontal();
var whitespace = window.width - width - 90;
// always draw x and y
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("X:", new GUILayoutOption[] { GUILayout.Width(30) });
x = GUILayout.TextField(x, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Y:", new GUILayoutOption[] { GUILayout.Width(30) });
y = GUILayout.TextField(y, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
if (VectorSize > 2)
{
// draw z
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("Z:", new GUILayoutOption[] { GUILayout.Width(30) });
z = GUILayout.TextField(z, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
}
if (VectorSize > 3)
{
// draw w
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
GUILayout.Label("W:", new GUILayoutOption[] { GUILayout.Width(30) });
w = GUILayout.TextField(w, new GUILayoutOption[] { GUILayout.Width(120) });
GUILayout.EndHorizontal();
}
// draw set value button
GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace);
if (GUILayout.Button("<color=lime>Apply</color>", new GUILayoutOption[] { GUILayout.Width(155) }))
{
SetValueFromInput();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
}
}
private void SetValueFromInput()
{
if (float.TryParse(x, out float fX)
&& float.TryParse(y, out float fY)
&& float.TryParse(z, out float fZ)
&& float.TryParse(w, out float fW))
{
object vector = null;
switch (VectorSize)
{
case 2: vector = new Vector2(fX, fY); break;
case 3: vector = new Vector3(fX, fY, fZ); break;
case 4: vector = new Vector4(fX, fY, fZ, fW); break;
}
if (vector != null)
{
Value = vector;
SetValue();
}
}
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -12,7 +13,7 @@ namespace Explorer
public class CppExplorer : MelonMod public class CppExplorer : MelonMod
{ {
public const string GUID = "com.sinai.cppexplorer"; public const string GUID = "com.sinai.cppexplorer";
public const string VERSION = "1.5.5"; public const string VERSION = "1.6.0";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer" public const string NAME = "CppExplorer"

View File

@ -120,17 +120,21 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CachedObjects\CacheDictionary.cs" /> <Compile Include="CachedObjects\Struct\CacheColor.cs" />
<Compile Include="CachedObjects\CacheEnum.cs" /> <Compile Include="CachedObjects\Object\CacheDictionary.cs" />
<Compile Include="CachedObjects\CacheGameObject.cs" /> <Compile Include="CachedObjects\Struct\CacheEnum.cs" />
<Compile Include="CachedObjects\CacheList.cs" /> <Compile Include="CachedObjects\Object\CacheGameObject.cs" />
<Compile Include="CachedObjects\CachePrimitive.cs" /> <Compile Include="CachedObjects\Object\CacheList.cs" />
<Compile Include="CachedObjects\CacheOther.cs" /> <Compile Include="CachedObjects\Struct\CachePrimitive.cs" />
<Compile Include="CachedObjects\CacheMethod.cs" /> <Compile Include="CachedObjects\Other\CacheOther.cs" />
<Compile Include="CachedObjects\Other\CacheMethod.cs" />
<Compile Include="CachedObjects\Struct\CacheQuaternion.cs" />
<Compile Include="CachedObjects\Struct\CacheVector.cs" />
<Compile Include="CachedObjects\Struct\CacheRect.cs" />
<Compile Include="CppExplorer.cs" /> <Compile Include="CppExplorer.cs" />
<Compile Include="Extensions\ReflectionExtensions.cs" /> <Compile Include="Extensions\ReflectionExtensions.cs" />
<Compile Include="Unstripping\GUIUnstrip.cs" /> <Compile Include="UnstripFixes\GUIUnstrip.cs" />
<Compile Include="Unstripping\ScrollViewStateUnstrip.cs" /> <Compile Include="UnstripFixes\ScrollViewStateUnstrip.cs" />
<Compile Include="Extensions\UnityExtensions.cs" /> <Compile Include="Extensions\UnityExtensions.cs" />
<Compile Include="Helpers\PageHelper.cs" /> <Compile Include="Helpers\PageHelper.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" /> <Compile Include="Helpers\ReflectionHelpers.cs" />
@ -138,8 +142,8 @@
<Compile Include="Helpers\UnityHelpers.cs" /> <Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="MainMenu\InspectUnderMouse.cs" /> <Compile Include="MainMenu\InspectUnderMouse.cs" />
<Compile Include="CachedObjects\CacheObjectBase.cs" /> <Compile Include="CachedObjects\CacheObjectBase.cs" />
<Compile Include="Unstripping\SliderHandlerUnstrip.cs" /> <Compile Include="UnstripFixes\SliderHandlerUnstrip.cs" />
<Compile Include="Unstripping\UnstripExtensions.cs" /> <Compile Include="UnstripFixes\UnstripExtensions.cs" />
<Compile Include="Windows\ResizeDrag.cs" /> <Compile Include="Windows\ResizeDrag.cs" />
<Compile Include="Windows\TabViewWindow.cs" /> <Compile Include="Windows\TabViewWindow.cs" />
<Compile Include="Windows\UIWindow.cs" /> <Compile Include="Windows\UIWindow.cs" />

View File

@ -16,7 +16,18 @@ namespace Explorer
public class PageHelper public class PageHelper
{ {
public int PageOffset { get; set; } public int PageOffset { get; set; }
public int ItemsPerPage { get; set; } = 20;
public int ItemsPerPage
{
get => m_itemsPerPage;
set
{
m_itemsPerPage = value;
CalculateMaxOffset();
}
}
private int m_itemsPerPage = 20;
public int ItemCount public int ItemCount
{ {
get => m_count; get => m_count;

View File

@ -74,12 +74,13 @@ namespace Explorer
return false; return false;
} }
public static bool IsArray(Type t) public static bool IsEnumerable(Type t)
{ {
return typeof(System.Collections.IEnumerable).IsAssignableFrom(t); return typeof(System.Collections.IEnumerable).IsAssignableFrom(t);
} }
public static bool IsList(Type t) // Only Il2Cpp List needs this check. C# List is IEnumerable.
public static bool IsCppList(Type t)
{ {
if (t.IsGenericType) if (t.IsGenericType)
{ {
@ -98,21 +99,21 @@ namespace Explorer
{ {
return t.IsGenericType return t.IsGenericType
&& t.GetGenericTypeDefinition() is Type typeDef && t.GetGenericTypeDefinition() is Type typeDef
&& typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>)); && (typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.Dictionary<,>))
|| typeDef.IsAssignableFrom(typeof(Dictionary<,>)));
} }
public static Type GetTypeByName(string typeName) public static Type GetTypeByName(string typeName)
{ {
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{ {
try foreach (var type in GetTypesSafe(asm))
{ {
if (asm.GetType(typeName) is Type type) if (type.FullName == typeName)
{ {
return type; return type;
} }
} }
catch { }
} }
return null; return null;
@ -137,6 +138,13 @@ namespace Explorer
return obj.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) public static Type[] GetAllBaseTypes(object obj)
{ {
var list = new List<Type>(); var list = new List<Type>();

View File

@ -56,7 +56,10 @@ namespace Explorer
{ {
_horizBarStyle = new GUIStyle(); _horizBarStyle = new GUIStyle();
_horizBarStyle.normal.background = Texture2D.whiteTexture; _horizBarStyle.normal.background = Texture2D.whiteTexture;
_horizBarStyle.margin = new RectOffset(0, 0, 4, 4); var rectOffset = new RectOffset();
rectOffset.top = 4;
rectOffset.bottom = 4;
_horizBarStyle.margin = rectOffset;
_horizBarStyle.fixedHeight = 2; _horizBarStyle.fixedHeight = 2;
} }
@ -73,7 +76,10 @@ namespace Explorer
{ {
_horizBarSmallStyle = new GUIStyle(); _horizBarSmallStyle = new GUIStyle();
_horizBarSmallStyle.normal.background = Texture2D.whiteTexture; _horizBarSmallStyle.normal.background = Texture2D.whiteTexture;
_horizBarSmallStyle.margin = new RectOffset(0, 0, 2, 2); var rectOffset = new RectOffset();
rectOffset.top = 2;
rectOffset.bottom = 2;
_horizBarSmallStyle.margin = rectOffset;
_horizBarSmallStyle.fixedHeight = 1; _horizBarSmallStyle.fixedHeight = 1;
} }

View File

@ -14,7 +14,7 @@ namespace Explorer
{ {
public class ConsolePage : WindowPage public class ConsolePage : WindowPage
{ {
public override string Name { get => "C# Console"; set => base.Name = value; } public override string Name { get => "C# Console"; }
private ScriptEvaluator _evaluator; private ScriptEvaluator _evaluator;
private readonly StringBuilder _sb = new StringBuilder(); private readonly StringBuilder _sb = new StringBuilder();

View File

@ -12,27 +12,26 @@ namespace Explorer
{ {
public static ScenePage Instance; public static ScenePage Instance;
public override string Name { get => "Scene Explorer"; set => base.Name = value; } public override string Name { get => "Scene Explorer"; }
public PageHelper Pages = new PageHelper(); public PageHelper Pages = new PageHelper();
private float m_timeOfLastUpdate = -1f; private float m_timeOfLastUpdate = -1f;
private const int PASSIVE_UPDATE_INTERVAL = 1;
// ----- Holders for GUI elements ----- // private static bool m_getRootObjectsFailed;
private string m_currentScene = ""; private static string m_currentScene = "";
// gameobject list // gameobject list
private Transform m_currentTransform; private Transform m_currentTransform;
private List<GameObjectCache> m_objectList = new List<GameObjectCache>(); private readonly List<GameObjectCache> m_objectList = new List<GameObjectCache>();
// search bar // search bar
private bool m_searching = false; private bool m_searching = false;
private string m_searchInput = ""; private string m_searchInput = "";
private List<GameObjectCache> m_searchResults = new List<GameObjectCache>(); private List<GameObjectCache> m_searchResults = new List<GameObjectCache>();
// ------------ Init and Update ------------ //
public override void Init() public override void Init()
{ {
Instance = this; Instance = this;
@ -44,64 +43,6 @@ namespace Explorer
SetTransformTarget(null); SetTransformTarget(null);
} }
//public void CheckOffset(ref int offset, int childCount)
//{
// if (offset >= childCount)
// {
// offset = 0;
// m_pageOffset = 0;
// }
//}
public override void Update()
{
if (m_searching) return;
if (Time.time - m_timeOfLastUpdate < 1f) return;
m_timeOfLastUpdate = Time.time;
m_objectList = new List<GameObjectCache>();
var allTransforms = new List<Transform>();
// get current list of all transforms (either scene root or our current transform children)
if (m_currentTransform)
{
for (int i = 0; i < m_currentTransform.childCount; i++)
{
allTransforms.Add(m_currentTransform.GetChild(i));
}
}
else
{
var scene = SceneManager.GetSceneByName(m_currentScene);
var list = new Il2CppSystem.Collections.Generic.List<GameObject>
{
Capacity = scene.rootCount
};
Scene.GetRootGameObjectsInternal(scene.handle, list);
foreach (var obj in list)
{
allTransforms.Add(obj.transform);
}
}
Pages.ItemCount = allTransforms.Count;
int offset = Pages.CalculateOffsetIndex();
// sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
{
var child = allTransforms[i];
m_objectList.Add(new GameObjectCache(child.gameObject));
}
}
public void SetTransformTarget(Transform t) public void SetTransformTarget(Transform t)
{ {
m_currentTransform = t; m_currentTransform = t;
@ -109,8 +50,7 @@ namespace Explorer
if (m_searching) if (m_searching)
CancelSearch(); CancelSearch();
m_timeOfLastUpdate = -1f; Update_Impl(true);
Update();
} }
public void TraverseUp() public void TraverseUp()
@ -135,23 +75,123 @@ namespace Explorer
public void CancelSearch() public void CancelSearch()
{ {
m_searching = false; m_searching = false;
if (m_getRootObjectsFailed && !m_currentTransform)
{
GetRootObjectsManual_Impl();
}
} }
public List<GameObjectCache> SearchSceneObjects(string _search) public List<GameObjectCache> SearchSceneObjects(string _search)
{ {
var matches = new List<GameObjectCache>(); var matches = new List<GameObjectCache>();
foreach (var obj in Resources.FindObjectsOfTypeAll<GameObject>()) foreach (var obj in Resources.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
{ {
if (obj.name.ToLower().Contains(_search.ToLower()) && obj.scene.name == m_currentScene) var go = obj.TryCast<GameObject>();
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
{ {
matches.Add(new GameObjectCache(obj)); matches.Add(new GameObjectCache(go));
} }
} }
return matches; return matches;
} }
public override void Update()
{
if (m_searching) return;
if (Time.time - m_timeOfLastUpdate < PASSIVE_UPDATE_INTERVAL) return;
m_timeOfLastUpdate = Time.time;
Update_Impl();
}
private void Update_Impl(bool manual = false)
{
List<Transform> allTransforms = new List<Transform>();
// get current list of all transforms (either scene root or our current transform children)
if (m_currentTransform)
{
for (int i = 0; i < m_currentTransform.childCount; i++)
{
allTransforms.Add(m_currentTransform.GetChild(i));
}
}
else
{
if (!m_getRootObjectsFailed)
{
try
{
var scene = SceneManager.GetSceneByName(m_currentScene);
allTransforms.AddRange(scene.GetRootGameObjects()
.Select(it => it.transform));
}
catch
{
MelonLogger.Log("Exception getting root scene objects, falling back to backup method...");
m_getRootObjectsFailed = true;
allTransforms.AddRange(GetRootObjectsManual_Impl());
}
}
else
{
if (!manual)
{
return;
}
allTransforms.AddRange(GetRootObjectsManual_Impl());
}
}
Pages.ItemCount = allTransforms.Count;
int offset = Pages.CalculateOffsetIndex();
// sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_objectList.Clear();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
{
var child = allTransforms[i];
m_objectList.Add(new GameObjectCache(child.gameObject));
}
}
private IEnumerable<Transform> GetRootObjectsManual_Impl()
{
try
{
var array = Resources.FindObjectsOfTypeAll(ReflectionHelpers.TransformType);
var list = new List<Transform>();
foreach (var obj in array)
{
var transform = obj.TryCast<Transform>();
if (transform.parent == null && transform.gameObject.scene.name == m_currentScene)
{
list.Add(transform);
}
}
return list;
}
catch (Exception e)
{
MelonLogger.Log("Exception getting root scene objects (manual): "
+ e.GetType() + ", " + e.Message + "\r\n"
+ e.StackTrace);
return new Transform[0];
}
}
// --------- GUI Draw Function --------- // // --------- GUI Draw Function --------- //
public override void DrawWindow() public override void DrawWindow()
@ -251,8 +291,7 @@ namespace Explorer
{ {
Pages.TurnPage(Turn.Left, ref this.scroll); Pages.TurnPage(Turn.Left, ref this.scroll);
m_timeOfLastUpdate = -1f; Update_Impl(true);
Update();
} }
Pages.CurrentPageLabel(); Pages.CurrentPageLabel();
@ -261,8 +300,7 @@ namespace Explorer
{ {
Pages.TurnPage(Turn.Right, ref this.scroll); Pages.TurnPage(Turn.Right, ref this.scroll);
m_timeOfLastUpdate = -1f; Update_Impl(true);
Update();
} }
} }
@ -292,12 +330,24 @@ namespace Explorer
else else
{ {
GUILayout.Label("Scene Root GameObjects:", null); GUILayout.Label("Scene Root GameObjects:", null);
if (m_getRootObjectsFailed)
{
if (GUILayout.Button("Update Root Object List (auto-update failed!)", null))
{
Update_Impl(true);
}
}
} }
if (m_objectList.Count > 0) if (m_objectList.Count > 0)
{ {
foreach (var obj in m_objectList) for (int i = 0; i < m_objectList.Count; i++)
{ {
var obj = m_objectList[i];
if (obj == null) continue;
if (!obj.RefGameObject) if (!obj.RefGameObject)
{ {
string label = "<color=red><i>null"; string label = "<color=red><i>null";

View File

@ -13,7 +13,7 @@ namespace Explorer
{ {
public static SearchPage Instance; public static SearchPage Instance;
public override string Name { get => "Object Search"; set => base.Name = value; } public override string Name { get => "Object Search"; }
private string m_searchInput = ""; private string m_searchInput = "";
private string m_typeInput = ""; private string m_typeInput = "";
@ -137,14 +137,11 @@ namespace Explorer
if (m_searchResults.Count > 0) if (m_searchResults.Count > 0)
{ {
//int offset = m_pageOffset * this.m_limit;
//if (offset >= count) m_pageOffset = 0;
int offset = Pages.CalculateOffsetIndex(); int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++) for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{ {
m_searchResults[i].Draw(MainMenu.MainRect, 0f); m_searchResults[i].Draw(MainMenu.MainRect, 0f);
//m_searchResults[i].DrawValue(MainMenu.MainRect);
} }
} }
else else
@ -175,16 +172,6 @@ namespace Explorer
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) }); GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) }); m_searchInput = GUILayout.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
//GUI.skin.label.alignment = TextAnchor.MiddleRight;
//GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
//var resultinput = m_limit.ToString();
//resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
//if (int.TryParse(resultinput, out int _i) && _i > 0)
//{
// m_limit = _i;
//}
//GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -266,7 +253,7 @@ namespace Explorer
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput)); CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
} }
private List<object> FindAllObjectsOfType(string _search, string _type) private List<object> FindAllObjectsOfType(string searchQuery, string typeName)
{ {
Il2CppSystem.Type searchType = null; Il2CppSystem.Type searchType = null;
@ -274,13 +261,18 @@ namespace Explorer
{ {
try try
{ {
var findType = ReflectionHelpers.GetTypeByName(_type); if (ReflectionHelpers.GetTypeByName(typeName) is Type t)
searchType = Il2CppSystem.Type.GetType(findType.AssemblyQualifiedName); {
//MelonLogger.Log("Search type: " + findType.AssemblyQualifiedName); searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
}
else
{
throw new Exception($"Could not find a Type by the name of '{typeName}'!");
}
} }
catch (Exception e) catch (Exception e)
{ {
MelonLogger.Log("Exception: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace); MelonLogger.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
} }
} }
else if (TypeMode == TypeFilter.Object) else if (TypeMode == TypeFilter.Object)
@ -298,7 +290,10 @@ namespace Explorer
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType)) if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
{ {
MelonLogger.LogError("Your Custom Class Type must inherit from UnityEngine.Object!"); if (searchType != null)
{
MelonLogger.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
}
return new List<object>(); return new List<object>();
} }
@ -313,12 +308,13 @@ namespace Explorer
{ {
if (i >= 2000) break; if (i >= 2000) break;
if (_search != "" && !obj.name.ToLower().Contains(_search.ToLower())) if (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower()))
{ {
continue; continue;
} }
if (searchType == ReflectionHelpers.ComponentType && ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType())) if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
{ {
// Transforms shouldn't really be counted as Components, skip them. // Transforms shouldn't really be counted as Components, skip them.
// They're more akin to GameObjects. // They're more akin to GameObjects.
@ -376,43 +372,70 @@ namespace Explorer
// ====== other ======== // ====== other ========
private static bool FilterName(string name)
{
// Don't really want these instances.
return !name.StartsWith("Mono")
&& !name.StartsWith("System")
&& !name.StartsWith("Il2CppSystem")
&& !name.StartsWith("Iced");
}
// credit: ManlyMarco (RuntimeUnityEditor) // credit: ManlyMarco (RuntimeUnityEditor)
public static IEnumerable<object> GetInstanceClassScanner() public static IEnumerable<object> GetInstanceClassScanner()
{ {
var query = AppDomain.CurrentDomain.GetAssemblies() var query = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => !x.FullName.StartsWith("Mono")) .SelectMany(ReflectionHelpers.GetTypesSafe)
.SelectMany(GetTypesSafe)
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters); .Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
var flags = BindingFlags.Public | BindingFlags.Static;
var flatFlags = flags | BindingFlags.FlattenHierarchy;
foreach (var type in query) foreach (var type in query)
{ {
object obj = null; object obj = null;
try try
{ {
obj = type.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null, null); var pi = type.GetProperty("Instance", flags);
if (pi == null)
{
pi = type.GetProperty("Instance", flatFlags);
} }
catch
if (pi != null)
{ {
try obj = pi.GetValue(null);
{
obj = type.GetField("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)?.GetValue(null);
} }
catch else
{ {
var fi = type.GetField("Instance", flags);
if (fi == null)
{
fi = type.GetField("Instance", flatFlags);
}
if (fi != null)
{
obj = fi.GetValue(null);
} }
} }
if (obj != null && !obj.ToString().StartsWith("Mono")) }
catch { }
if (obj != null)
{ {
var t = ReflectionHelpers.GetActualType(obj);
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t) || ReflectionHelpers.IsCppList(t))
{
continue;
}
yield return obj; yield return obj;
} }
} }
} }
public static IEnumerable<Type> GetTypesSafe(Assembly asm)
{
try { return asm.GetTypes(); }
catch (ReflectionTypeLoadException e) { return e.Types.Where(x => x != null); }
catch { return Enumerable.Empty<Type>(); }
}
} }
} }

View File

@ -9,7 +9,7 @@ namespace Explorer
{ {
public abstract class WindowPage public abstract class WindowPage
{ {
public virtual string Name { get; set; } public virtual string Name { get; }
public Vector2 scroll = Vector2.zero; public Vector2 scroll = Vector2.zero;

View File

@ -13,13 +13,6 @@ using Harmony;
namespace Explorer namespace Explorer
{ {
// This is a copy+paste of UnityEngine source code, fixed for Il2Cpp.
// Taken from dnSpy output using Unity 2018.4.20.
// Subject to Unity's License and ToS.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public class GUIUnstrip public class GUIUnstrip
{ {
public static int s_ScrollControlId; public static int s_ScrollControlId;
@ -31,19 +24,26 @@ namespace Explorer
{ {
get get
{ {
#if Release_2019 if (m_scrollViewStatesInfo == null)
return GUI.scrollViewStates; {
#else try
return GUI.s_ScrollViewStates; {
#endif m_scrollViewStatesInfo = typeof(GUI).GetProperty("scrollViewStates");
if (m_scrollViewStatesInfo == null) throw new Exception();
}
catch
{
m_scrollViewStatesInfo = typeof(GUI).GetProperty("s_scrollViewStates");
} }
} }
return (GenericStack)m_scrollViewStatesInfo?.GetValue(null, null);
}
}
private static PropertyInfo m_scrollViewStatesInfo;
// ======= public methods ======= // // ======= public methods ======= //
// Fix for GUILayoutUtility.GetLastRect().
// Calls UnstripExtensions.GetLastUnstripped().
public static Rect GetLastRect() public static Rect GetLastRect()
{ {
EventType type = Event.current.type; EventType type = Event.current.type;
@ -59,28 +59,7 @@ namespace Explorer
return last; return last;
} }
// Simple unstrips for HorizontalScrollbar and VerticalScrollbar, they just call the Scroller unstrip.
public static float HorizontalScrollbar(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, leftValue, rightValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "leftbutton"),
GUI.skin.GetStyle(style.name + "rightbutton"),
true);
}
public static float VerticalScrollbar(Rect position, float value, float size, float topValue, float bottomValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, topValue, bottomValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "upbutton"),
GUI.skin.GetStyle(style.name + "downbutton"),
false);
}
// Fix for BeginScrollView. // Fix for BeginScrollView.
// Uses several manually unstripped methods.
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options) public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
{ {
@ -98,7 +77,7 @@ namespace Explorer
} }
} }
// Try manual unstripping implementation. // Try manual implementation.
if (!ManualUnstripFailed) if (!ManualUnstripFailed)
{ {
try try
@ -107,7 +86,7 @@ namespace Explorer
} }
catch (Exception e) catch (Exception e)
{ {
MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_Impl: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace); MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_ImplLayout: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
ManualUnstripFailed = true; ManualUnstripFailed = true;
return scroll; return scroll;
@ -136,8 +115,6 @@ namespace Explorer
// ======= private methods ======= // // ======= private methods ======= //
// Actual unstrip of GUILayout.BeginScrollView()
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options) GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
{ {
@ -171,8 +148,6 @@ namespace Explorer
); );
} }
// Actual unstrip of GUI.BeginScrollView()
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal, private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background) bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
{ {
@ -204,7 +179,7 @@ namespace Explorer
ScrollStack.Push(scrollViewState); ScrollStack.Push(scrollViewState);
Rect screenRect = new Rect(position); Rect screenRect = new Rect(position.x, position.y, position.width, position.height);
EventType type = Event.current.type; EventType type = Event.current.type;
if (type != EventType.Layout) if (type != EventType.Layout)
{ {
@ -238,7 +213,7 @@ namespace Explorer
} }
if (flag2 && horizontalScrollbar != GUIStyle.none) if (flag2 && horizontalScrollbar != GUIStyle.none)
{ {
scrollPosition.x = HorizontalScrollbar( scrollPosition.x = HorizBar_Impl(
new Rect( new Rect(
position.x, position.x,
position.yMax - horizontalScrollbar.fixedHeight, position.yMax - horizontalScrollbar.fixedHeight,
@ -260,7 +235,7 @@ namespace Explorer
} }
if (flag && verticalScrollbar != GUIStyle.none) if (flag && verticalScrollbar != GUIStyle.none)
{ {
scrollPosition.y = VerticalScrollbar( scrollPosition.y = VertBar_Impl(
new Rect( new Rect(
screenRect.xMax + (float)verticalScrollbar.margin.left, screenRect.xMax + (float)verticalScrollbar.margin.left,
screenRect.y, screenRect.y,
@ -296,7 +271,23 @@ namespace Explorer
return scrollPosition; return scrollPosition;
} }
// Actual unstrip of GUI.EndScrollView() public static float HorizBar_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, leftValue, rightValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "leftbutton"),
GUI.skin.GetStyle(style.name + "rightbutton"),
true);
}
public static float VertBar_Impl(Rect position, float value, float size, float topValue, float bottomValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, topValue, bottomValue, style,
GUI.skin.GetStyle(style.name + "thumb"),
GUI.skin.GetStyle(style.name + "upbutton"),
GUI.skin.GetStyle(style.name + "downbutton"),
false);
}
private static void EndScrollView_Impl(bool handleScrollWheel) private static void EndScrollView_Impl(bool handleScrollWheel)
{ {
@ -336,8 +327,6 @@ namespace Explorer
} }
} }
// Actual unstrip of GUI.Scroller
private static float Scroller_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz) private static float Scroller_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
{ {
GUIUtility.CheckOnGUI(); GUIUtility.CheckOnGUI();
@ -358,7 +347,7 @@ namespace Explorer
rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight); rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight);
} }
value = Slider(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID); value = Slider_Impl(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
bool flag = Event.current.type == EventType.MouseUp; bool flag = Event.current.type == EventType.MouseUp;
if (ScrollerRepeatButton_Impl(controlID, rect, leftButton)) if (ScrollerRepeatButton_Impl(controlID, rect, leftButton))
@ -384,9 +373,7 @@ namespace Explorer
return value; return value;
} }
// Actual unstrip of GUI.Slider public static float Slider_Impl(Rect position, float value, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
public static float Slider(Rect position, float value, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
{ {
if (id == 0) if (id == 0)
{ {
@ -396,8 +383,6 @@ namespace Explorer
return sliderHandler.Handle(); return sliderHandler.Handle();
} }
// Actual unstrip of GUI.ScrollerRepeatButton
private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style) private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style)
{ {
bool result = false; bool result = false;

View File

@ -9,13 +9,6 @@ using UnityEngine;
namespace Explorer namespace Explorer
{ {
// This is a copy+paste of UnityEngine source code, fixed for Il2Cpp.
// Taken from dnSpy output using Unity 2018.4.20.
// Subject to Unity's License and ToS.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public class ScrollViewStateUnstrip public class ScrollViewStateUnstrip
{ {
public Rect position; public Rect position;
@ -24,9 +17,6 @@ namespace Explorer
public Vector2 scrollPosition; public Vector2 scrollPosition;
public bool apply; public bool apply;
// The code below is not unstripped.
// This is a custom dictionary to allow for the manual implementation.
public static Dictionary<IntPtr, ScrollViewStateUnstrip> Dict = new Dictionary<IntPtr, ScrollViewStateUnstrip>(); public static Dictionary<IntPtr, ScrollViewStateUnstrip> Dict = new Dictionary<IntPtr, ScrollViewStateUnstrip>();
public static ScrollViewStateUnstrip FromPointer(IntPtr ptr) public static ScrollViewStateUnstrip FromPointer(IntPtr ptr)

View File

@ -8,13 +8,6 @@ using UnhollowerRuntimeLib;
namespace Explorer namespace Explorer
{ {
// This is a copy+paste of UnityEngine source code, fixed for Il2Cpp.
// Taken from dnSpy output using Unity 2018.4.20.
// Subject to Unity's License and ToS.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public struct SliderHandlerUnstrip public struct SliderHandlerUnstrip
{ {
private readonly Rect position; private readonly Rect position;

View File

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

View File

@ -1,51 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
// This is a copy+paste of UnityEngine source code, fixed for Il2Cpp.
// Taken from dnSpy output using Unity 2018.4.20.
// Subject to Unity's License and ToS.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public static class UnstripExtensions
{
// This is a manual unstrip of GUILayoutGroup.GetLast().
// I'm using it as an Extension because it's easier this way.
public static Rect GetLastUnstripped(this GUILayoutGroup group)
{
Rect result;
if (group.m_Cursor == 0)
{
Debug.LogError("You cannot call GetLast immediately after beginning a group.");
result = GUILayoutEntry.kDummyRect;
}
else if (group.m_Cursor <= group.entries.Count)
{
GUILayoutEntry guilayoutEntry = group.entries[group.m_Cursor - 1];
result = guilayoutEntry.rect;
}
else
{
Debug.LogError(string.Concat(new object[]
{
"Getting control ",
group.m_Cursor,
"'s position in a group with only ",
group.entries.Count,
" controls when doing ",
Event.current.type
}));
result = GUILayoutEntry.kDummyRect;
}
return result;
}
}
}

View File

@ -29,9 +29,17 @@ namespace Explorer
private Vector2 m_compScroll = Vector2.zero; private Vector2 m_compScroll = Vector2.zero;
private PageHelper CompPages = new PageHelper(); private PageHelper CompPages = new PageHelper();
private readonly Vector3[] m_cachedInput = new Vector3[3];
private float m_translateAmount = 0.3f; private float m_translateAmount = 0.3f;
private float m_rotateAmount = 50f; private float m_rotateAmount = 50f;
private float m_scaleAmount = 0.1f; private float m_scaleAmount = 0.1f;
private bool m_freeze;
private Vector3 m_frozenPosition;
private Quaternion m_frozenRotation;
private Vector3 m_frozenScale;
private bool m_autoApplyTransform;
private bool m_autoUpdateTransform;
private bool m_localContext;
private readonly List<Component> m_cachedDestroyList = new List<Component>(); private readonly List<Component> m_cachedDestroyList = new List<Component>();
//private string m_addComponentInput = ""; //private string m_addComponentInput = "";
@ -73,12 +81,29 @@ namespace Explorer
m_name = m_object.name; m_name = m_object.name;
m_scene = string.IsNullOrEmpty(m_object.scene.name) m_scene = string.IsNullOrEmpty(m_object.scene.name)
? "None" ? "None (Asset/Resource)"
: m_object.scene.name; : m_object.scene.name;
CacheTransformValues();
Update(); Update();
} }
private void CacheTransformValues()
{
if (m_localContext)
{
m_cachedInput[0] = m_object.transform.localPosition;
m_cachedInput[1] = m_object.transform.localEulerAngles;
}
else
{
m_cachedInput[0] = m_object.transform.position;
m_cachedInput[1] = m_object.transform.eulerAngles;
}
m_cachedInput[2] = m_object.transform.localScale;
}
public override void Update() public override void Update()
{ {
try try
@ -88,28 +113,37 @@ namespace Explorer
throw new Exception("Object is null!"); throw new Exception("Object is null!");
} }
var list = new List<Transform>(); if (m_freeze)
{
if (m_localContext)
{
m_object.transform.localPosition = m_frozenPosition;
m_object.transform.localRotation = m_frozenRotation;
}
else
{
m_object.transform.position = m_frozenPosition;
m_object.transform.rotation = m_frozenRotation;
}
m_object.transform.localScale = m_frozenScale;
}
// update child objects
var childList = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++) for (int i = 0; i < m_object.transform.childCount; i++)
{ {
list.Add(m_object.transform.GetChild(i)); childList.Add(m_object.transform.GetChild(i));
} }
list.Sort((a, b) => b.childCount.CompareTo(a.childCount)); childList.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_children = list.ToArray(); m_children = childList.ToArray();
ChildPages.ItemCount = m_children.Length; ChildPages.ItemCount = m_children.Length;
var list2 = new List<Component>(); // update components
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType)) var compList = new Il2CppSystem.Collections.Generic.List<Component>();
{ m_object.GetComponentsInternal(ReflectionHelpers.ComponentType, true, false, true, false, compList);
var ilType = comp.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
list2.Add(comp); m_components = compList.ToArray();
}
m_components = list2.ToArray();
CompPages.ItemCount = m_components.Length; CompPages.ItemCount = m_components.Length;
} }
@ -412,6 +446,16 @@ namespace Explorer
m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset; m_object.hideFlags |= HideFlags.DontUnloadUnusedAsset;
} }
var lbl = m_freeze ? "<color=lime>Unfreeze</color>" : "<color=orange>Freeze Pos/Rot</color>";
if (GUILayout.Button(lbl, new GUILayoutOption[] { GUILayout.Width(110) }))
{
m_freeze = !m_freeze;
if (m_freeze)
{
UpdateFreeze();
}
}
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -436,10 +480,52 @@ namespace Explorer
GUILayout.BeginVertical(GUI.skin.box, null); GUILayout.BeginVertical(GUI.skin.box, null);
var t = m_object.transform; m_cachedInput[0] = TranslateControl(TranslateType.Position, ref m_translateAmount, false);
TranslateControl(t, TranslateType.Position, ref m_translateAmount, false); m_cachedInput[1] = TranslateControl(TranslateType.Rotation, ref m_rotateAmount, true);
TranslateControl(t, TranslateType.Rotation, ref m_rotateAmount, true); m_cachedInput[2] = TranslateControl(TranslateType.Scale, ref m_scaleAmount, false);
TranslateControl(t, TranslateType.Scale, ref m_scaleAmount, false);
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("<color=lime>Apply to Transform</color>", null) || m_autoApplyTransform)
{
if (m_localContext)
{
m_object.transform.localPosition = m_cachedInput[0];
m_object.transform.localEulerAngles = m_cachedInput[1];
}
else
{
m_object.transform.position = m_cachedInput[0];
m_object.transform.eulerAngles = m_cachedInput[1];
}
m_object.transform.localScale = m_cachedInput[2];
if (m_freeze)
{
UpdateFreeze();
}
}
if (GUILayout.Button("<color=lime>Update from Transform</color>", null) || m_autoUpdateTransform)
{
CacheTransformValues();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
BoolToggle(ref m_autoApplyTransform, "Auto-apply to Transform?");
BoolToggle(ref m_autoUpdateTransform, "Auto-update from transform?");
GUILayout.EndHorizontal();
bool b = m_localContext;
b = GUILayout.Toggle(b, "<color=" + (b ? "lime" : "red") + ">Use local transform values?</color>", null);
if (b != m_localContext)
{
m_localContext = b;
CacheTransformValues();
if (m_freeze)
{
UpdateFreeze();
}
}
GUILayout.EndVertical(); GUILayout.EndVertical();
@ -453,6 +539,30 @@ namespace Explorer
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
private void UpdateFreeze()
{
if (m_localContext)
{
m_frozenPosition = m_object.transform.localPosition;
m_frozenRotation = m_object.transform.localRotation;
}
else
{
m_frozenPosition = m_object.transform.position;
m_frozenRotation = m_object.transform.rotation;
}
m_frozenScale = m_object.transform.localScale;
}
private void BoolToggle(ref bool value, string message)
{
string lbl = "<color=";
lbl += value ? "lime" : "red";
lbl += $">{message}</color>";
value = GUILayout.Toggle(value, lbl, null);
}
public enum TranslateType public enum TranslateType
{ {
Position, Position,
@ -460,50 +570,55 @@ namespace Explorer
Scale Scale
} }
private void TranslateControl(Transform transform, TranslateType mode, ref float amount, bool multByTime) private Vector3 TranslateControl(TranslateType mode, ref float amount, bool multByTime)
{ {
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<color=cyan><b>" + mode + "</b></color>:", new GUILayoutOption[] { GUILayout.Width(65) }); GUILayout.Label($"<color=cyan><b>{(m_localContext ? "Local " : "")}{mode}</b></color>:",
new GUILayoutOption[] { GUILayout.Width(m_localContext ? 110 : 65) });
Vector3 vector = Vector3.zero; var transform = m_object.transform;
switch (mode) switch (mode)
{ {
case TranslateType.Position: vector = transform.localPosition; break; case TranslateType.Position:
case TranslateType.Rotation: vector = transform.localRotation.eulerAngles; break; var pos = m_localContext ? transform.localPosition : transform.position;
case TranslateType.Scale: vector = transform.localScale; break; GUILayout.Label(pos.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
break;
case TranslateType.Rotation:
var rot = m_localContext ? transform.localEulerAngles : transform.eulerAngles;
GUILayout.Label(rot.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
break;
case TranslateType.Scale:
GUILayout.Label(transform.localScale.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
break;
} }
GUILayout.Label(vector.ToString(), new GUILayoutOption[] { GUILayout.Width(250) });
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
Vector3 input = m_cachedInput[(int)mode];
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUI.skin.label.alignment = TextAnchor.MiddleRight; GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) }); GUILayout.Label("<color=cyan>X:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
PlusMinusFloat(ref vector.x, amount, multByTime); PlusMinusFloat(ref input.x, amount, multByTime);
GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) }); GUILayout.Label("<color=cyan>Y:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
PlusMinusFloat(ref vector.y, amount, multByTime); PlusMinusFloat(ref input.y, amount, multByTime);
GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) }); GUILayout.Label("<color=cyan>Z:</color>", new GUILayoutOption[] { GUILayout.Width(20) });
PlusMinusFloat(ref vector.z, amount, multByTime); PlusMinusFloat(ref input.z, amount, multByTime);
switch (mode)
{
case TranslateType.Position: transform.localPosition = vector; break;
case TranslateType.Rotation: transform.localRotation = Quaternion.Euler(vector); break;
case TranslateType.Scale: transform.localScale = vector; break;
}
GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) }); GUILayout.Label("+/-:", new GUILayoutOption[] { GUILayout.Width(30) });
var input = amount.ToString("F3"); var amountInput = amount.ToString("F3");
input = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.Width(40) }); amountInput = GUILayout.TextField(amountInput, new GUILayoutOption[] { GUILayout.Width(60) });
if (float.TryParse(input, out float f)) if (float.TryParse(amountInput, out float f))
{ {
amount = f; amount = f;
} }
GUI.skin.label.alignment = TextAnchor.UpperLeft; GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
return input;
} }
private void PlusMinusFloat(ref float f, float amount, bool multByTime) private void PlusMinusFloat(ref float f, float amount, bool multByTime)
@ -523,7 +638,5 @@ namespace Explorer
f += multByTime ? amount * Time.deltaTime : amount; f += multByTime ? amount * Time.deltaTime : amount;
} }
} }
} }
} }

View File

@ -26,6 +26,7 @@ namespace Explorer
} }
public override void Init() { } public override void Init() { }
public override void Update() public override void Update()
{ {
while (TargetTabID >= WindowManager.Windows.Count) while (TargetTabID >= WindowManager.Windows.Count)

View File

@ -109,7 +109,7 @@ namespace Explorer
if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget) if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
{ {
if (iCurrent.GetIl2CppType() != iTarget.GetIl2CppType()) if (iCurrent.GetIl2CppType().FullName != iTarget.GetIl2CppType().FullName)
{ {
if (iCurrent is Transform transform) if (iCurrent is Transform transform)
{ {