Compare commits

...

7 Commits
1.5.0 ... 1.5.4

Author SHA1 Message Date
6adaaf5500 1.5.4
* Implemented manual unstripping for ScrollView and Resize, should now work on any Unity 2018 or 2019 game.
* Fixed a bug with page view on the Scene Explorer
* Back-end cleanups
2020-09-04 21:36:40 +10:00
5de771389e 1.5.3
* Added exception handling for scrollview when unstripping fails
* Added some better logging in some places
2020-09-04 01:27:44 +10:00
51cfbe524e Update README.md 2020-09-03 20:59:54 +10:00
217b93ef4f 1.5.2
* Added ability to force Reflection Inspector for GameObjects and Transforms if you hold Left Shift while clicking the Inspect button
* Fixed a bug causing duplicate windows to open when you inspect Transforms, the current active window will now be focused. Note: does not apply if you hold Left Shift for forced reflection.
2020-09-03 20:58:04 +10:00
42156e1160 1.5.2
* Added page view to GameObject Children/Component lists
* Made a generic Page Handler helper class, replaced all page view implementations with the helper (no real change for users but should make things easier to maintain in the future, and they were basically all copy+pastes).
2020-09-03 19:48:50 +10:00
e7208d0c9d 1.5.1 2020-09-01 18:04:38 +10:00
2f3b779199 1.5.1
* Added support for Properties with an index parameter on the Reflection Window (ie. "this[index]")
* Fixed a crash that occured when inspecting Il2CppSystem.Type objects
* Back-end cleanups
2020-09-01 18:03:44 +10:00
26 changed files with 1621 additions and 494 deletions

View File

@ -51,7 +51,9 @@ Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be ins
CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>. CppExplorer has two main inspector modes: <b>GameObject Inspector</b>, and <b>Reflection Inspector</b>.
<b>Tip:</b> when in Tab View, GameObjects are denoted by a [G] prefix, and Reflection objects are denoted by a [R] prefix. <b>Tips:</b>
* When in Tab View, GameObjects are denoted by a [G] prefix, and Reflection objects are denoted by a [R] prefix.
* Hold <b>Left Shift</b> when you click the Inspect button to force Reflection mode for GameObjects and Transforms.
### GameObject Inspector ### GameObject Inspector

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Reflection;
using MelonLoader; using MelonLoader;
using UnityEngine; using UnityEngine;
@ -15,8 +16,23 @@ namespace Explorer
public override void Init() public override void Init()
{ {
EnumType = Value.GetType(); try
EnumNames = Enum.GetNames(EnumType); {
EnumType = Value.GetType();
}
catch
{
EnumType = (MemInfo as FieldInfo)?.FieldType ?? (MemInfo as PropertyInfo).PropertyType;
}
if (EnumType != null)
{
EnumNames = Enum.GetNames(EnumType);
}
else
{
ReflectionException = "Unknown, could not get Enum names.";
}
} }
public override void DrawValue(Rect window, float width) public override void DrawValue(Rect window, float width)

View File

@ -10,32 +10,9 @@ namespace Explorer
{ {
public class CacheGameObject : CacheObjectBase public class CacheGameObject : CacheObjectBase
{ {
private GameObject GameObj
{
get
{
if (m_gameObject == null)
{
if (Value is Il2CppSystem.Object ilObj)
{
var ilType = ilObj.GetIl2CppType();
if (ilType == ReflectionHelpers.GameObjectType || ilType == ReflectionHelpers.TransformType)
{
m_gameObject = ilObj.TryCast<GameObject>() ?? ilObj.TryCast<Transform>()?.gameObject;
}
}
}
return m_gameObject;
}
}
private GameObject m_gameObject;
public override void DrawValue(Rect window, float width) public override void DrawValue(Rect window, float width)
{ {
UIHelpers.GameobjButton(GameObj, null, false, width); UIHelpers.GOButton(Value, null, false, width);
} }
public override void UpdateValue() public override void UpdateValue()

View File

@ -11,8 +11,7 @@ namespace Explorer
public class CacheList : CacheObjectBase public class CacheList : CacheObjectBase
{ {
public bool IsExpanded { get; set; } public bool IsExpanded { get; set; }
public int ArrayOffset { get; set; } public PageHelper Pages = new PageHelper();
public int ArrayLimit { get; set; } = 20;
public float WhiteSpace = 215f; public float WhiteSpace = 215f;
public float ButtonWidthOffset = 290f; public float ButtonWidthOffset = 290f;
@ -145,16 +144,16 @@ namespace Explorer
{ {
if (m_entryType == null) if (m_entryType == null)
{ {
if (this.MemberInfo != null) if (this.MemInfo != null)
{ {
Type memberType = null; Type memberType = null;
switch (this.MemberInfo.MemberType) switch (this.MemInfo.MemberType)
{ {
case MemberTypes.Field: case MemberTypes.Field:
memberType = (MemberInfo as FieldInfo).FieldType; memberType = (MemInfo as FieldInfo).FieldType;
break; break;
case MemberTypes.Property: case MemberTypes.Property:
memberType = (MemberInfo as PropertyInfo).PropertyType; memberType = (MemInfo as PropertyInfo).PropertyType;
break; break;
} }
@ -201,17 +200,35 @@ namespace Explorer
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
var obj = enumerator.Current; var obj = enumerator.Current;
var type = ReflectionHelpers.GetActualType(obj);
if (obj is Il2CppSystem.Object iObj) if (obj != null && ReflectionHelpers.GetActualType(obj) is Type t)
{ {
obj = iObj.Il2CppCast(type); if (obj is Il2CppSystem.Object iObj)
{
try
{
var cast = iObj.Il2CppCast(t);
if (cast != null)
{
obj = cast;
}
}
catch { }
}
if (GetCacheObject(obj, t) is CacheObjectBase cached)
{
list.Add(cached);
}
else
{
list.Add(null);
}
}
else
{
list.Add(null);
} }
var cached = GetCacheObject(obj, null, null, type);
cached.UpdateValue();
list.Add(cached);
} }
m_cachedEntries = list.ToArray(); m_cachedEntries = list.ToArray();
@ -256,51 +273,47 @@ namespace Explorer
if (IsExpanded) if (IsExpanded)
{ {
float whitespace = WhiteSpace; float whitespace = WhiteSpace;
if (whitespace > 0) if (whitespace > 0)
{ {
ClampLabelWidth(window, ref whitespace); ClampLabelWidth(window, ref whitespace);
} }
if (count > ArrayLimit) Pages.ItemCount = count;
if (count > Pages.ItemsPerPage)
{ {
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Space(whitespace); GUILayout.Space(whitespace);
Pages.CurrentPageLabel();
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)ArrayLimit)) - 1;
GUILayout.Label($"Page {ArrayOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
// prev/next page buttons // prev/next page buttons
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) })) if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(60) }))
{ {
if (ArrayOffset > 0) ArrayOffset--; Pages.TurnPage(Turn.Left);
} }
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) })) if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(60) }))
{ {
if (ArrayOffset < maxOffset) ArrayOffset++; Pages.TurnPage(Turn.Right);
}
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.ArrayLimit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != ArrayLimit.ToString() && int.TryParse(limit, out int i))
{
ArrayLimit = i;
} }
Pages.DrawLimitInputArea();
GUILayout.Space(5); GUILayout.Space(5);
} }
int offset = ArrayOffset * ArrayLimit; //int offset = ArrayOffset * ArrayLimit;
//if (offset >= count)
//{
// offset = 0;
// ArrayOffset = 0;
//}
int offset = Pages.CalculateOffsetIndex();
if (offset >= count) for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
{
offset = 0;
ArrayOffset = 0;
}
for (int i = offset; i < offset + ArrayLimit && i < count; i++)
{ {
var entry = m_cachedEntries[i]; var entry = m_cachedEntries[i];
@ -310,16 +323,18 @@ namespace Explorer
GUILayout.Space(whitespace); GUILayout.Space(whitespace);
if (entry.Value == null) if (entry == null || entry.Value == null)
{ {
GUILayout.Label(i + "<i><color=grey> (null)</color></i>", null); GUILayout.Label($"[{i}] <i><color=grey>(null)</color></i>", null);
} }
else else
{ {
GUI.skin.label.alignment = TextAnchor.MiddleCenter; GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) }); GUILayout.Label($"[{i}]", new GUILayoutOption[] { GUILayout.Width(30) });
entry.DrawValue(window, window.width - (whitespace + 85));
entry.DrawValue(window, window.width - (whitespace + 85));
} }
} }
GUI.skin.label.alignment = TextAnchor.UpperLeft; GUI.skin.label.alignment = TextAnchor.UpperLeft;

View File

