Compare commits

..

8 Commits
1.5.5 ... 1.5.7

Author SHA1 Message Date
9a059c1056 Update ScenePage.cs 2020-09-06 03:19:39 +10:00
ffb6cad8c2 1.5.7
* More fixes for failed unstripping, should fix most issues in Audica and other games
* If `GetRootSceneObjects` fails and we fall back to the manual implementation, the auto-update for root scene objects will be disabled. Instead, there will be a button to press to update the list.
* Transforms are now listed on the Components list in the GameObject inspector
* Various cleanups
2020-09-06 03:19:21 +10:00
d0a4863139 Update ScenePage.cs 2020-09-05 23:18:58 +10:00
bb8837d58c 1.5.6 hotfix
* Fix for setting CacheColor value
* Cleanup
2020-09-05 23:10:50 +10:00
a236b272c1 Update README.md 2020-09-05 20:41:48 +10:00
18de1eaf1c 1.5.6
Cleanup
2020-09-05 20:38:46 +10:00
b1264c6912 1.5.6
* Added a fallback method for GetRootSceneObjects for games where this fails.
* Fixed an issue where the `new Rect(Rect source)` constructor was failing in some games, using the normal ctor now.
* Added special support for `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Color` and `Rect` structs in the reflection inspector to allow for easier editing.
* Several improvements to GameObject Inspector, such as position/rotation freezing, local/global context, and an improved way to edit the transform values.
2020-09-05 20:27:00 +10:00
9836566e55 tidy up 2020-09-05 01:30:50 +10:00
27 changed files with 765 additions and 286 deletions

View File

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

View File

