More progress

This commit is contained in:
Sinai
2021-05-05 21:27:09 +10:00
parent 961ff80c6d
commit e4ff86259b
42 changed files with 1159 additions and 730 deletions

View File

@ -6,11 +6,48 @@ using System.Linq;
using System.Reflection;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Runtime;
using System.Text;
namespace UnityExplorer
{
public static class ReflectionUtility
{
static ReflectionUtility()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
CacheTypes(asm);
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
}
private static readonly Dictionary<string, Type> allCachedTypes = new Dictionary<string, Type>();
private static void CacheTypes(Assembly asm)
{
foreach (var type in asm.TryGetTypes())
{
if (allCachedTypes.ContainsKey(type.FullName))
continue;
if (type.FullName.ContainsIgnoreCase("PrivateImplementationDetails"))
continue;
allCachedTypes.Add(type.FullName, type);
}
}
private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
if (args.LoadedAssembly == null)
return;
s_cachedTypeInheritance.Clear();
s_cachedGenericParameterInheritance.Clear();
CacheTypes(args.LoadedAssembly);
}
public const BF AllFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
public static bool ValueEqual<T>(this T objA, T objB)
@ -119,28 +156,8 @@ namespace UnityExplorer
/// <returns>The Type if found, otherwise null.</returns>
public static Type GetTypeByName(string fullName)
{
s_typesByName.TryGetValue(fullName, out Type ret);
if (ret != null)
return ret;
foreach (var type in from asm in AppDomain.CurrentDomain.GetAssemblies()
from type in asm.TryGetTypes()
select type)
{
if (type.FullName == fullName)
{
ret = type;
break;
}
}
if (s_typesByName.ContainsKey(fullName))
s_typesByName[fullName] = ret;
else
s_typesByName.Add(fullName, ret);
return ret;
allCachedTypes.TryGetValue(fullName, out Type type);
return type;
}
// cache for GetBaseTypes
@ -180,49 +197,47 @@ namespace UnityExplorer
}
// cache for GetImplementationsOf
internal static readonly Dictionary<Type, HashSet<Type>> s_cachedTypeInheritance = new Dictionary<Type, HashSet<Type>>();
internal static int s_lastAssemblyCount;
internal static readonly Dictionary<string, HashSet<Type>> s_cachedTypeInheritance = new Dictionary<string, HashSet<Type>>();
internal static readonly Dictionary<string, HashSet<Type>> s_cachedGenericParameterInheritance = new Dictionary<string, HashSet<Type>>();
/// <summary>
/// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain.
/// Also works for generic parameters by analyzing the constraints.
/// </summary>
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
/// <returns>All implementations of the type in the current AppDomain.</returns>
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
{
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var key = baseType.AssemblyQualifiedName;
if (!s_cachedTypeInheritance.ContainsKey(baseType) || assemblies.Length != s_lastAssemblyCount)
if (!s_cachedTypeInheritance.ContainsKey(key))
{
if (assemblies.Length != s_lastAssemblyCount)
{
s_cachedTypeInheritance.Clear();
s_lastAssemblyCount = assemblies.Length;
}
var set = new HashSet<Type>();
if (!baseType.IsAbstract && !baseType.IsInterface)
set.Add(baseType);
foreach (var asm in assemblies)
var keys = allCachedTypes.Keys.ToArray();
for (int i = 0; i < keys.Length; i++)
{
foreach (var t in asm.TryGetTypes().Where(t => (allowAbstract || (!t.IsAbstract && !t.IsInterface))
&& (allowGeneric || !t.IsGenericType)))
var type = allCachedTypes[keys[i]];
try
{
try
{
if (baseType.IsAssignableFrom(t) && !set.Contains(t))
set.Add(t);
}
catch { }
if ((type.IsAbstract && type.IsSealed) // ignore static classes
|| (!allowAbstract && type.IsAbstract)
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
continue;
if (baseType.IsAssignableFrom(type) && !set.Contains(type))
set.Add(type);
}
catch { }
}
s_cachedTypeInheritance.Add(baseType, set);
s_cachedTypeInheritance.Add(key, set);
}
return s_cachedTypeInheritance[baseType];
return s_cachedTypeInheritance[key];
}
/// <summary>

View File

@ -25,6 +25,15 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
Instance = this;
TryLoadGameModules();
BuildDeobfuscationCache();
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoaded;
}
private void OnAssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
foreach (var type in args.LoadedAssembly.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
public override object Cast(object obj, Type castTo)
@ -71,10 +80,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
public override Type GetDeobfuscatedType(Type type)
{
if (!builtDeobCache)
BuildDeobfuscationCache();
Type ret = type;
try
{
var cppType = Il2CppType.From(type);
@ -84,14 +89,11 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
}
catch { }
return ret;
return type;
}
public override string ProcessTypeFullNameInString(Type type, string theString, ref string typeName)
{
if (!builtDeobCache)
BuildDeobfuscationCache();
if (!Il2CppTypeNotNull(type))
return theString;
@ -105,27 +107,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return theString;
}
//public override string ProcessTypeFullNameInString(Type type, string theString, ref string typeName)
//{
// if (!builtDeobCache)
// BuildDeobfuscationCache();
// try
// {
// var cppType = Il2CppType.From(type);
// if (s_deobfuscatedTypeNames.ContainsKey(cppType.FullName))
// {
// typeName = s_deobfuscatedTypeNames[cppType.FullName];
// theString = theString.Replace(cppType.FullName, typeName);
// }
// }
// catch
// {
// }
// return theString;
//}
public override Type GetActualType(object obj)
{
if (obj == null)
@ -161,9 +142,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return getType;
}
}
catch // (Exception ex)
catch (Exception ex)
{
// ExplorerCore.LogWarning("Exception in GetActualType: " + ex);
ExplorerCore.LogWarning("Exception in GetActualType: " + ex);
}
return type;
@ -175,37 +156,36 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
// keep deobfuscated type name cache, used to display proper name.
internal static Dictionary<string, string> s_deobfuscatedTypeNames = new Dictionary<string, string>();
private static bool builtDeobCache = false;
private static void BuildDeobfuscationCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
{
try
{
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
{
var cppType = Il2CppType.From(type);
if (!Il2CppToMonoType.ContainsKey(cppType.FullName))
{
Il2CppToMonoType.Add(cppType.AssemblyQualifiedName, type);
s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName);
}
}
}
catch { }
}
TryCacheDeobfuscatedType(type);
}
builtDeobCache = true;
if (s_deobfuscatedTypeNames.Count > 0)
ExplorerCore.Log($"Built deobfuscation cache, count: {s_deobfuscatedTypeNames.Count}");
}
private static void TryCacheDeobfuscatedType(Type type)
{
try
{
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
{
var cppType = Il2CppType.From(type);
if (!Il2CppToMonoType.ContainsKey(cppType.FullName))
{
Il2CppToMonoType.Add(cppType.AssemblyQualifiedName, type);
s_deobfuscatedTypeNames.Add(cppType.FullName, type.FullName);
}
}
}
catch { }
}
/// <summary>
/// Try to get the Mono (Unhollowed) Type representation of the provided <see cref="Il2CppSystem.Type"/>.
/// </summary>
@ -213,9 +193,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
/// <returns>The Mono Type if found, otherwise null.</returns>
public static Type GetMonoType(CppType cppType)
{
if (!builtDeobCache)
BuildDeobfuscationCache();
string name = cppType.AssemblyQualifiedName;
if (Il2CppToMonoType.ContainsKey(name))
@ -344,6 +321,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return false;
}
// Not currently using, not sure if its necessary anymore, was necessary to prevent crashes at one point.
public override bool IsReflectionSupported(Type type)
{
try
@ -467,7 +445,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
var valueList = new List<object>();
var hashtable = value.TryCast(typeof(Il2CppSystem.Collections.Hashtable)) as Il2CppSystem.Collections.Hashtable;
if (hashtable != null)
{
EnumerateCppHashtable(hashtable, keyList, valueList);

View File

@ -9,8 +9,8 @@ namespace UnityExplorer.Core.Search
{
UnityObject,
GameObject,
Component,
Custom,
//Component,
//Custom,
Singleton,
StaticClass
}

View File

@ -11,6 +11,114 @@ namespace UnityExplorer.Core.Search
{
public static class SearchProvider
{
private static bool Filter(Scene scene, SceneFilter filter)
{
switch (filter)
{
case SceneFilter.Any:
return true;
case SceneFilter.DontDestroyOnLoad:
return scene == SceneHandler.DontDestroyScene;
case SceneFilter.HideAndDontSave:
return scene == SceneHandler.AssetScene;
case SceneFilter.ActivelyLoaded:
return scene != SceneHandler.DontDestroyScene && scene != SceneHandler.AssetScene;
default:
return false;
}
}
internal static List<object> UnityObjectSearch(string input, string customTypeInput, SearchContext context,
ChildFilter childFilter, SceneFilter sceneFilter)
{
var results = new List<object>();
Type searchType;
switch (context)
{
case SearchContext.GameObject:
searchType = typeof(GameObject);
break;
case SearchContext.UnityObject:
default:
if (!string.IsNullOrEmpty(customTypeInput))
{
if (ReflectionUtility.GetTypeByName(customTypeInput) is Type customType)
{
if (typeof(UnityEngine.Object).IsAssignableFrom(customType))
{
searchType = customType;
break;
}
else
ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!");
}
else
ExplorerCore.LogWarning($"Could not find any type by name '{customTypeInput}'!");
}
searchType = typeof(UnityEngine.Object);
break;
}
if (searchType == null)
return results;
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(searchType);
// perform filter comparers
string nameFilter = null;
if (!string.IsNullOrEmpty(input))
nameFilter = input;
bool canGetGameObject = context == SearchContext.GameObject || typeof(Component).IsAssignableFrom(searchType);
foreach (var obj in allObjects)
{
// name check
if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ContainsIgnoreCase(nameFilter))
continue;
if (canGetGameObject)
{
var go = context == SearchContext.GameObject
? obj.TryCast<GameObject>()
: obj.TryCast<Component>().gameObject;
if (go)
{
// scene check
if (sceneFilter != SceneFilter.Any)
{
if (!Filter(go.scene, sceneFilter))
continue;
}
if (childFilter != ChildFilter.Any)
{
if (!go)
continue;
// root object check (no parent)
if (childFilter == ChildFilter.HasParent && !go.transform.parent)
continue;
else if (childFilter == ChildFilter.RootObject && go.transform.parent)
continue;
}
}
}
results.Add(obj);
}
return results;
}
internal static List<object> StaticClassSearch(string input)
{
var list = new List<object>();
@ -76,125 +184,5 @@ namespace UnityExplorer.Core.Search
return instances;
}
private static bool Filter(Scene scene, SceneFilter filter)
{
switch (filter)
{
case SceneFilter.Any:
return true;
case SceneFilter.DontDestroyOnLoad:
return scene == SceneHandler.DontDestroyScene;
case SceneFilter.HideAndDontSave:
return scene == SceneHandler.AssetScene;
case SceneFilter.ActivelyLoaded:
return scene != SceneHandler.DontDestroyScene && scene != SceneHandler.AssetScene;
default:
return false;
}
}
internal static List<object> UnityObjectSearch(string input, string customTypeInput, SearchContext context,
ChildFilter childFilter, SceneFilter sceneFilter)
{
var results = new List<object>();
Type searchType = null;
switch (context)
{
case SearchContext.GameObject:
searchType = typeof(GameObject); break;
case SearchContext.Component:
searchType = typeof(Component); break;
case SearchContext.Custom:
if (string.IsNullOrEmpty(customTypeInput))
{
ExplorerCore.LogWarning("Custom Type input must not be empty!");
return results;
}
if (ReflectionUtility.GetTypeByName(customTypeInput) is Type customType)
{
if (typeof(UnityEngine.Object).IsAssignableFrom(customType))
searchType = customType;
else
ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!");
}
else
ExplorerCore.LogWarning($"Could not find a type by the name '{customTypeInput}'!");
break;
default:
searchType = typeof(UnityEngine.Object); break;
}
if (searchType == null)
return results;
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(searchType);
// perform filter comparers
string nameFilter = null;
if (!string.IsNullOrEmpty(input))
nameFilter = input;
bool canGetGameObject = (sceneFilter != SceneFilter.Any || childFilter != ChildFilter.Any)
&& (context == SearchContext.GameObject || typeof(Component).IsAssignableFrom(searchType));
if (!canGetGameObject)
{
if (context != SearchContext.UnityObject && (sceneFilter != SceneFilter.Any || childFilter != ChildFilter.Any))
ExplorerCore.LogWarning($"Type '{searchType}' cannot have Scene or Child filters applied to it");
}
foreach (var obj in allObjects)
{
// name check
if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ContainsIgnoreCase(nameFilter))
continue;
if (canGetGameObject)
{
var go = context == SearchContext.GameObject
? obj.TryCast<GameObject>()
: obj.TryCast<Component>().gameObject;
// scene check
if (sceneFilter != SceneFilter.Any)
{
if (!go)
continue;
switch (context)
{
case SearchContext.GameObject:
case SearchContext.Custom:
case SearchContext.Component:
if (!Filter(go.scene, sceneFilter))
continue;
break;
}
}
if (childFilter != ChildFilter.Any)
{
if (!go)
continue;
// root object check (no parent)
if (childFilter == ChildFilter.HasParent && !go.transform.parent)
continue;
else if (childFilter == ChildFilter.RootObject && go.transform.parent)
continue;
}
}
results.Add(obj);
}
return results;
}
}
}