@ -24,7 +24,7 @@ namespace Explorer
{ {
if (m_hasParams == null) if (m_hasParams == null)
{ {
m_hasParams = (MemberInfo as MethodInfo).GetParameters().Length > 0; m_hasParams = (MemInfo as MethodInfo).GetParameters().Length > 0;
} }
return (bool)m_hasParams; return (bool)m_hasParams;
} }
@ -55,7 +55,7 @@ namespace Explorer
{ {
base.Init(); base.Init();
var mi = MemberInfo as MethodInfo; var mi = MemInfo as MethodInfo;
m_arguments = mi.GetParameters(); m_arguments = mi.GetParameters();
m_argumentInput = new string[m_arguments.Length]; m_argumentInput = new string[m_arguments.Length];
@ -136,12 +136,12 @@ namespace Explorer
} }
else else
{ {
GUILayout.Label($"null (<color=yellow>{ValueType}</color>)", null); GUILayout.Label($"null (<color=yellow>{ValueTypeName}</color>)", null);
} }
} }
else else
{ {
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueType}</color>)", null); GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> (<color=yellow>{ValueTypeName}</color>)", null);
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
@ -150,7 +150,7 @@ namespace Explorer
private void Evaluate() private void Evaluate()
{ {
var mi = MemberInfo as MethodInfo; var mi = MemInfo as MethodInfo;
object ret = null; object ret = null;

View File

@ -13,67 +13,70 @@ namespace Explorer
public abstract class CacheObjectBase public abstract class CacheObjectBase
{ {
public object Value; public object Value;
public string ValueType; public string ValueTypeName;
// Reflection window only // Reflection Inspector only
public MemberInfo MemberInfo { get; set; } public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; } public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; } public object DeclaringInstance { get; set; }
public string ReflectionException { get; set; }
public string RichTextName
{
get
{
if (m_richTextName == null)
{
GetRichTextName();
}
return m_richTextName;
}
}
private string m_richTextName;
public string ReflectionException; public int PropertyIndex { get; private set; }
private string m_propertyIndexInput = "0";
public string RichTextName => m_richTextName ?? GetRichTextName();
private string m_richTextName;
public bool CanWrite public bool CanWrite
{ {
get get
{ {
if (MemberInfo is FieldInfo fi) if (MemInfo is FieldInfo fi)
{
return !(fi.IsLiteral && !fi.IsInitOnly); return !(fi.IsLiteral && !fi.IsInitOnly);
} else if (MemInfo is PropertyInfo pi)
else if (MemberInfo is PropertyInfo pi)
{
return pi.CanWrite; return pi.CanWrite;
}
else else
{
return false; return false;
}
} }
} }
// methods // ===== Abstract/Virtual Methods ===== //
public virtual void Init() { } public virtual void Init() { }
public abstract void DrawValue(Rect window, float width); public abstract void DrawValue(Rect window, float width);
// ===== Static Methods ===== //
/// <summary>
/// Get CacheObject from only an object instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (obj, null, null)</summary>
public static CacheObjectBase GetCacheObject(object obj) public static CacheObjectBase GetCacheObject(object obj)
{ {
return GetCacheObject(obj, null, null); return GetCacheObject(obj, null, null);
} }
/// <summary> /// <summary>
/// Gets the CacheObject subclass for an object or MemberInfo /// Get CacheObject from an object instance and provide the value type
/// </summary> /// Calls GetCacheObjectImpl directly</summary>
/// <param name="obj">The current value (can be null if memberInfo is not null)</param> public static CacheObjectBase GetCacheObject(object obj, Type valueType)
/// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param> {
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param> return GetCacheObjectImpl(obj, null, null, valueType);
/// <returns></returns> }
/// <summary>
/// Get CacheObject from only a MemberInfo and declaring instance
/// Calls GetCacheObject(obj, memberInfo, declaringInstance) with (null, memberInfo, declaringInstance)</summary>
public static CacheObjectBase GetCacheObject(MemberInfo memberInfo, object declaringInstance)
{
return GetCacheObject(null, memberInfo, declaringInstance);
}
/// <summary>
/// Get CacheObject from either an object or MemberInfo, and don't provide the type.
/// This gets the type and then calls GetCacheObjectImpl</summary>
public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance) public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance)
{ {
//var type = ReflectionHelpers.GetActualType(obj) ?? (memberInfo as FieldInfo)?.FieldType ?? (memberInfo as PropertyInfo)?.PropertyType;
Type type = null; Type type = null;
if (obj != null) if (obj != null)
@ -101,18 +104,13 @@ namespace Explorer
return null; return null;
} }
return GetCacheObject(obj, memberInfo, declaringInstance, type); return GetCacheObjectImpl(obj, memberInfo, declaringInstance, type);
} }
/// <summary> /// <summary>
/// Gets the CacheObject subclass for an object or MemberInfo /// Actual GetCacheObject implementation (private)
/// </summary> /// </summary>
/// <param name="obj">The current value (can be null if memberInfo is not null)</param> private static CacheObjectBase GetCacheObjectImpl(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
/// <param name="memberInfo">The MemberInfo (can be null if obj is not null)</param>
/// <param name="declaringInstance">If MemberInfo is not null, the declaring class instance. Can be null if static.</param>
/// <param name="valueType">The type of the object or MemberInfo value.</param>
/// <returns></returns>
public static CacheObjectBase GetCacheObject(object obj, MemberInfo memberInfo, object declaringInstance, Type valueType)
{ {
CacheObjectBase holder; CacheObjectBase holder;
@ -153,11 +151,11 @@ namespace Explorer
} }
holder.Value = obj; holder.Value = obj;
holder.ValueType = valueType.FullName; holder.ValueTypeName = valueType.FullName;
if (memberInfo != null) if (memberInfo != null)
{ {
holder.MemberInfo = memberInfo; holder.MemInfo = memberInfo;
holder.DeclaringType = memberInfo.DeclaringType; holder.DeclaringType = memberInfo.DeclaringType;
holder.DeclaringInstance = declaringInstance; holder.DeclaringInstance = declaringInstance;
@ -169,30 +167,40 @@ namespace Explorer
return holder; return holder;
} }
// ======== Updating and Setting Value (memberinfo only) ========= // ======== Instance Methods =========
public virtual void UpdateValue() public virtual void UpdateValue()
{ {
if (MemberInfo == null || !string.IsNullOrEmpty(ReflectionException)) if (MemInfo == null || !string.IsNullOrEmpty(ReflectionException))
{ {
return; return;
} }
try try
{ {
if (MemberInfo.MemberType == MemberTypes.Field) if (MemInfo.MemberType == MemberTypes.Field)
{ {
var fi = MemberInfo as FieldInfo; var fi = MemInfo as FieldInfo;
Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance); Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
} }
else if (MemberInfo.MemberType == MemberTypes.Property) else if (MemInfo.MemberType == MemberTypes.Property)
{ {
var pi = MemberInfo as PropertyInfo; var pi = MemInfo as PropertyInfo;
bool isStatic = pi.GetAccessors()[0].IsStatic; bool isStatic = pi.GetAccessors()[0].IsStatic;
var target = isStatic ? null : DeclaringInstance; var target = isStatic ? null : DeclaringInstance;
Value = pi.GetValue(target, null);
if (pi.GetIndexParameters().Length > 0)
{
var indexes = new object[] { PropertyIndex };
Value = pi.GetValue(target, indexes);
}
else
{
Value = pi.GetValue(target, null);
}
} }
//ReflectionException = null;
ReflectionException = null;
} }
catch (Exception e) catch (Exception e)
{ {
@ -204,15 +212,24 @@ namespace Explorer
{ {
try try
{ {
if (MemberInfo.MemberType == MemberTypes.Field) if (MemInfo.MemberType == MemberTypes.Field)
{ {
var fi = MemberInfo as FieldInfo; var fi = MemInfo as FieldInfo;
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, Value); fi.SetValue(fi.IsStatic ? null : DeclaringInstance, Value);
} }
else if (MemberInfo.MemberType == MemberTypes.Property) else if (MemInfo.MemberType == MemberTypes.Property)
{ {
var pi = MemberInfo as PropertyInfo; var pi = MemInfo as PropertyInfo;
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
if (pi.GetIndexParameters().Length > 0)
{
var indexes = new object[] { PropertyIndex };
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value, indexes);
}
else
{
pi.SetValue(pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance, Value);
}
} }
} }
catch (Exception e) catch (Exception e)
@ -221,7 +238,7 @@ namespace Explorer
} }
} }
// ========= Gui Draw ========== // ========= Instance Gui Draw ==========
public const float MAX_LABEL_WIDTH = 400f; public const float MAX_LABEL_WIDTH = 400f;
@ -240,11 +257,15 @@ namespace Explorer
ClampLabelWidth(window, ref labelWidth); ClampLabelWidth(window, ref labelWidth);
} }
if (MemberInfo != null) 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 else
{ {
@ -255,20 +276,44 @@ namespace Explorer
{ {
GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null); GUILayout.Label("<color=red>Reflection failed!</color> (" + ReflectionException + ")", null);
} }
else if (Value == null && MemberInfo?.MemberType != MemberTypes.Method) else if (Value == null && MemInfo?.MemberType != MemberTypes.Method)
{ {
GUILayout.Label("<i>null (" + ValueType + ")</i>", null); GUILayout.Label("<i>null (" + ValueTypeName + ")</i>", null);
} }
else 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); DrawValue(window, window.width - labelWidth - 90);
} }
} }
private void GetRichTextName() private string GetRichTextName()
{ {
string memberColor = ""; string memberColor = "";
switch (MemberInfo.MemberType) switch (MemInfo.MemberType)
{ {
case MemberTypes.Field: case MemberTypes.Field:
memberColor = "#c266ff"; break; memberColor = "#c266ff"; break;
@ -278,9 +323,9 @@ namespace Explorer
memberColor = "#ff8000"; break; memberColor = "#ff8000"; break;
}; };
m_richTextName = $"<color=#2df7b2>{MemberInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemberInfo.Name}</color>"; m_richTextName = $"<color=#2df7b2>{MemInfo.DeclaringType.Name}</color>.<color={memberColor}>{MemInfo.Name}</color>";
if (MemberInfo is MethodInfo mi) if (MemInfo is MethodInfo mi)
{ {
m_richTextName += "("; m_richTextName += "(";
var _params = ""; var _params = "";
@ -293,6 +338,8 @@ namespace Explorer
m_richTextName += _params; m_richTextName += _params;
m_richTextName += ")"; m_richTextName += ")";
} }
return m_richTextName;
} }
} }
} }

View File

@ -12,33 +12,25 @@ namespace Explorer
public class CacheOther : CacheObjectBase public class CacheOther : CacheObjectBase
{ {
private MethodInfo m_toStringMethod; private MethodInfo m_toStringMethod;
private bool m_triedToGetMethod;
public MethodInfo ToStringMethod public MethodInfo ToStringMethod
{ {
get get
{ {
if (m_toStringMethod == null && !m_triedToGetMethod) if (m_toStringMethod == null)
{ {
if (Value == null) return null;
m_triedToGetMethod = true;
try try
{ {
var methods = ReflectionHelpers.GetActualType(Value) m_toStringMethod = ReflectionHelpers.GetActualType(Value).GetMethod("ToString", new Type[0])
.GetMethods(ReflectionHelpers.CommonFlags) ?? typeof(object).GetMethod("ToString", new Type[0]);
.Where(x => x.Name == "ToString")
.GetEnumerator();
while (methods.MoveNext()) // test invoke
{ m_toStringMethod.Invoke(Value, null);
// just get the first (top-most level) method, then break. }
m_toStringMethod = methods.Current; catch
break; {
} m_toStringMethod = typeof(object).GetMethod("ToString", new Type[0]);
} }
catch { }
} }
return m_toStringMethod; return m_toStringMethod;
} }
@ -48,9 +40,9 @@ namespace Explorer
{ {
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString(); string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
if (!label.Contains(ValueType)) if (!label.Contains(ValueTypeName))
{ {
label += $" ({ValueType})"; label += $" ({ValueTypeName})";
} }
if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name)) if (Value is UnityEngine.Object unityObj && !label.Contains(unityObj.name))
{ {
@ -58,7 +50,7 @@ namespace Explorer
} }
GUI.skin.button.alignment = TextAnchor.MiddleLeft; GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.MaxWidth(width + 40) })) if (GUILayout.Button("<color=yellow>" + label + "</color>", new GUILayoutOption[] { GUILayout.Width(width) }))
{ {
WindowManager.InspectObject(Value, out bool _); WindowManager.InspectObject(Value, out bool _);
} }

View File

@ -152,7 +152,7 @@ namespace Explorer
public void SetValueFromInput(string value) public void SetValueFromInput(string value)
{ {
if (MemberInfo == null) if (MemInfo == null)
{ {
MelonLogger.Log("Trying to SetValue but the MemberInfo is null!"); MelonLogger.Log("Trying to SetValue but the MemberInfo is null!");
return; return;

View File

@ -12,7 +12,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.0"; public const string VERSION = "1.5.4";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer" public const string NAME = "CppExplorer"
@ -99,6 +99,8 @@ namespace Explorer
public override void OnGUI() public override void OnGUI()
{ {
if (!ShowMenu) return;
MainMenu.Instance.OnGUI(); MainMenu.Instance.OnGUI();
WindowManager.Instance.OnGUI(); WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI(); InspectUnderMouse.OnGUI();
@ -133,7 +135,7 @@ namespace Explorer
// value that we set back to when we close the menu or disable force-unlock. // value that we set back to when we close the menu or disable force-unlock.
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)] [HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Setter)]
public class Cursor_lockState public class Cursor_set_lockState
{ {
[HarmonyPrefix] [HarmonyPrefix]
public static void Prefix(ref CursorLockMode value) public static void Prefix(ref CursorLockMode value)
@ -170,19 +172,6 @@ namespace Explorer
// Make it appear as though UnlockMouse is disabled to the rest of the application. // Make it appear as though UnlockMouse is disabled to the rest of the application.
[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;
}
}
}
[HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Getter)] [HarmonyPatch(typeof(Cursor), nameof(Cursor.lockState), MethodType.Getter)]
public class Cursor_get_lockState public class Cursor_get_lockState
{ {
@ -195,5 +184,18 @@ namespace Explorer
} }
} }
} }
[HarmonyPatch(typeof(Cursor), nameof(Cursor.visible), MethodType.Getter)]
public class Cursor_get_visible
{
[HarmonyPostfix]
public static void Postfix(ref bool __result)
{
if (ShouldForceMouse)
{
__result = m_lastVisibleState;
}
}
}
} }
} }