@ -137,6 +137,22 @@ namespace Explorer
{
holder = new CacheEnum();
}
else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
{
holder = new CacheVector();
}
else if (valueType == typeof(Quaternion))
{
holder = new CacheQuaternion();
}
else if (valueType == typeof(Color))
{
holder = new CacheColor();
}
else if (valueType == typeof(Rect))
{
holder = new CacheRect();
}
else if (ReflectionHelpers.IsArray(valueType) || ReflectionHelpers.IsList(valueType))
{
holder = new CacheList();

View File

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

View File

@ -8,7 +8,7 @@ namespace Explorer
{
public class CachePrimitive : CacheObjectBase
{
public enum PrimitiveTypes
public enum Types
{
Bool,
Double,
@ -20,20 +20,9 @@ namespace Explorer
private string m_valueToString;
public PrimitiveTypes PrimitiveType;
public MethodInfo ParseMethod
{
get
{
if (m_parseMethod == null)
{
m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) });
}
return m_parseMethod;
}
}
public Types PrimitiveType;
public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) }));
private MethodInfo m_parseMethod;
public override void Init()
@ -41,58 +30,39 @@ namespace Explorer
if (Value == null)
{
// this must mean it is a string. No other primitive type should be nullable.
PrimitiveType = PrimitiveTypes.String;
PrimitiveType = Types.String;
return;
}
m_valueToString = Value.ToString();
var type = Value.GetType();
var type = Value.GetType();
if (type == typeof(bool))
{
PrimitiveType = PrimitiveTypes.Bool;
PrimitiveType = Types.Bool;
}
else if (type == typeof(double))
{
PrimitiveType = PrimitiveTypes.Double;
PrimitiveType = Types.Double;
}
else if (type == typeof(float))
{
PrimitiveType = PrimitiveTypes.Float;
}
else if (IsInteger(type))
{
PrimitiveType = PrimitiveTypes.Int;
PrimitiveType = Types.Float;
}
else if (type == typeof(char))
{
PrimitiveType = PrimitiveTypes.Char;
PrimitiveType = Types.Char;
}
else if (typeof(int).IsAssignableFrom(type))
{
PrimitiveType = Types.Int;
}
else
{
PrimitiveType = PrimitiveTypes.String;
PrimitiveType = Types.String;
}
}
private static bool IsInteger(Type type)
{
// For our purposes, all types of int can be treated the same, including IntPtr.
return _integerTypes.Contains(type);
}
private static readonly HashSet<Type> _integerTypes = new HashSet<Type>
{
typeof(int),
typeof(uint),
typeof(short),
typeof(ushort),
typeof(long),
typeof(ulong),
typeof(byte),
typeof(sbyte),
typeof(IntPtr)
};
public override void UpdateValue()
{
base.UpdateValue();
@ -102,11 +72,10 @@ namespace Explorer
public override void DrawValue(Rect window, float width)
{
if (PrimitiveType == PrimitiveTypes.Bool)
if (PrimitiveType == Types.Bool)
{
var b = (bool)Value;
var color = $"<color={(b ? "lime>" : "red>")}";
var label = $"{color}{b}</color>";
var label = $"<color={(b ? "lime" : "red")}>{b}</color>";
if (CanWrite)
{
@ -150,7 +119,7 @@ namespace Explorer
}
}
public void SetValueFromInput(string value)
public void SetValueFromInput(string valueString)
{
if (MemInfo == null)
{
@ -158,16 +127,15 @@ namespace Explorer
return;
}
if (PrimitiveType == PrimitiveTypes.String)
if (PrimitiveType == Types.String)
{
Value = value;
Value = valueString;
}
else
{
try
{
var val = ParseMethod.Invoke(null, new object[] { value });
Value = val;
Value = ParseMethod.Invoke(null, new object[] { valueString });
}
catch (Exception e)
{

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ namespace Explorer
public class CppExplorer : MelonMod
{
public const string GUID = "com.sinai.cppexplorer";
public const string VERSION = "1.5.5";
public const string VERSION = "1.5.7";
public const string AUTHOR = "Sinai";
public const string NAME = "CppExplorer"

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ namespace Explorer
{
public static SearchPage Instance;
public override string Name { get => "Object Search"; set => base.Name = value; }
public override string Name { get => "Object Search"; }
private string m_searchInput = "";
private string m_typeInput = "";
@ -318,7 +318,8 @@ namespace Explorer
continue;
}
if (searchType == ReflectionHelpers.ComponentType && ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
{
// Transforms shouldn't really be counted as Components, skip them.
// They're more akin to GameObjects.

View File

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

View File

@ -13,13 +13,6 @@ using Harmony;
namespace Explorer
{
// This is a copy+paste of UnityEngine source code, fixed for Il2Cpp.
// Taken from dnSpy output using Unity 2018.4.20.
// Subject to Unity's License and ToS.
// https://unity3d.com/legal/terms-of-service
// https://unity3d.com/legal/terms-of-service/software
public class GUIUnstrip
{
public static int s_ScrollControlId;
@ -41,9 +34,6 @@ namespace Explorer
// ======= public methods ======= //
// Fix for GUILayoutUtility.GetLastRect().
// Calls UnstripExtensions.GetLastUnstripped().
public static Rect GetLastRect()
{
EventType type = Event.current.type;
@ -59,8 +49,6 @@ namespace Explorer
return last;
}
// Simple unstrips for HorizontalScrollbar and VerticalScrollbar, they just call the Scroller unstrip.
public static float HorizontalScrollbar(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle style)
{
return Scroller_Impl(position, value, size, leftValue, rightValue, style,
@ -80,7 +68,6 @@ namespace Explorer
}
// Fix for BeginScrollView.
// Uses several manually unstripped methods.
public static Vector2 BeginScrollView(Vector2 scroll, params GUILayoutOption[] options)
{
@ -98,7 +85,7 @@ namespace Explorer
}
}
// Try manual unstripping implementation.
// Try manual implementation.
if (!ManualUnstripFailed)
{
try
@ -107,7 +94,7 @@ namespace Explorer
}
catch (Exception e)
{
MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_Impl: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
MelonLogger.Log("Exception on GUIUnstrip.BeginScrollView_ImplLayout: " + e.GetType() + ", " + e.Message + "\r\n" + e.StackTrace);
ManualUnstripFailed = true;
return scroll;
@ -136,8 +123,6 @@ namespace Explorer
// ======= private methods ======= //
// Actual unstrip of GUILayout.BeginScrollView()
private static Vector2 BeginScrollView_ImplLayout(Vector2 scrollPosition, bool alwaysShowHorizontal, bool alwaysShowVertical,
GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background, params GUILayoutOption[] options)
{
@ -171,8 +156,6 @@ namespace Explorer
);
}
// Actual unstrip of GUI.BeginScrollView()
private static Vector2 BeginScrollView_Impl(Rect position, Vector2 scrollPosition, Rect viewRect, bool alwaysShowHorizontal,
bool alwaysShowVertical, GUIStyle horizontalScrollbar, GUIStyle verticalScrollbar, GUIStyle background)
{
@ -204,7 +187,7 @@ namespace Explorer
ScrollStack.Push(scrollViewState);
Rect screenRect = new Rect(position);
Rect screenRect = new Rect(position.x, position.y, position.width, position.height);
EventType type = Event.current.type;
if (type != EventType.Layout)
{
@ -296,8 +279,6 @@ namespace Explorer
return scrollPosition;
}
// Actual unstrip of GUI.EndScrollView()
private static void EndScrollView_Impl(bool handleScrollWheel)
{
GUIUtility.CheckOnGUI();
@ -336,8 +317,6 @@ namespace Explorer
}
}
// Actual unstrip of GUI.Scroller
private static float Scroller_Impl(Rect position, float value, float size, float leftValue, float rightValue, GUIStyle slider, GUIStyle thumb, GUIStyle leftButton, GUIStyle rightButton, bool horiz)
{
GUIUtility.CheckOnGUI();
@ -384,8 +363,6 @@ namespace Explorer
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)
{
if (id == 0)
@ -396,8 +373,6 @@ namespace Explorer
return sliderHandler.Handle();
}
// Actual unstrip of GUI.ScrollerRepeatButton
private static bool ScrollerRepeatButton_Impl(int scrollerID, Rect rect, GUIStyle style)
{
bool result = false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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