View File

@ -76,6 +76,31 @@ namespace UnityExplorer.Tests
}
}
private static void TestGeneric<T>()
{
ExplorerCore.Log("Test1 " + typeof(T).FullName);
}
private static void TestGenericClass<T>() where T : class
{
ExplorerCore.Log("Test2 " + typeof(T).FullName);
}
//private static void TestGenericMultiInterface<T>() where T : IEnumerable, IList, ICollection
//{
// ExplorerCore.Log("Test3 " + typeof(T).FullName);
//}
private static void TestComponent<T>() where T : Component
{
ExplorerCore.Log("Test3 " + typeof(T).FullName);
}
private static void TestStruct<T>() where T : struct
{
ExplorerCore.Log("Test3 " + typeof(T).FullName);
}
private static object GetRandomObject()
{
object ret = null;

View File

@ -10,9 +10,21 @@ namespace UnityExplorer
{
private static CultureInfo _enCulture = new CultureInfo("en-US");
/// <summary>
/// Check if a string contains another string, case-insensitive.
/// </summary>
public static bool ContainsIgnoreCase(this string _this, string s)
{
return _enCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
}
/// <summary>
/// Just to allow Enum to do .HasFlag() in NET 3.5
/// </summary>
public static bool HasFlag(this Enum flags, Enum value)
{
ulong num = Convert.ToUInt64(value);
return (Convert.ToUInt64(flags) & num) == num;
}
}
}