View File

@ -18,8 +18,8 @@
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>..\Release\</OutputPath> <OutputPath>..\Release\2019\</OutputPath>
<DefineConstants>DEBUG</DefineConstants> <DefineConstants>Release_2019</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
@ -30,7 +30,7 @@
<DebugSymbols>false</DebugSymbols> <DebugSymbols>false</DebugSymbols>
<DebugType>none</DebugType> <DebugType>none</DebugType>
<Optimize>false</Optimize> <Optimize>false</Optimize>
<OutputPath>..\Release\</OutputPath> <OutputPath>..\Release\2018\</OutputPath>
<DefineConstants>Release_Unity2018</DefineConstants> <DefineConstants>Release_Unity2018</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
@ -44,7 +44,7 @@
</Reference> </Reference>
<Reference Include="mcs"> <Reference Include="mcs">
<HintPath>..\lib\mcs.dll</HintPath> <HintPath>..\lib\mcs.dll</HintPath>
<Private>False</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="MelonLoader.ModHandler"> <Reference Include="MelonLoader.ModHandler">
<HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath> <HintPath>..\..\..\..\..\Steam\steamapps\common\Hellpoint\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
@ -129,12 +129,17 @@
<Compile Include="CachedObjects\CacheMethod.cs" /> <Compile Include="CachedObjects\CacheMethod.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="Unstripping\ScrollViewStateUnstrip.cs" />
<Compile Include="Extensions\UnityExtensions.cs" /> <Compile Include="Extensions\UnityExtensions.cs" />
<Compile Include="Helpers\PageHelper.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" /> <Compile Include="Helpers\ReflectionHelpers.cs" />
<Compile Include="Helpers\UIHelpers.cs" /> <Compile Include="Helpers\UIHelpers.cs" />
<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="Unstripping\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

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnityEngine; using UnityEngine;
namespace Explorer namespace Explorer

98
src/Helpers/PageHelper.cs Normal file
View File

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
public enum Turn
{
Left,
Right
}
public class PageHelper
{
public int PageOffset { get; set; }
public int ItemsPerPage { get; set; } = 20;
public int ItemCount
{
get => m_count;
set
{
m_count = value;
CalculateMaxOffset();
}
}
private int m_count;
public int MaxPageOffset { get; private set; } = -1;
private int CalculateMaxOffset()
{
return MaxPageOffset = (int)Mathf.Ceil((float)(ItemCount / (decimal)ItemsPerPage)) - 1;
}
public void CurrentPageLabel()
{
var orig = GUI.skin.label.alignment;
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Label($"Page {PageOffset + 1}/{MaxPageOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
GUI.skin.label.alignment = orig;
}
public void TurnPage(Turn direction)
{
var _ = Vector2.zero;
TurnPage(direction, ref _);
}
public void TurnPage(Turn direction, ref Vector2 scroll)
{
if (direction == Turn.Left)
{
if (PageOffset > 0)
{
PageOffset--;
scroll = Vector2.zero;
}
}
else
{
if (PageOffset < MaxPageOffset)
{
PageOffset++;
scroll = Vector2.zero;
}
}
}
public int CalculateOffsetIndex()
{
int offset = PageOffset * ItemsPerPage;
if (offset >= ItemCount)
{
offset = 0;
PageOffset = 0;
}
return offset;
}
public void DrawLimitInputArea()
{
GUILayout.Label("Limit: ", new GUILayoutOption[] { GUILayout.Width(50) });
var limit = this.ItemsPerPage.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(50) });
if (limit != ItemsPerPage.ToString() && int.TryParse(limit, out int i))
{
ItemsPerPage = i;
}
}
}
}

View File

@ -8,6 +8,7 @@ using UnhollowerBaseLib;
using UnhollowerRuntimeLib; using UnhollowerRuntimeLib;
using UnityEngine; using UnityEngine;
using BF = System.Reflection.BindingFlags; using BF = System.Reflection.BindingFlags;
using MelonLoader;
namespace Explorer namespace Explorer
{ {
@ -27,8 +28,9 @@ namespace Explorer
{ {
if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) return obj; if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo)) return obj;
var generic = m_tryCastMethodInfo.MakeGenericMethod(castTo); return m_tryCastMethodInfo
return generic.Invoke(obj, null); .MakeGenericMethod(castTo)
.Invoke(obj, null);
} }
public static string ExceptionToString(Exception e) public static string ExceptionToString(Exception e)
@ -81,9 +83,10 @@ namespace Explorer
{ {
if (t.IsGenericType) if (t.IsGenericType)
{ {
return t.GetGenericTypeDefinition() is Type typeDef var generic = t.GetGenericTypeDefinition();
&& (typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))
|| typeDef.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.IList<>))); return generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.List<>))
|| generic.IsAssignableFrom(typeof(Il2CppSystem.Collections.Generic.IList<>));
} }
else else
{ {
@ -115,58 +118,36 @@ namespace Explorer
return null; return null;
} }
public static Type GetActualType(object m_object) public static Type GetActualType(object obj)
{ {
if (m_object == null) return null; if (obj == null) return null;
if (m_object is Il2CppSystem.Object ilObject) if (obj is Il2CppSystem.Object ilObject)
{ {
var iltype = ilObject.GetIl2CppType(); var ilTypeName = ilObject.GetIl2CppType().AssemblyQualifiedName;
if (Type.GetType(iltype.AssemblyQualifiedName) is Type type)
if (Type.GetType(ilTypeName) is Type t && !t.FullName.Contains("System.RuntimeType"))
{ {
return type; return t;
}
else
{
return ilObject.GetType();
} }
return ilObject.GetType();
} }
else
{ return obj.GetType();
return m_object.GetType();
}
} }
public static Type[] GetAllBaseTypes(object m_object) public static Type[] GetAllBaseTypes(object obj)
{ {
var list = new List<Type>(); var list = new List<Type>();
if (m_object is Il2CppSystem.Object ilObject) var type = GetActualType(obj);
{ list.Add(type);
var ilType = ilObject.GetIl2CppType();
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilTypeToManaged)
{
list.Add(ilTypeToManaged);
while (ilType.BaseType != null) while (type.BaseType != null)
{
ilType = ilType.BaseType;
if (Type.GetType(ilType.AssemblyQualifiedName) is Type ilBaseTypeToManaged)
{
list.Add(ilBaseTypeToManaged);
}
}
}
}
else
{ {
var type = m_object.GetType(); type = type.BaseType;
list.Add(type); list.Add(type);
while (type.BaseType != null)
{
type = type.BaseType;
list.Add(type);
}
} }
return list.ToArray(); return list.ToArray();

View File

@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Reflection;
using UnhollowerRuntimeLib;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object; using Object = UnityEngine.Object;
@ -22,11 +24,13 @@ namespace Explorer
} }
// helper for drawing a styled button for a GameObject or Transform // helper for drawing a styled button for a GameObject or Transform
public static void GameobjButton(GameObject obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380) public static void GOButton(object _obj, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{ {
bool children = obj.transform.childCount > 0; var obj = (_obj as GameObject) ?? (_obj as Transform).gameObject;
string label = children ? "[" + obj.transform.childCount + " children] " : ""; bool hasChild = obj.transform.childCount > 0;
string label = hasChild ? $"[{obj.transform.childCount} children] " : "";
label += obj.name; label += obj.name;
bool enabled = obj.activeSelf; bool enabled = obj.activeSelf;
@ -49,11 +53,13 @@ namespace Explorer
color = Color.red; color = Color.red;
} }
FastGameobjButton(obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width); GOButton_Impl(_obj, color, label, obj.activeSelf, specialInspectMethod, showSmallInspectBtn, width);
} }
public static void FastGameobjButton(GameObject obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380) public static void GOButton_Impl(object _obj, Color activeColor, string label, bool enabled, Action<Transform> specialInspectMethod = null, bool showSmallInspectBtn = true, float width = 380)
{ {
var obj = _obj as GameObject ?? (_obj as Transform).gameObject;
if (!obj) if (!obj)
{ {
GUILayout.Label("<i><color=red>null</color></i>", null); GUILayout.Label("<i><color=red>null</color></i>", null);
@ -83,7 +89,7 @@ namespace Explorer
} }
else else
{ {
WindowManager.InspectObject(obj, out bool _); WindowManager.InspectObject(_obj, out bool _);
} }
} }
@ -94,7 +100,7 @@ namespace Explorer
if (showSmallInspectBtn) if (showSmallInspectBtn)
{ {
SmallInspectButton(obj); SmallInspectButton(_obj);
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();

View File

@ -33,12 +33,14 @@ namespace Explorer
} }
} }
public static void HorizontalLine(Color color) public static void HorizontalLine(Color _color, bool small = false)
{ {
var c = GUI.color; var orig = GUI.color;
GUI.color = color;
GUILayout.Box(GUIContent.none, HorizontalBar, null); GUI.color = _color;
GUI.color = c; GUILayout.Box(GUIContent.none, !small ? HorizontalBar : HorizontalBarSmall, null);
GUI.color = orig;
} }
private static GUISkin _customSkin; private static GUISkin _customSkin;
@ -46,8 +48,6 @@ namespace Explorer
public static Texture2D m_nofocusTex; public static Texture2D m_nofocusTex;
public static Texture2D m_focusTex; public static Texture2D m_focusTex;
private static GUIStyle _horizBarStyle;
private static GUIStyle HorizontalBar private static GUIStyle HorizontalBar
{ {
get get
@ -63,6 +63,24 @@ namespace Explorer
return _horizBarStyle; return _horizBarStyle;
} }
} }
private static GUIStyle _horizBarStyle;
private static GUIStyle HorizontalBarSmall
{
get
{
if (_horizBarSmallStyle == null)
{
_horizBarSmallStyle = new GUIStyle();
_horizBarSmallStyle.normal.background = Texture2D.whiteTexture;
_horizBarSmallStyle.margin = new RectOffset(0, 0, 2, 2);
_horizBarSmallStyle.fixedHeight = 1;
}
return _horizBarSmallStyle;
}
}
private static GUIStyle _horizBarSmallStyle;
private static GUISkin CreateWindowSkin() private static GUISkin CreateWindowSkin()
{ {

View File

@ -51,15 +51,12 @@ namespace Explorer
public void OnGUI() public void OnGUI()
{ {
if (CppExplorer.ShowMenu) var origSkin = GUI.skin;
{ GUI.skin = UIStyles.WindowSkin;
var origSkin = GUI.skin;
GUI.skin = UIStyles.WindowSkin;
MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, CppExplorer.NAME); MainRect = GUI.Window(MainWindowID, MainRect, (GUI.WindowFunction)MainWindow, CppExplorer.NAME);
GUI.skin = origSkin; GUI.skin = origSkin;
}
} }
private void MainWindow(int id) private void MainWindow(int id)
@ -77,9 +74,12 @@ namespace Explorer
MainHeader(); MainHeader();
var page = Pages[m_currentPage]; var page = Pages[m_currentPage];
page.scroll = GUILayout.BeginScrollView(page.scroll, GUI.skin.scrollView);
page.scroll = GUIUnstrip.BeginScrollView(page.scroll);
page.DrawWindow(); page.DrawWindow();
GUILayout.EndScrollView();
GUIUnstrip.EndScrollView();
MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID); MainRect = ResizeDrag.ResizeWindow(MainRect, MainWindowID);

