mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-16 00:07:52 +08:00
Cleanup, extend ParseUtility to dict keys
This commit is contained in:
@ -92,7 +92,7 @@ namespace UnityExplorer
|
||||
public static string ReflectionExToString(this Exception e, bool innerMost = true)
|
||||
{
|
||||
if (innerMost)
|
||||
e.GetInnerMostException();
|
||||
e = e.GetInnerMostException();
|
||||
|
||||
return $"{e.GetType()}: {e.Message}";
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
|
@ -4,7 +4,6 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer
|
||||
@ -264,7 +263,7 @@ namespace UnityExplorer
|
||||
if (!(obj is Quaternion quaternion))
|
||||
return null;
|
||||
|
||||
Vector3 vector = Quaternion.ToEulerAngles(quaternion);
|
||||
Vector3 vector = quaternion.eulerAngles;
|
||||
|
||||
return string.Format(en_US, "{0}, {1}, {2}", new object[]
|
||||
{
|
||||
|
281
src/Core/Utility/SignatureHighlighter.cs
Normal file
281
src/Core/Utility/SignatureHighlighter.cs
Normal file
@ -0,0 +1,281 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Syntax-highlights a member's signature, by either the Type name or a Type and Member together.
|
||||
/// </summary>
|
||||
public static class SignatureHighlighter
|
||||
{
|
||||
public const string NAMESPACE = "#a8a8a8";
|
||||
|
||||
public const string CONST = "#92c470";
|
||||
|
||||
public const string CLASS_STATIC = "#3a8d71";
|
||||
public const string CLASS_INSTANCE = "#2df7b2";
|
||||
|
||||
public const string STRUCT = "#0fba3a";
|
||||
public const string INTERFACE = "#9b9b82";
|
||||
|
||||
public const string FIELD_STATIC = "#8d8dc6";
|
||||
public const string FIELD_INSTANCE = "#c266ff";
|
||||
|
||||
public const string METHOD_STATIC = "#b55b02";
|
||||
public const string METHOD_INSTANCE = "#ff8000";
|
||||
|
||||
public const string PROP_STATIC = "#588075";
|
||||
public const string PROP_INSTANCE = "#55a38e";
|
||||
|
||||
public const string LOCAL_ARG = "#a6e9e9";
|
||||
|
||||
internal const string ARRAY_TOKEN = "[]";
|
||||
internal const string OPEN_COLOR = "<color=";
|
||||
internal const string CLOSE_COLOR = "</color>";
|
||||
internal const string OPEN_ITALIC = "<i>";
|
||||
internal const string CLOSE_ITALIC = "</i>";
|
||||
|
||||
public static readonly Color StringOrange = new Color(0.83f, 0.61f, 0.52f);
|
||||
public static readonly Color EnumGreen = new Color(0.57f, 0.76f, 0.43f);
|
||||
public static readonly Color KeywordBlue = new Color(0.3f, 0.61f, 0.83f);
|
||||
public static readonly Color NumberGreen = new Color(0.71f, 0.8f, 0.65f);
|
||||
|
||||
internal static string GetClassColor(Type type)
|
||||
{
|
||||
if (type.IsAbstract && type.IsSealed)
|
||||
return CLASS_STATIC;
|
||||
else if (type.IsEnum || type.IsGenericParameter)
|
||||
return CONST;
|
||||
else if (type.IsValueType)
|
||||
return STRUCT;
|
||||
else if (type.IsInterface)
|
||||
return INTERFACE;
|
||||
else
|
||||
return CLASS_INSTANCE;
|
||||
}
|
||||
|
||||
//private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156);
|
||||
|
||||
private static bool GetNamespace(Type type, out string ns)
|
||||
{
|
||||
var ret = !string.IsNullOrEmpty(ns = type.Namespace?.Trim());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string Parse(Type type, bool includeNamespace, MemberInfo memberInfo = null)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException("type");
|
||||
|
||||
var syntaxBuilder = new StringBuilder();
|
||||
|
||||
// Namespace
|
||||
|
||||
bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter);
|
||||
|
||||
if (!isGeneric)
|
||||
{
|
||||
if (includeNamespace && GetNamespace(type, out string ns))
|
||||
syntaxBuilder.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.');
|
||||
|
||||
// Declaring type
|
||||
|
||||
var declaring = type.DeclaringType;
|
||||
while (declaring != null)
|
||||
{
|
||||
syntaxBuilder.Append(HighlightType(declaring));
|
||||
syntaxBuilder.Append('.');
|
||||
declaring = declaring.DeclaringType;
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the type name
|
||||
|
||||
syntaxBuilder.Append(HighlightType(type));
|
||||
|
||||
// If memberInfo, highlight the member info
|
||||
|
||||
if (memberInfo != null)
|
||||
{
|
||||
syntaxBuilder.Append('.');
|
||||
|
||||
int start = syntaxBuilder.Length - 1;
|
||||
syntaxBuilder.Append(OPEN_COLOR)
|
||||
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
|
||||
.Append('>')
|
||||
.Append(memberInfo.Name)
|
||||
.Append(CLOSE_COLOR);
|
||||
|
||||
if (isStatic)
|
||||
{
|
||||
syntaxBuilder.Insert(start, OPEN_ITALIC);
|
||||
syntaxBuilder.Append(CLOSE_ITALIC);
|
||||
}
|
||||
|
||||
if (memberInfo is MethodInfo method)
|
||||
{
|
||||
var args = method.GetGenericArguments();
|
||||
if (args.Length > 0)
|
||||
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxBuilder.ToString();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> typeToRichType = new Dictionary<string, string>();
|
||||
|
||||
private static bool EndsWith(this StringBuilder sb, string _string)
|
||||
{
|
||||
int len = _string.Length;
|
||||
|
||||
if (sb.Length < len)
|
||||
return false;
|
||||
|
||||
int stringpos = 0;
|
||||
for (int i = sb.Length - len; i < sb.Length; i++, stringpos++)
|
||||
{
|
||||
if (sb[i] != _string[stringpos])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string HighlightType(Type type)
|
||||
{
|
||||
string key = type.ToString();
|
||||
|
||||
if (typeToRichType.ContainsKey(key))
|
||||
return typeToRichType[key];
|
||||
|
||||
var sb = new StringBuilder(type.Name);
|
||||
|
||||
bool isArray = false;
|
||||
if (sb.EndsWith(ARRAY_TOKEN))
|
||||
{
|
||||
isArray = true;
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
type = type.GetElementType();
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
|
||||
{
|
||||
sb.Insert(0, $"<color={CONST}>");
|
||||
sb.Append(CLOSE_COLOR);
|
||||
}
|
||||
else
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
// remove the `N from the end of the type name
|
||||
// this could actually be >9 in some cases, so get the length of the length string and use that.
|
||||
// eg, if it was "List`15", we would remove the ending 3 chars
|
||||
|
||||
int suffixLen = 1 + args.Length.ToString().Length;
|
||||
|
||||
// make sure the typename actually has expected "`N" format.
|
||||
if (sb[sb.Length - suffixLen] == '`')
|
||||
sb.Remove(sb.Length - suffixLen, suffixLen);
|
||||
}
|
||||
|
||||
// highlight the base name itself
|
||||
// do this after removing the `N suffix, so only the name itself is in the color tags.
|
||||
sb.Insert(0, $"{OPEN_COLOR}{GetClassColor(type)}>");
|
||||
sb.Append(CLOSE_COLOR);
|
||||
|
||||
// parse the generic args, if any
|
||||
if (args.Length > 0)
|
||||
{
|
||||
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray)
|
||||
sb.Append('[').Append(']');
|
||||
|
||||
var ret = sb.ToString();
|
||||
typeToRichType.Add(key, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ParseGenericArgs(Type[] args, bool isGenericParams = false)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
return string.Empty;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
sb.Append(',').Append(' ');
|
||||
|
||||
if (isGenericParams)
|
||||
{
|
||||
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.Append(HighlightType(args[i]));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic)
|
||||
{
|
||||
isStatic = false;
|
||||
if (memberInfo is FieldInfo fi)
|
||||
{
|
||||
if (fi.IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
return FIELD_STATIC;
|
||||
}
|
||||
|
||||
return FIELD_INSTANCE;
|
||||
}
|
||||
else if (memberInfo is MethodInfo mi)
|
||||
{
|
||||
if (mi.IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
return METHOD_STATIC;
|
||||
}
|
||||
|
||||
return METHOD_INSTANCE;
|
||||
}
|
||||
else if (memberInfo is PropertyInfo pi)
|
||||
{
|
||||
if (pi.GetAccessors(true)[0].IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
return PROP_STATIC;
|
||||
}
|
||||
|
||||
return PROP_INSTANCE;
|
||||
}
|
||||
//else if (memberInfo is EventInfo ei)
|
||||
//{
|
||||
// if (ei.GetAddMethod().IsStatic)
|
||||
// {
|
||||
// isStatic = true;
|
||||
// return EVENT_STATIC;
|
||||
// }
|
||||
|
||||
// return EVENT_INSTANCE;
|
||||
//}
|
||||
|
||||
throw new NotImplementedException(memberInfo.GetType().Name + " is not supported");
|
||||
}
|
||||
}
|
||||
}
|
165
src/Core/Utility/ToStringUtility.cs
Normal file
165
src/Core/Utility/ToStringUtility.cs
Normal file
@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class ToStringUtility
|
||||
{
|
||||
internal static Dictionary<string, MethodInfo> toStringMethods = new Dictionary<string, MethodInfo>();
|
||||
internal static Dictionary<string, MethodInfo> toStringFormattedMethods = new Dictionary<string, MethodInfo>();
|
||||
|
||||
// string allocs
|
||||
private const string nullString = "<color=grey>null</color>";
|
||||
private const string nullUnknown = nullString + " (?)";
|
||||
private const string destroyedString = "<color=red>Destroyed</color>";
|
||||
private const string untitledString = "<i><color=grey>untitled</color></i>";
|
||||
|
||||
private const string eventSystemNamespace = "UnityEngine.EventSystem";
|
||||
|
||||
public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true)
|
||||
{
|
||||
if (value.IsNullOrDestroyed() && fallbackType == null)
|
||||
return nullUnknown;
|
||||
|
||||
Type type = value?.GetActualType() ?? fallbackType;
|
||||
|
||||
string richType = SignatureHighlighter.Parse(type, includeNamespace);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (value.IsNullOrDestroyed())
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
sb.Append(nullString);
|
||||
AppendRichType(sb, richType);
|
||||
return sb.ToString();
|
||||
}
|
||||
else // destroyed unity object
|
||||
{
|
||||
sb.Append(destroyedString);
|
||||
AppendRichType(sb, richType);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (value is UnityEngine.Object obj)
|
||||
{
|
||||
var name = obj.name;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = untitledString;
|
||||
else if (name.Length > 50)
|
||||
name = $"{name.Substring(0, 50)}...";
|
||||
|
||||
sb.Append($"\"{name}\"");
|
||||
AppendRichType(sb, richType);
|
||||
}
|
||||
else if (type.FullName.StartsWith(eventSystemNamespace))
|
||||
{
|
||||
// UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text.
|
||||
sb.Append(richType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var toString = ToString(value);
|
||||
|
||||
if (type.IsGenericType
|
||||
|| toString == type.FullName
|
||||
|| toString == $"{type.FullName} {type.FullName}"
|
||||
|| toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}")
|
||||
{
|
||||
sb.Append(richType);
|
||||
}
|
||||
else // the ToString contains some actual implementation, use that value.
|
||||
{
|
||||
// prune long strings unless they're unity structs
|
||||
// (Matrix4x4 and Rect can have some longs ones that we want to display fully)
|
||||
if (toString.Length > 100 && !(type.IsValueType && type.FullName.StartsWith("UnityEngine")))
|
||||
sb.Append(toString.Substring(0, 100));
|
||||
else
|
||||
sb.Append(toString);
|
||||
|
||||
AppendRichType(sb, richType);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void AppendRichType(StringBuilder sb, string richType)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.Append('(');
|
||||
sb.Append(richType);
|
||||
sb.Append(')');
|
||||
}
|
||||
|
||||
private static string ToString(object value)
|
||||
{
|
||||
if (value.IsNullOrDestroyed())
|
||||
{
|
||||
if (value == null)
|
||||
return nullString;
|
||||
else // destroyed unity object
|
||||
return destroyedString;
|
||||
}
|
||||
|
||||
var type = value.GetActualType();
|
||||
|
||||
// Find and cache the relevant ToString method for this Type, if haven't already.
|
||||
|
||||
if (!toStringMethods.ContainsKey(type.AssemblyQualifiedName))
|
||||
{
|
||||
try
|
||||
{
|
||||
var formatMethod = type.GetMethod("ToString", ArgumentUtility.ParseArgs);
|
||||
formatMethod.Invoke(value, new object[] { "F3" });
|
||||
toStringFormattedMethods.Add(type.AssemblyQualifiedName, formatMethod);
|
||||
toStringMethods.Add(type.AssemblyQualifiedName, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var toStringMethod = type.GetMethod("ToString", ArgumentUtility.EmptyTypes);
|
||||
toStringMethods.Add(type.AssemblyQualifiedName, toStringMethod);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke the ToString method on the object
|
||||
|
||||
value = value.TryCast(type);
|
||||
|
||||
string toString;
|
||||
try
|
||||
{
|
||||
if (toStringFormattedMethods.TryGetValue(type.AssemblyQualifiedName, out MethodInfo f3method))
|
||||
toString = (string)f3method.Invoke(value, new object[] { "F3" });
|
||||
else
|
||||
toString = (string)toStringMethods[type.AssemblyQualifiedName].Invoke(value, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toString = ex.ReflectionExToString();
|
||||
}
|
||||
|
||||
toString = ReflectionUtility.ProcessTypeInString(type, toString);
|
||||
|
||||
#if CPP
|
||||
if (value is Il2CppSystem.Type cppType)
|
||||
{
|
||||
var monoType = Il2CppReflection.GetUnhollowedType(cppType);
|
||||
if (monoType != null)
|
||||
toString = ReflectionUtility.ProcessTypeInString(monoType, toString);
|
||||
}
|
||||
#endif
|
||||
|
||||
return toString;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user