View File

@ -14,9 +14,7 @@ namespace Explorer
public override string Name { get => "Scene Explorer"; set => base.Name = value; } public override string Name { get => "Scene Explorer"; set => base.Name = value; }
private int m_pageOffset = 0; public PageHelper Pages = new PageHelper();
private int m_limit = 20;
private int m_currentTotalCount = 0;
private float m_timeOfLastUpdate = -1f; private float m_timeOfLastUpdate = -1f;
@ -46,14 +44,14 @@ namespace Explorer
SetTransformTarget(null); SetTransformTarget(null);
} }
public void CheckOffset(ref int offset, int childCount) //public void CheckOffset(ref int offset, int childCount)
{ //{
if (offset >= childCount) // if (offset >= childCount)
{ // {
offset = 0; // offset = 0;
m_pageOffset = 0; // m_pageOffset = 0;
} // }
} //}
public override void Update() public override void Update()
{ {
@ -63,7 +61,6 @@ namespace Explorer
m_timeOfLastUpdate = Time.time; m_timeOfLastUpdate = Time.time;
m_objectList = new List<GameObjectCache>(); m_objectList = new List<GameObjectCache>();
int offset = m_pageOffset * m_limit;
var allTransforms = new List<Transform>(); var allTransforms = new List<Transform>();
@ -86,15 +83,14 @@ namespace Explorer
} }
} }
m_currentTotalCount = allTransforms.Count; Pages.ItemCount = allTransforms.Count;
// make sure offset doesn't exceed count int offset = Pages.CalculateOffsetIndex();
CheckOffset(ref offset, m_currentTotalCount);
// sort by childcount // sort by childcount
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount)); allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
for (int i = offset; i < offset + m_limit && i < m_currentTotalCount; i++) for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
{ {
var child = allTransforms[i]; var child = allTransforms[i];
m_objectList.Add(new GameObjectCache(child.gameObject)); m_objectList.Add(new GameObjectCache(child.gameObject));
@ -128,7 +124,7 @@ namespace Explorer
{ {
m_searchResults = SearchSceneObjects(m_searchInput); m_searchResults = SearchSceneObjects(m_searchInput);
m_searching = true; m_searching = true;
m_currentTotalCount = m_searchResults.Count; Pages.ItemCount = m_searchResults.Count;
} }
public void CancelSearch() public void CancelSearch()
@ -242,32 +238,24 @@ namespace Explorer
{ {
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("Limit per page: ", new GUILayoutOption[] { GUILayout.Width(100) }); Pages.DrawLimitInputArea();
var limit = m_limit.ToString();
limit = GUILayout.TextField(limit, new GUILayoutOption[] { GUILayout.Width(30) });
if (int.TryParse(limit, out int lim))
{
m_limit = lim;
}
// prev/next page buttons if (Pages.ItemCount > Pages.ItemsPerPage)
if (m_currentTotalCount > m_limit)
{ {
int count = m_currentTotalCount; if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1;
if (GUILayout.Button("< Prev", null))
{ {
if (m_pageOffset > 0) m_pageOffset--; Pages.TurnPage(Turn.Left, ref this.scroll);
m_timeOfLastUpdate = -1f; m_timeOfLastUpdate = -1f;
Update(); Update();
} }
GUI.skin.label.alignment = TextAnchor.MiddleCenter; Pages.CurrentPageLabel();
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
if (GUILayout.Button("Next >", null)) if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{ {
if (m_pageOffset < maxOffset) m_pageOffset++; Pages.TurnPage(Turn.Right, ref this.scroll);
m_timeOfLastUpdate = -1f; m_timeOfLastUpdate = -1f;
Update(); Update();
} }
@ -319,7 +307,7 @@ namespace Explorer
} }
else else
{ {
UIHelpers.FastGameobjButton(obj.RefGameObject, UIHelpers.GOButton_Impl(obj.RefGameObject,
obj.EnabledColor, obj.EnabledColor,
obj.Label, obj.Label,
obj.RefGameObject.activeSelf, obj.RefGameObject.activeSelf,
@ -342,21 +330,15 @@ namespace Explorer
if (m_searchResults.Count > 0) if (m_searchResults.Count > 0)
{ {
int offset = m_pageOffset * m_limit; int offset = Pages.CalculateOffsetIndex();
if (offset >= m_searchResults.Count) for (int i = offset; i < offset + Pages.ItemsPerPage && i < m_searchResults.Count; i++)
{
offset = 0;
m_pageOffset = 0;
}
for (int i = offset; i < offset + m_limit && i < m_searchResults.Count; i++)
{ {
var obj = m_searchResults[i]; var obj = m_searchResults[i];
if (obj.RefGameObject) if (obj.RefGameObject)
{ {
UIHelpers.FastGameobjButton(obj.RefGameObject, UIHelpers.GOButton_Impl(obj.RefGameObject,
obj.EnabledColor, obj.EnabledColor,
obj.Label, obj.Label,
obj.RefGameObject.activeSelf, obj.RefGameObject.activeSelf,

View File

@ -5,11 +5,7 @@ using System.Linq;
using System.Text; using System.Text;
using UnityEngine; using UnityEngine;
using System.Reflection; using System.Reflection;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
using UnhollowerRuntimeLib;
using MelonLoader; using MelonLoader;
using UnhollowerBaseLib;
namespace Explorer namespace Explorer
{ {
@ -21,11 +17,11 @@ namespace Explorer
private string m_searchInput = ""; private string m_searchInput = "";
private string m_typeInput = ""; private string m_typeInput = "";
private int m_limit = 20;
private int m_pageOffset = 0;
//private List<object> m_searchResults = new List<object>();
private Vector2 resultsScroll = Vector2.zero; private Vector2 resultsScroll = Vector2.zero;
public PageHelper Pages = new PageHelper();
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>(); private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
public SceneFilter SceneMode = SceneFilter.Any; public SceneFilter SceneMode = SceneFilter.Any;
@ -55,7 +51,7 @@ namespace Explorer
public void OnSceneChange() public void OnSceneChange()
{ {
m_searchResults.Clear(); m_searchResults.Clear();
m_pageOffset = 0; Pages.PageOffset = 0;
} }
public override void Update() public override void Update()
@ -78,6 +74,9 @@ namespace Explorer
var cache = CacheObjectBase.GetCacheObject(toCache); var cache = CacheObjectBase.GetCacheObject(toCache);
m_searchResults.Add(cache); m_searchResults.Add(cache);
} }
Pages.ItemCount = m_searchResults.Count;
Pages.PageOffset = 0;
} }
public override void DrawWindow() public override void DrawWindow()
@ -90,8 +89,7 @@ namespace Explorer
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) })) if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
{ {
//m_searchResults = GetInstanceClassScanner().ToList(); //m_searchResults = GetInstanceClassScanner().ToList();
CacheResults(GetInstanceClassScanner()); CacheResults(GetInstanceClassScanner());
m_pageOffset = 0;
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
@ -107,35 +105,43 @@ namespace Explorer
int count = m_searchResults.Count; int count = m_searchResults.Count;
if (count > this.m_limit) GUILayout.BeginHorizontal(null);
Pages.DrawLimitInputArea();
if (count > Pages.ItemsPerPage)
{ {
// prev/next page buttons // prev/next page buttons
GUILayout.BeginHorizontal(null);
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limit)) - 1;
if (GUILayout.Button("< Prev", null))
{
if (m_pageOffset > 0) m_pageOffset--;
}
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) }); if (Pages.ItemCount > Pages.ItemsPerPage)
if (GUILayout.Button("Next >", null))
{ {
if (m_pageOffset < maxOffset) m_pageOffset++; if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Left, ref this.resultsScroll);
}
Pages.CurrentPageLabel();
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
Pages.TurnPage(Turn.Right, ref this.resultsScroll);
}
} }
GUILayout.EndHorizontal();
} }
resultsScroll = GUILayout.BeginScrollView(resultsScroll, GUI.skin.scrollView); GUILayout.EndHorizontal();
resultsScroll = GUIUnstrip.BeginScrollView(resultsScroll);
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height); var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
if (m_searchResults.Count > 0) if (m_searchResults.Count > 0)
{ {
int offset = m_pageOffset * this.m_limit; //int offset = m_pageOffset * this.m_limit;
if (offset >= count) m_pageOffset = 0; //if (offset >= count) m_pageOffset = 0;
int offset = Pages.CalculateOffsetIndex();
for (int i = offset; i < offset + m_limit && 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); //m_searchResults[i].DrawValue(MainMenu.MainRect);
@ -146,7 +152,7 @@ namespace Explorer
GUILayout.Label("<color=red><i>No results found!</i></color>", null); GUILayout.Label("<color=red><i>No results found!</i></color>", null);
} }
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
catch catch
@ -169,15 +175,15 @@ 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; //GUI.skin.label.alignment = TextAnchor.MiddleRight;
GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) }); //GUILayout.Label("Results per page:", new GUILayoutOption[] { GUILayout.Width(120) });
var resultinput = m_limit.ToString(); //var resultinput = m_limit.ToString();
resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) }); //resultinput = GUILayout.TextField(resultinput, new GUILayoutOption[] { GUILayout.Width(55) });
if (int.TryParse(resultinput, out int _i) && _i > 0) //if (int.TryParse(resultinput, out int _i) && _i > 0)
{ //{
m_limit = _i; // m_limit = _i;
} //}
GUI.skin.label.alignment = TextAnchor.UpperLeft; //GUI.skin.label.alignment = TextAnchor.UpperLeft;
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
@ -256,7 +262,7 @@ namespace Explorer
private void Search() private void Search()
{ {
m_pageOffset = 0; Pages.PageOffset = 0;
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput)); CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
} }

View File

@ -0,0 +1,443 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using MelonLoader;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Reflection;
using UnityEngineInternal;
using Harmony;
namespace Explorer
{
// This is a manual unstrip of UnityEngine.GUI and UnityEngine.GUILayout methods.
// This code is provided "as-is".
// Taken from dnSpy output using Unity 2018.4.20.
// "Unity", Unity logos, and other Unity trademarks are trademarks or
// registered trademarks of Unity Technologies or its affiliates in the
// U.S. and elsewhere.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public class GUIUnstrip
{
public static int s_ScrollControlId;
public static bool ScrollFailed = false;
public static bool ManualUnstripFailed = false;
private static GenericStack ScrollStack
{
get
{
#if Release_2019
return GUI.scrollViewStates;
#else
return GUI.s_ScrollViewStates;
#endif
}
}
// ======= public methods ======= //
// Fix for GUILayoutUtility.GetLastRect().
// Calls UnstripExtensions.GetLastRectUnstripped.
public static Rect GetLastRect()
{
EventType type = Event.current.type;
Rect last;
if (type != EventType.Layout && type != EventType.Used)
{
last = GUILayoutUtility.current.topLevel.GetLastUnstripped();
}
else
{
last = GUILayoutUtility.kDummyRect;
}
return last;
}
// Fix for GUILayout.Scroller and GUILayout.ScrollerRepeatButton, just calling fixed implementations.
public static float Scroller(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
=> Scroller_Impl(position, value, size, leftValue, rightValue, slider, thumb, leftButton, rightButton, horiz);
public static bool ScrollerRepeatButton(int scrollerID, Rect rect, GUIStyle style)
=> ScrollerRepeatButton_Impl(scrollerID, rect, style);
// 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(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(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.
// Uses several manually unstripped methods.
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
{
// First, just try normal way, may not have been stripped or was unstripped successfully.
if (!ScrollFailed)
{
try
{
return GUILayout.BeginScrollView(scroll, options);
}
catch
{
ScrollFailed = true;
return scroll;
}
}
// Try manual unstripping implementation.
if (!ManualUnstripFailed)
{
try
{
return BeginScrollView_Impl(scroll, false, false, GUI.skin.horizontalScrollbar, GUI.skin.verticalScrollbar, GUI.skin.scrollView, options);
}
catch (Exception e)
{
MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_Impl: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
ManualUnstripFailed = true;
return scroll;
}
}
// Sorry! No scrolling for you.
return scroll;
}
public static void EndScrollView(bool handleScrollWheel = true)
{
// Only end the scroll view for the relevant BeginScrollView option, if any.
if (!ScrollFailed)
{
GUILayout.EndScrollView();
}
else if (!ManualUnstripFailed)
{
GUILayoutUtility.EndLayoutGroup();
EndScrollView_Impl(handleScrollWheel);
}
}
// ======= private methods ======= //
// Actual unstrip of GUILayout.BeginScrollView()
private static Vector2 BeginScrollView_Impl(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical, GUIStyle horizontalScrollbar,
GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
{
GUIUtility.CheckOnGUI();
var guiscrollGroup = (GUIScrollGroup)GUILayoutUtility.BeginLayoutGroup(
background,
null,
Il2CppType.Of<GUIScrollGroup>()
).Il2CppCast(typeof(GUIScrollGroup));
EventType type = Event.current.type;
if (type == EventType.Layout)
{
guiscrollGroup.resetCoords = true;
guiscrollGroup.isVertical = true;
guiscrollGroup.stretchWidth = 1;
guiscrollGroup.stretchHeight = 1;
guiscrollGroup.verticalScrollbar = verticalScrollbar;
guiscrollGroup.horizontalScrollbar = horizontalScrollbar;
guiscrollGroup.needsVerticalScrollbar = alwaysShowVertical;
guiscrollGroup.needsHorizontalScrollbar = alwaysShowHorizontal;
guiscrollGroup.ApplyOptions(options);
}
return BeginScrollView_Impl(guiscrollGroup.rect,
scrollPosition,
new Rect(0f, 0f, guiscrollGroup.clientWidth, guiscrollGroup.clientHeight),
alwaysShowHorizontal,
alwaysShowVertical,
horizontalScrollbar,
verticalScrollbar,
background
);
}
// Actual unstrip of GUI.BeginScrollView() -- note: not GUILayout.
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal, bool alwaysShowVertical,
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
{
GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_ScrollviewHash, FocusType.Passive);
var scrollViewState = (ScrollViewState)GUIUtility.GetStateObject(Il2CppType.Of<ScrollViewState>(), controlID).Il2CppCast(typeof(ScrollViewState));
var scrollExt = ScrollViewStateUnstrip.FromPointer(scrollViewState.Pointer);
if (scrollExt == null) throw new Exception($"Could not get scrollExt for pointer '{scrollViewState.Pointer}'!");
bool apply = scrollExt.apply;
if (apply)
{
scrollPosition = scrollExt.scrollPosition;
scrollExt.apply = false;
}
scrollExt.position = position;
scrollExt.scrollPosition = scrollPosition;
scrollExt.visibleRect = scrollExt.viewRect = viewRect;
var rect = scrollExt.visibleRect;
rect.width = position.width;
rect.height = position.height;
ScrollStack.Push(scrollViewState);
Rect screenRect = new Rect(position);
EventType type = Event.current.type;
if (type != EventType.Layout)
{
if (type != EventType.Used)
{
bool flag = alwaysShowVertical;
bool flag2 = alwaysShowHorizontal;
if (flag2 || viewRect.width > screenRect.width)
{
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
flag2 = true;
}
if (flag || viewRect.height > screenRect.height)
{
rect.width = position.width - verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
screenRect.width -= verticalScrollbar.fixedWidth + (float)verticalScrollbar.margin.left;
flag = true;
if (!flag2 && viewRect.width > screenRect.width)
{
rect.height = position.height - horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
screenRect.height -= horizontalScrollbar.fixedHeight + (float)horizontalScrollbar.margin.top;
flag2 = true;
}
}
if (Event.current.type == EventType.Repaint && background != GUIStyle.none)
{
background.Draw(position, position.Contains(Event.current.mousePosition), false, flag2 && flag, false);
}
if (flag2 && horizontalScrollbar != GUIStyle.none)
{
scrollPosition.x = HorizontalScrollbar(
new Rect(
position.x,
position.yMax - horizontalScrollbar.fixedHeight,
screenRect.width,
horizontalScrollbar.fixedHeight),
scrollPosition.x,
Mathf.Min(screenRect.width, viewRect.width),
0f,
viewRect.width,
horizontalScrollbar
);
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
scrollPosition.x = ((horizontalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.x, 0f, Mathf.Max(viewRect.width - position.width, 0f)) : 0f);
}
if (flag && verticalScrollbar != GUIStyle.none)
{
scrollPosition.y = VerticalScrollbar(
new Rect(
screenRect.xMax + (float)verticalScrollbar.margin.left,
screenRect.y,
verticalScrollbar.fixedWidth,
screenRect.height),
scrollPosition.y,
Mathf.Min(screenRect.height, viewRect.height),
0f,
viewRect.height,
verticalScrollbar
);
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
scrollPosition.y = ((verticalScrollbar == GUIStyle.none) ? Mathf.Clamp(scrollPosition.y, 0f, Mathf.Max(viewRect.height - position.height, 0f)) : 0f);
}
}
}
else
{
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
GUIUtility.GetControlID(GUI.s_RepeatButtonHash, FocusType.Passive);
}
GUIClip.Push(screenRect, new Vector2(Mathf.Round(-scrollPosition.x - viewRect.x), Mathf.Round(-scrollPosition.y - viewRect.y)), Vector2.zero, false);
return scrollPosition;
}
// Actual unstrip of GUI.EndScrollView()
private static void EndScrollView_Impl(bool handleScrollWheel)
{
GUIUtility.CheckOnGUI();
if (ScrollStack.Count <= 0) return;
//ScrollViewState scrollViewState = (ScrollViewState)GUI.s_ScrollViewStates.Peek();
var state = (ScrollViewState)ScrollStack.Peek().Il2CppCast(typeof(ScrollViewState));
//ScrollViewExtensions.Dict.TryGetValue(state.Pointer, out ScrollViewExtensions scrollExt);
var scrollExt = ScrollViewStateUnstrip.FromPointer(state.Pointer);
if (scrollExt == null) throw new Exception("Could not get scrollExt!");
GUIClip.Pop();
//GUI.s_ScrollViewStates.Pop();
ScrollStack.Pop();
var position = scrollExt.position;
if (handleScrollWheel && Event.current.type == EventType.ScrollWheel && position.Contains(Event.current.mousePosition))
{
var pos = scrollExt.scrollPosition;
pos.x = Mathf.Clamp(scrollExt.scrollPosition.x + Event.current.delta.x * 20f, 0f, scrollExt.viewRect.width - scrollExt.visibleRect.width);
pos.y = Mathf.Clamp(scrollExt.scrollPosition.y + Event.current.delta.y * 20f, 0f, scrollExt.viewRect.height - scrollExt.visibleRect.height);
if (scrollExt.scrollPosition.x < 0f)
{
pos.x = 0f;
}
if (pos.y < 0f)
{
pos.y = 0f;
}
// state.apply = true;
scrollExt.apply = true;
Event.current.Use();
}
}
// 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)
{
GUIUtility.CheckOnGUI();
int controlID = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
Rect position2;
Rect rect;
Rect rect2;
if (horiz)
{
position2 = new Rect(position.x + leftButton.fixedWidth, position.y, position.width - leftButton.fixedWidth - rightButton.fixedWidth, position.height);
rect = new Rect(position.x, position.y, leftButton.fixedWidth, position.height);
rect2 = new Rect(position.xMax - rightButton.fixedWidth, position.y, rightButton.fixedWidth, position.height);
}
else
{
position2 = new Rect(position.x, position.y + leftButton.fixedHeight, position.width, position.height - leftButton.fixedHeight - rightButton.fixedHeight);
rect = new Rect(position.x, position.y, position.width, leftButton.fixedHeight);
rect2 = new Rect(position.x, position.yMax - rightButton.fixedHeight, position.width, rightButton.fixedHeight);
}
//value = GUI.Slider(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
value = Slider(position2, value, size, leftValue, rightValue, slider, thumb, horiz, controlID);
bool flag = Event.current.type == EventType.MouseUp;
if (ScrollerRepeatButton_Impl(controlID, rect, leftButton))
{
value -= 10f * ((leftValue >= rightValue) ? -1f : 1f);
}
if (ScrollerRepeatButton_Impl(controlID, rect2, rightButton))
{
value += 10f * ((leftValue >= rightValue) ? -1f : 1f);
}
if (flag && Event.current.type == EventType.Used)
{
//GUI.s_ScrollControlId = 0;
GUIUnstrip.s_ScrollControlId = 0;
}
if (leftValue < rightValue)
{
value = Mathf.Clamp(value, leftValue, rightValue - size);
}
else
{
value = Mathf.Clamp(value, rightValue, leftValue - size);
}
return value;
}
// Actual unstrip of GUI.Slider
public static float Slider(Rect position, float value, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
{
//GUIUtility.CheckOnGUI();
if (id == 0)
{
id = GUIUtility.GetControlID(GUI.s_SliderHash, FocusType.Passive, position);
}
var sliderHandler = new SliderHandlerUnstrip(position, value, size, start, end, slider, thumb, horiz, id);
return sliderHandler.Handle();
}
// Actual unstrip of GUI.ScrollerRepeatButton
private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style)
{
bool result = false;
if (GUI.DoRepeatButton(rect, GUIContent.none, style, FocusType.Passive))
{
//bool flag = GUI.s_ScrollControlId != scrollerID;
//GUI.s_ScrollControlId = scrollerID;
bool flag = GUIUnstrip.s_ScrollControlId != scrollerID;
GUIUnstrip.s_ScrollControlId = scrollerID;
if (flag)
{
result = true;
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(250.0);
}
else if (Il2CppSystem.DateTime.Now >= GUI.nextScrollStepTime)
{
result = true;
GUI.nextScrollStepTime = Il2CppSystem.DateTime.Now.AddMilliseconds(30.0);
}
if (Event.current.type == EventType.Repaint)
{
GUI.InternalRepaintEditorWindow();
}
}
return result;
}
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Harmony;
using MelonLoader;
using UnityEngine;
namespace Explorer
{
// This is a manual unstrip of UnityEngine.ScrollViewState.
// This code is provided "as-is".
// Taken from dnSpy output using Unity 2018.4.20.
// "Unity", Unity logos, and other Unity trademarks are trademarks or
// registered trademarks of Unity Technologies or its affiliates in the
// U.S. and elsewhere.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public class ScrollViewStateUnstrip
{
public Rect position;
public Rect visibleRect;
public Rect viewRect;
public Vector2 scrollPosition;
public bool apply;
public static Dictionary<IntPtr, ScrollViewStateUnstrip> Dict = new Dictionary<IntPtr, ScrollViewStateUnstrip>();
public static ScrollViewStateUnstrip FromPointer(IntPtr ptr)
{
if (!Dict.ContainsKey(ptr))
{
Dict.Add(ptr, new ScrollViewStateUnstrip());
}
return Dict[ptr];
}
}
}

View File

@ -0,0 +1,381 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnhollowerRuntimeLib;
namespace Explorer
{
// This is a manual unstrip of UnityEngine.SliderHandler.
// This code is provided "as-is".
// Taken from dnSpy output using Unity 2018.4.20.
// "Unity", Unity logos, and other Unity trademarks are trademarks or
// registered trademarks of Unity Technologies or its affiliates in the
// U.S. and elsewhere.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public struct SliderHandlerUnstrip
{
private readonly Rect position;
private readonly float currentValue;
private readonly float size;
private readonly float start;
private readonly float end;
private readonly GUIStyle slider;
private readonly GUIStyle thumb;
private readonly bool horiz;
private readonly int id;
public SliderHandlerUnstrip(Rect position, float currentValue, float size, float start, float end, GUIStyle slider, GUIStyle thumb, bool horiz, int id)
{
this.position = position;
this.currentValue = currentValue;
this.size = size;
this.start = start;
this.end = end;
this.slider = slider;
this.thumb = thumb;
this.horiz = horiz;
this.id = id;
}
public float Handle()
{
float result;
if (this.slider == null || this.thumb == null)
{
result = this.currentValue;
}
else
{
switch (this.CurrentEventType())
{
case EventType.MouseDown:
return this.OnMouseDown();
case EventType.MouseUp:
return this.OnMouseUp();
case EventType.MouseDrag:
return this.OnMouseDrag();
case EventType.Repaint:
return this.OnRepaint();
}
result = this.currentValue;
}
return result;
}
private float OnMouseDown()
{
float result;
if (!this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
{
result = this.currentValue;
}
else
{
GUI.scrollTroughSide = 0;
GUIUtility.hotControl = this.id;
this.CurrentEvent().Use();
if (this.ThumbSelectionRect().Contains(this.CurrentEvent().mousePosition))
{
this.StartDraggingWithValue(this.ClampedCurrentValue());
result = this.currentValue;
}
else
{
GUI.changed = true;
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(250.0);
GUI.scrollTroughSide = this.CurrentScrollTroughSide();
result = this.PageMovementValue();
}
else
{
float num = this.ValueForCurrentMousePosition();
this.StartDraggingWithValue(num);
result = this.Clamp(num);
}
}
}
return result;
}
private float OnMouseDrag()
{
float result;
if (GUIUtility.hotControl != this.id)
{
result = this.currentValue;
}
else
{
SliderState sliderState = this.SliderState();
if (!sliderState.isDragging)
{
result = this.currentValue;
}
else
{
GUI.changed = true;
this.CurrentEvent().Use();
float num = this.MousePosition() - sliderState.dragStartPos;
float value = sliderState.dragStartValue + num / this.ValuesPerPixel();
result = this.Clamp(value);
}
}
return result;
}
private float OnMouseUp()
{
if (GUIUtility.hotControl == this.id)
{
this.CurrentEvent().Use();
GUIUtility.hotControl = 0;
}
return this.currentValue;
}
private float OnRepaint()
{
this.slider.Draw(this.position, GUIContent.none, this.id);
if (!this.IsEmptySlider() && this.currentValue >= this.MinValue() && this.currentValue <= this.MaxValue())
{
this.thumb.Draw(this.ThumbRect(), GUIContent.none, this.id);
}
float result;
if (GUIUtility.hotControl != this.id || !this.position.Contains(this.CurrentEvent().mousePosition) || this.IsEmptySlider())
{
result = this.currentValue;
}
else if (this.ThumbRect().Contains(this.CurrentEvent().mousePosition))
{
if (GUI.scrollTroughSide != 0)
{
GUIUtility.hotControl = 0;
}
result = this.currentValue;
}
else
{
GUI.InternalRepaintEditorWindow();
if (SystemClock.now < GUI.nextScrollStepTime)
{
result = this.currentValue;
}
else if (this.CurrentScrollTroughSide() != GUI.scrollTroughSide)
{
result = this.currentValue;
}
else
{
GUI.nextScrollStepTime = SystemClock.now.AddMilliseconds(30.0);
if (this.SupportsPageMovements())
{
this.SliderState().isDragging = false;
GUI.changed = true;
result = this.PageMovementValue();
}
else
{
result = this.ClampedCurrentValue();
}
}
}
return result;
}
private EventType CurrentEventType()
{
return this.CurrentEvent().GetTypeForControl(this.id);
}
private int CurrentScrollTroughSide()
{
float num = (!this.horiz) ? this.CurrentEvent().mousePosition.y : this.CurrentEvent().mousePosition.x;
float num2 = (!this.horiz) ? this.ThumbRect().y : this.ThumbRect().x;
return (num <= num2) ? -1 : 1;
}
private bool IsEmptySlider()
{
return this.start == this.end;
}
private bool SupportsPageMovements()
{
return this.size != 0f && GUI.usePageScrollbars;
}
private float PageMovementValue()
{
float num = this.currentValue;
int num2 = (this.start <= this.end) ? 1 : -1;
if (this.MousePosition() > this.PageUpMovementBound())
{
num += this.size * (float)num2 * 0.9f;
}
else
{
num -= this.size * (float)num2 * 0.9f;
}
return this.Clamp(num);
}
private float PageUpMovementBound()
{
float result;
if (this.horiz)
{
result = this.ThumbRect().xMax - this.position.x;
}
else
{
result = this.ThumbRect().yMax - this.position.y;
}
return result;
}
private Event CurrentEvent()
{
return Event.current;
}
private float ValueForCurrentMousePosition()
{
float result;
if (this.horiz)
{
result = (this.MousePosition() - this.ThumbRect().width * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
}
else
{
result = (this.MousePosition() - this.ThumbRect().height * 0.5f) / this.ValuesPerPixel() + this.start - this.size * 0.5f;
}
return result;
}
private float Clamp(float value)
{
return Mathf.Clamp(value, this.MinValue(), this.MaxValue());
}
private Rect ThumbSelectionRect()
{
return this.ThumbRect();
}
private void StartDraggingWithValue(float dragStartValue)
{
SliderState sliderState = this.SliderState();
sliderState.dragStartPos = this.MousePosition();
sliderState.dragStartValue = dragStartValue;
sliderState.isDragging = true;
}
private SliderState SliderState()
{
return (SliderState)GUIUtility.GetStateObject(Il2CppType.Of<SliderState>(), this.id).Il2CppCast(typeof(SliderState));
}
private Rect ThumbRect()
{
return (!this.horiz) ? this.VerticalThumbRect() : this.HorizontalThumbRect();
}
private Rect VerticalThumbRect()
{
float num = this.ValuesPerPixel();
Rect result;
if (this.start < this.end)
{
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * num + this.ThumbSize());
}
else
{
result = new Rect(this.position.x + (float)this.slider.padding.left, (this.ClampedCurrentValue() + this.size - this.start) * num + this.position.y + (float)this.slider.padding.top, this.position.width - (float)this.slider.padding.horizontal, this.size * -num + this.ThumbSize());
}
return result;
}
private Rect HorizontalThumbRect()
{
float num = this.ValuesPerPixel();
Rect result;
if (this.start < this.end)
{
result = new Rect((this.ClampedCurrentValue() - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y + (float)this.slider.padding.top, this.size * num + this.ThumbSize(), this.position.height - (float)this.slider.padding.vertical);
}
else
{
result = new Rect((this.ClampedCurrentValue() + this.size - this.start) * num + this.position.x + (float)this.slider.padding.left, this.position.y, this.size * -num + this.ThumbSize(), this.position.height);
}
return result;
}
private float ClampedCurrentValue()
{
return this.Clamp(this.currentValue);
}
private float MousePosition()
{
float result;
if (this.horiz)
{
result = this.CurrentEvent().mousePosition.x - this.position.x;
}
else
{
result = this.CurrentEvent().mousePosition.y - this.position.y;
}
return result;
}
private float ValuesPerPixel()
{
float result;
if (this.horiz)
{
result = (this.position.width - (float)this.slider.padding.horizontal - this.ThumbSize()) / (this.end - this.start);
}
else
{
result = (this.position.height - (float)this.slider.padding.vertical - this.ThumbSize()) / (this.end - this.start);
}
return result;
}
private float ThumbSize()
{
float result;
if (this.horiz)
{
result = ((this.thumb.fixedWidth == 0f) ? ((float)this.thumb.padding.horizontal) : this.thumb.fixedWidth);
}
else
{
result = ((this.thumb.fixedHeight == 0f) ? ((float)this.thumb.padding.vertical) : this.thumb.fixedHeight);
}
return result;
}
private float MaxValue()
{
return Mathf.Max(this.start, this.end) - this.size;
}
private float MinValue()
{
return Mathf.Min(this.start, this.end);
}
}
}

View File

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace Explorer
{
// "Unity", Unity logos, and other Unity trademarks are trademarks or
// registered trademarks of Unity Technologies or its affiliates in the
// U.S. and elsewhere.
// 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

@ -21,11 +21,13 @@ namespace Explorer
private string m_name; private string m_name;
private string m_scene; private string m_scene;
private Vector2 m_transformScroll = Vector2.zero;
private Transform[] m_children; private Transform[] m_children;
private Component[] m_components; private Vector2 m_transformScroll = Vector2.zero;
private PageHelper ChildPages = new PageHelper();
private Component[] m_components;
private Vector2 m_compScroll = Vector2.zero; private Vector2 m_compScroll = Vector2.zero;
private PageHelper CompPages = new PageHelper();
private float m_translateAmount = 0.3f; private float m_translateAmount = 0.3f;
private float m_rotateAmount = 50f; private float m_rotateAmount = 50f;
@ -71,15 +73,10 @@ 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"
: m_object.scene.name; : m_object.scene.name;
var list = new List<Transform>(); Update();
for (int i = 0; i < m_object.transform.childCount; i++)
{
list.Add(m_object.transform.GetChild(i));
}
m_children = list.ToArray();
} }
public override void Update() public override void Update()
@ -91,12 +88,30 @@ namespace Explorer
throw new Exception("Object is null!"); throw new Exception("Object is null!");
} }
var list = new List<Component>(); var list = new List<Transform>();
for (int i = 0; i < m_object.transform.childCount; i++)
{
list.Add(m_object.transform.GetChild(i));
}
list.Sort((a, b) => b.childCount.CompareTo(a.childCount));
m_children = list.ToArray();
ChildPages.ItemCount = m_children.Length;
var list2 = new List<Component>();
foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType)) foreach (var comp in m_object.GetComponents(ReflectionHelpers.ComponentType))
{ {
list.Add(comp); var ilType = comp.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
list2.Add(comp);
} }
m_components = list.ToArray(); m_components = list2.ToArray();
CompPages.ItemCount = m_components.Length;
} }
catch (Exception e) catch (Exception e)
{ {
@ -106,7 +121,7 @@ namespace Explorer
private void DestroyOnException(Exception e) private void DestroyOnException(Exception e)
{ {
MelonLogger.Log($"{e.GetType()}, {e.Message}"); MelonLogger.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
DestroyWindow(); DestroyWindow();
} }
@ -154,18 +169,22 @@ namespace Explorer
GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box); GUILayout.BeginArea(new Rect(5, 25, rect.width - 10, rect.height - 35), GUI.skin.box);
} }
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); scroll = GUIUnstrip.BeginScrollView(scroll);
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null); GUILayout.Label("Scene: <color=cyan>" + (m_scene == "" ? "n/a" : m_scene) + "</color>", null);
if (m_scene == UnityHelpers.ActiveSceneName) if (m_scene == UnityHelpers.ActiveSceneName)
{ {
if (GUILayout.Button("<color=#00FF00>< View in Scene Explorer</color>", new GUILayoutOption[] { GUILayout.Width(230) })) if (GUILayout.Button("<color=#00FF00>Send to Scene View</color>", new GUILayoutOption[] { GUILayout.Width(150) }))
{ {
ScenePage.Instance.SetTransformTarget(m_object.transform); ScenePage.Instance.SetTransformTarget(m_object.transform);
MainMenu.SetCurrentPage(0); MainMenu.SetCurrentPage(0);
} }
} }
if (GUILayout.Button("Reflection Inspect", new GUILayoutOption[] { GUILayout.Width(150) }))
{
WindowManager.InspectObject(Target, out _, true);
}
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
@ -201,7 +220,7 @@ namespace Explorer
GameObjectControls(); GameObjectControls();
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
if (!WindowManager.TabView) if (!WindowManager.TabView)
{ {
@ -218,29 +237,47 @@ namespace Explorer
private void TransformList(Rect m_rect) private void TransformList(Rect m_rect)
{ {
GUILayout.BeginVertical(GUI.skin.box, null); // new GUILayoutOption[] { GUILayout.Height(250) }); GUILayout.BeginVertical(GUI.skin.box, null);
m_transformScroll = GUILayout.BeginScrollView(m_transformScroll, GUI.skin.scrollView); m_transformScroll = GUIUnstrip.BeginScrollView(m_transformScroll);
GUILayout.Label("<b><size=15>Children</size></b>", null);
GUILayout.BeginHorizontal(null);
ChildPages.DrawLimitInputArea();
if (ChildPages.ItemCount > ChildPages.ItemsPerPage)
{
ChildPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
ChildPages.TurnPage(Turn.Left, ref this.m_transformScroll);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
ChildPages.TurnPage(Turn.Right, ref this.m_transformScroll);
}
}
GUILayout.EndHorizontal();
GUILayout.Label("<b>Children:</b>", null);
if (m_children != null && m_children.Length > 0) if (m_children != null && m_children.Length > 0)
{ {
foreach (var obj in m_children.Where(x => x.childCount > 0)) int start = ChildPages.CalculateOffsetIndex();
for (int j = start; (j < start + ChildPages.ItemsPerPage && j < ChildPages.ItemCount); j++)
{ {
var obj = m_children[j];
if (!obj) if (!obj)
{ {
GUILayout.Label("null", null); GUILayout.Label("null", null);
continue; continue;
} }
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 60);
} UIHelpers.GOButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 80);
foreach (var obj in m_children.Where(x => x.childCount == 0))
{
if (!obj)
{
GUILayout.Label("null", null);
continue;
}
UIHelpers.GameobjButton(obj.gameObject, InspectGameObject, false, m_rect.width / 2 - 60);
} }
} }
else else
@ -248,17 +285,37 @@ namespace Explorer
GUILayout.Label("<i>None</i>", null); GUILayout.Label("<i>None</i>", null);
} }
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }
private void ComponentList(Rect m_rect) private void ComponentList(Rect m_rect)
{ {
GUILayout.BeginVertical(GUI.skin.box, null); // new GUILayoutOption[] { GUILayout.Height(250) }); GUILayout.BeginVertical(GUI.skin.box, null);
m_compScroll = GUILayout.BeginScrollView(m_compScroll, GUI.skin.scrollView); m_compScroll = GUIUnstrip.BeginScrollView(m_compScroll);
GUILayout.Label("<b><size=15>Components</size></b>", null); GUILayout.Label("<b><size=15>Components</size></b>", null);
GUILayout.BeginHorizontal(null);
CompPages.DrawLimitInputArea();
if (CompPages.ItemCount > CompPages.ItemsPerPage)
{
CompPages.CurrentPageLabel();
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal(null);
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{
CompPages.TurnPage(Turn.Left, ref this.m_compScroll);
}
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{
CompPages.TurnPage(Turn.Right, ref this.m_compScroll);
}
}
GUILayout.EndHorizontal();
GUI.skin.button.alignment = TextAnchor.MiddleLeft; GUI.skin.button.alignment = TextAnchor.MiddleLeft;
if (m_cachedDestroyList.Count > 0) if (m_cachedDestroyList.Count > 0)
{ {
@ -267,15 +324,15 @@ namespace Explorer
if (m_components != null) if (m_components != null)
{ {
foreach (var component in m_components) int start = CompPages.CalculateOffsetIndex();
for (int j = start; (j < start + CompPages.ItemsPerPage && j < CompPages.ItemCount); j++)
{ {
var component = m_components[j];
if (!component) continue; if (!component) continue;
var ilType = component.GetIl2CppType(); var ilType = component.GetIl2CppType();
if (ilType == ReflectionHelpers.TransformType)
{
continue;
}
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType)) if (ReflectionHelpers.BehaviourType.IsAssignableFrom(ilType))
@ -286,7 +343,7 @@ namespace Explorer
{ {
GUILayout.Space(26); GUILayout.Space(26);
} }
if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 90) })) if (GUILayout.Button("<color=cyan>" + ilType.Name + "</color>", new GUILayoutOption[] { GUILayout.Width(m_rect.width / 2 - 100) }))
{ {
ReflectObject(component); ReflectObject(component);
} }
@ -308,7 +365,7 @@ namespace Explorer
} }
} }
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
GUILayout.EndVertical(); GUILayout.EndVertical();
} }

View File

@ -13,15 +13,17 @@ namespace Explorer
public class ReflectionWindow : UIWindow public class ReflectionWindow : UIWindow
{ {
public override string Title => WindowManager.TabView public override string Title => WindowManager.TabView
? $"<color=cyan>[R]</color> {ObjectType.Name}" ? $"<color=cyan>[R]</color> {TargetType.Name}"
: $"Reflection Inspector ({ObjectType.Name})"; : $"Reflection Inspector ({TargetType.Name})";
public Type ObjectType; public Type TargetType;
private CacheObjectBase[] m_allCachedMembers; private CacheObjectBase[] m_allCachedMembers;
private CacheObjectBase[] m_cachedMembersFiltered; private CacheObjectBase[] m_cachedMembersFiltered;
private int m_pageOffset;
private int m_limitPerPage = 20; public PageHelper Pages = new PageHelper();
//private int m_pageOffset;
//private int m_limitPerPage = 20;
private bool m_autoUpdate = false; private bool m_autoUpdate = false;
private string m_search = ""; private string m_search = "";
@ -35,16 +37,11 @@ namespace Explorer
public override void Init() public override void Init()
{ {
var type = ReflectionHelpers.GetActualType(Target); var type = ReflectionHelpers.GetActualType(Target);
if (type == null)
{
MelonLogger.Log($"Could not get underlying type for object! Type: {Target?.GetType().AssemblyQualifiedName}, Value ToString: {Target?.ToString()}");
DestroyWindow();
return;
}
ObjectType = type; TargetType = type;
var types = ReflectionHelpers.GetAllBaseTypes(Target); var types = ReflectionHelpers.GetAllBaseTypes(Target);
CacheMembers(types); CacheMembers(types);
if (Target is Il2CppSystem.Object ilObject) if (Target is Il2CppSystem.Object ilObject)
@ -90,13 +87,13 @@ namespace Explorer
private bool ShouldProcessMember(CacheObjectBase holder) private bool ShouldProcessMember(CacheObjectBase holder)
{ {
if (m_filter != MemberTypes.All && m_filter != holder.MemberInfo?.MemberType) return false; if (m_filter != MemberTypes.All && m_filter != holder.MemInfo?.MemberType) return false;
if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false; if (!string.IsNullOrEmpty(holder.ReflectionException) && m_hideFailedReflection) return false;
if (m_search == "" || holder.MemberInfo == null) return true; if (m_search == "" || holder.MemInfo == null) return true;
var name = holder.MemberInfo.DeclaringType.Name + "." + holder.MemberInfo.Name; var name = holder.MemInfo.DeclaringType.Name + "." + holder.MemInfo.Name;
return name.ToLower().Contains(m_search.ToLower()); return name.ToLower().Contains(m_search.ToLower());
} }
@ -118,7 +115,7 @@ namespace Explorer
} }
catch catch
{ {
MelonLogger.Log("Exception getting members for type: " + declaringType.Name); MelonLogger.Log($"Exception getting members for type: {declaringType.FullName}");
continue; continue;
} }
@ -140,11 +137,14 @@ namespace Explorer
{ {
if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method) if (member.MemberType == MemberTypes.Field || member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Method)
{ {
// ignore these var name = $"{member.DeclaringType.Name}.{member.Name}";
if (member.Name.Contains("Il2CppType") || member.Name.StartsWith("get_") || member.Name.StartsWith("set_"))
continue; // blacklist (should probably make a proper implementation)
if (name == "Type.DeclaringMethod" || member.Name.StartsWith("get_") || member.Name.StartsWith("set_")) //|| member.Name.Contains("Il2CppType")
{
continue;
}
var name = member.DeclaringType.Name + "." + member.Name;
if (member is MethodInfo mi) if (member is MethodInfo mi)
{ {
name += " ("; name += " (";
@ -161,7 +161,7 @@ namespace Explorer
try try
{ {
var cached = CacheObjectBase.GetCacheObject(null, member, target); var cached = CacheObjectBase.GetCacheObject(member, target);
if (cached != null) if (cached != null)
{ {
names.Add(name); names.Add(name);
@ -198,7 +198,7 @@ namespace Explorer
} }
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Type:</b> <color=cyan>" + ObjectType.FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(245f) }); GUILayout.Label("<b>Type:</b> <color=cyan>" + TargetType.FullName + "</color>", new GUILayoutOption[] { GUILayout.Width(245f) });
if (m_uObj) if (m_uObj)
{ {
GUILayout.Label("Name: " + m_uObj.name, null); GUILayout.Label("Name: " + m_uObj.name, null);
@ -256,41 +256,32 @@ namespace Explorer
GUILayout.Space(10); GUILayout.Space(10);
Pages.ItemCount = m_cachedMembersFiltered.Length;
// prev/next page buttons // prev/next page buttons
GUILayout.BeginHorizontal(null); GUILayout.BeginHorizontal(null);
GUILayout.Label("<b>Limit per page:</b>", new GUILayoutOption[] { GUILayout.Width(125) });
var limitString = m_limitPerPage.ToString();
limitString = GUILayout.TextField(limitString, new GUILayoutOption[] { GUILayout.Width(60) });
if (int.TryParse(limitString, out int lim))
{
m_limitPerPage = lim;
}
int count = m_cachedMembersFiltered.Length; Pages.DrawLimitInputArea();
if (count > m_limitPerPage)
if (Pages.ItemCount > Pages.ItemsPerPage)
{ {
int maxOffset = (int)Mathf.Ceil((float)(count / (decimal)m_limitPerPage)) - 1;
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) })) if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
{ {
if (m_pageOffset > 0) m_pageOffset--; Pages.TurnPage(Turn.Left, ref this.scroll);
scroll = Vector2.zero;
} }
GUI.skin.label.alignment = TextAnchor.MiddleCenter; Pages.CurrentPageLabel();
GUILayout.Label($"Page {m_pageOffset + 1}/{maxOffset + 1}", new GUILayoutOption[] { GUILayout.Width(80) });
GUI.skin.label.alignment = TextAnchor.UpperLeft;
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) })) if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
{ {
if (m_pageOffset < maxOffset) m_pageOffset++; Pages.TurnPage(Turn.Right, ref this.scroll);
scroll = Vector2.zero;
} }
} }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
// ====== BODY ====== // ====== BODY ======
scroll = GUILayout.BeginScrollView(scroll, GUI.skin.scrollView); scroll = GUIUnstrip.BeginScrollView(scroll);
GUILayout.Space(10); GUILayout.Space(10);
@ -298,38 +289,32 @@ namespace Explorer
GUILayout.BeginVertical(GUI.skin.box, null); GUILayout.BeginVertical(GUI.skin.box, null);
int index = 0;
var members = this.m_cachedMembersFiltered; var members = this.m_cachedMembersFiltered;
int offsetIndex = (m_pageOffset * m_limitPerPage) + index; int start = Pages.CalculateOffsetIndex();
if (offsetIndex >= count) for (int j = start; (j < start + Pages.ItemsPerPage && j < members.Length); j++)
{
int maxOffset = (int)Mathf.Ceil((float)(m_cachedMembersFiltered.Length / (decimal)m_limitPerPage)) - 1;
if (m_pageOffset > maxOffset)
{
m_pageOffset = 0;
}
}
for (int j = offsetIndex; (j < offsetIndex + m_limitPerPage && j < members.Length); j++)
{ {
var holder = members[j]; var holder = members[j];
GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) }); GUILayout.BeginHorizontal(new GUILayoutOption[] { GUILayout.Height(25) });
try
try {
{
holder.Draw(rect, 180f); holder.Draw(rect, 180f);
}
catch
{
GUILayout.EndHorizontal();
continue;
} }
catch { }
GUILayout.EndHorizontal(); GUILayout.EndHorizontal();
index++; // if not last element
if (!(j == (start + Pages.ItemsPerPage - 1) || j == (members.Length - 1)))
UIStyles.HorizontalLine(new Color(0.07f, 0.07f, 0.07f), true);
} }
GUILayout.EndVertical(); GUILayout.EndVertical();
GUILayout.EndScrollView(); GUIUnstrip.EndScrollView();
if (!WindowManager.TabView) if (!WindowManager.TabView)
{ {
@ -366,7 +351,8 @@ namespace Explorer
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) })) if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
{ {
m_filter = mode; m_filter = mode;
m_pageOffset = 0; Pages.PageOffset = 0;
scroll = Vector2.zero;
} }
GUI.color = Color.white; GUI.color = Color.white;
} }

View File

@ -31,7 +31,8 @@ namespace Explorer
GUI.skin.label.alignment = TextAnchor.MiddleCenter; GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) }); GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
var r = GUILayoutUtility.GetLastRect(); //var r = GUILayoutUtility.GetLastRect();
var r = GUIUnstrip.GetLastRect();
Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y)); Vector2 mouse = GUIUtility.ScreenToGUIPoint(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y));
@ -65,6 +66,7 @@ namespace Explorer
{ {
RESIZE_FAILED = true; RESIZE_FAILED = true;
MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message); MelonLogger.Log("Exception on GuiResize: " + e.GetType() + ", " + e.Message);
MelonLogger.Log(e.StackTrace);
return origRect; return origRect;
} }

View File

@ -86,6 +86,90 @@ namespace Explorer
// ========= Public Helpers ========= // ========= Public Helpers =========
public static UIWindow InspectObject(object obj, out bool createdNew, bool forceReflection = false)
{
createdNew = false;
if (Input.GetKey(KeyCode.LeftShift))
{
forceReflection = true;
}
Il2CppSystem.Object iObj = null;
if (obj is Il2CppSystem.Object isObj)
{
iObj = isObj;
}
if (!forceReflection)
{
foreach (var window in Windows)
{
bool equals = ReferenceEquals(obj, window.Target);
if (!equals && iObj is Il2CppSystem.Object iCurrent && window.Target is Il2CppSystem.Object iTarget)
{
if (iCurrent.GetIl2CppType() != iTarget.GetIl2CppType())
{
if (iCurrent is Transform transform)
{
iCurrent = transform.gameObject;
}
}
equals = iCurrent.Pointer == iTarget.Pointer;
}
if (equals)
{
FocusWindow(window);
return window;
}
}
}
createdNew = true;
if (!forceReflection && (obj is GameObject || obj is Transform))
{
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
}
else
{
return InspectReflection(obj);
}
}
private static void FocusWindow(UIWindow window)
{
if (!TabView)
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
}
else
{
TabViewWindow.Instance.TargetTabID = Windows.IndexOf(window);
}
}
private static UIWindow InspectGameObject(GameObject obj)
{
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
FocusWindow(new_window);
return new_window;
}
private static UIWindow InspectReflection(object obj)
{
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
FocusWindow(new_window);
return new_window;
}
// === Misc Helpers ===
public static bool IsMouseInWindow public static bool IsMouseInWindow
{ {
get get
@ -140,71 +224,5 @@ namespace Explorer
return rect; return rect;
} }
public static UIWindow InspectObject(object obj, out bool createdNew)
{
createdNew = false;
UnityEngine.Object uObj = null;
if (obj is UnityEngine.Object)
{
uObj = obj as UnityEngine.Object;
}
foreach (var window in Windows)
{
bool equals = ReferenceEquals(obj, window.Target);
if (!equals && uObj != null && window.Target is UnityEngine.Object uTarget)
{
equals = uObj.m_CachedPtr == uTarget.m_CachedPtr;
}
if (equals)
{
FocusWindow(window);
return window;
}
}
createdNew = true;
if (obj is GameObject || obj is Transform)
{
return InspectGameObject(obj as GameObject ?? (obj as Transform).gameObject);
}
else
{
return InspectReflection(obj);
}
}
private static void FocusWindow(UIWindow window)
{
if (!TabView)
{
GUI.BringWindowToFront(window.windowID);
GUI.FocusWindow(window.windowID);
}
else
{
TabViewWindow.Instance.TargetTabID = Windows.IndexOf(window);
}
}
private static UIWindow InspectGameObject(GameObject obj)
{
var new_window = UIWindow.CreateWindow<GameObjectWindow>(obj);
FocusWindow(new_window);
return new_window;
}
public static UIWindow InspectReflection(object obj)
{
var new_window = UIWindow.CreateWindow<ReflectionWindow>(obj);
FocusWindow(new_window);
return new_window;
}
} }
} }