using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using LibCpp2IL.BinaryStructures;
using LibCpp2IL.Metadata;
namespace LibCpp2IL.Reflection;
///
/// Per-context reflection caches. This is the non-static equivalent of .
///
public sealed class LibCpp2IlReflectionCache
{
private readonly ConcurrentDictionary<(string, string?), Il2CppTypeDefinition?> _cachedTypes = new();
private readonly ConcurrentDictionary _cachedTypesByFullName = new();
private readonly Dictionary> _typeIndices = new();
// lazily built, see BuildOnce
private readonly object _lazyLock = new();
private Dictionary>? _methodIndices;
private Dictionary>? _fieldIndices;
private Dictionary? _propertyIndices;
private Dictionary? _fieldDeclaringTypes;
private readonly Dictionary _primitiveTypeCache = new();
public Dictionary PrimitiveTypeDefinitions { get; } = new();
private readonly Dictionary, Il2CppType> _il2CppTypeCache = new();
private LibCpp2IlContext? _context;
private LibCpp2IlContext Context =>
_context ?? throw new InvalidOperationException("LibCpp2IlReflectionCache must be initialized before use");
public void Reset()
{
_context = null;
_cachedTypes.Clear();
_cachedTypesByFullName.Clear();
lock (_typeIndices)
_typeIndices.Clear();
lock (_lazyLock)
{
_methodIndices = null;
_fieldIndices = null;
_propertyIndices = null;
_fieldDeclaringTypes = null;
}
_primitiveTypeCache.Clear();
PrimitiveTypeDefinitions.Clear();
_il2CppTypeCache.Clear();
}
internal void Init(LibCpp2IlContext context)
{
Reset();
_context = context;
for (var e = Il2CppTypeEnum.IL2CPP_TYPE_VOID; e <= Il2CppTypeEnum.IL2CPP_TYPE_STRING; e++)
_primitiveTypeCache[e] = context.Binary.AllTypes.First(t => t.Type == e && t.Byref == 0);
_primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_TYPEDBYREF] = context.Binary.AllTypes.FirstOrDefault(t => t.Type == Il2CppTypeEnum.IL2CPP_TYPE_TYPEDBYREF && t.Byref == 0)
?? context.Metadata.typeDefs.First(t => t.DeclaringAssembly?.Name is "mscorlib.dll" && t.Namespace is "System" && t.Name is "TypedReference").RawType;
for (var e = Il2CppTypeEnum.IL2CPP_TYPE_I; e <= Il2CppTypeEnum.IL2CPP_TYPE_U; e++)
_primitiveTypeCache[e] = context.Binary.AllTypes.First(t => t.Type == e && t.Byref == 0);
_primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_OBJECT] = context.Binary.AllTypes.First(t => t.Type == Il2CppTypeEnum.IL2CPP_TYPE_OBJECT && t.Byref == 0);
for (var i = 0; i < context.Metadata.TypeDefinitionCount; i++)
{
var typeDefinition = context.Metadata.typeDefs[i];
_typeIndices[typeDefinition] = Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(i);
var type = typeDefinition.RawType;
if (type.Type.IsIl2CppPrimitive())
PrimitiveTypeDefinitions[type.Type] = typeDefinition;
}
foreach (var type in context.Binary.AllTypes)
{
if (type.Type is not Il2CppTypeEnum.IL2CPP_TYPE_CLASS and not Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE)
continue;
if (type.Byref == 0)
_il2CppTypeCache[type.Data.ClassIndex] = type;
}
}
public Il2CppTypeDefinition? GetType(string name, string? @namespace = null)
{
var key = (name, @namespace);
if (!_cachedTypes.ContainsKey(key))
{
var typeDef = Context.Metadata.typeDefs.FirstOrDefault(td => td.Name == name && (@namespace == null || @namespace == td.Namespace));
_cachedTypes[key] = typeDef;
}
return _cachedTypes[key];
}
public Il2CppTypeDefinition? GetTypeByFullName(string fullName)
{
if (!_cachedTypesByFullName.ContainsKey(fullName))
{
var typeDef = Context.Metadata.typeDefs.FirstOrDefault(td => td.FullName == fullName);
_cachedTypesByFullName[fullName] = typeDef;
}
return _cachedTypesByFullName[fullName];
}
public Il2CppTypeDefinition? GetTypeDefinitionByTypeIndex(Il2CppVariableWidthIndex index)
{
if (index.IsNull) return null;
var type = Context.Binary.GetType(index);
return type.CoerceToUnderlyingTypeDefinition();
}
[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")]
public Il2CppVariableWidthIndex GetTypeIndexFromType(Il2CppTypeDefinition typeDefinition)
=> _typeIndices.GetOrDefault(typeDefinition, Il2CppVariableWidthIndex.Null);
// A half-built dictionary must never be visible to another thread, so the fully populated one is
// published in a single reference write rather than filled in place behind a count check.
private T BuildOnce(ref T? cache, Func build) where T : class
{
if (Volatile.Read(ref cache) is { } existing)
return existing;
lock (_lazyLock)
{
if (cache is { } raced)
return raced;
var built = build();
Volatile.Write(ref cache, built);
return built;
}
}
public Il2CppVariableWidthIndex GetMethodIndexFromMethod(Il2CppMethodDefinition methodDefinition)
{
var indices = BuildOnce(ref _methodIndices, () =>
{
var result = new Dictionary>();
for (var i = 0; i < Context.Metadata.MethodDefinitionCount; i++)
result[Context.Metadata.methodDefs[i]] = Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(i);
return result;
});
return indices.GetOrDefault(methodDefinition, Il2CppVariableWidthIndex.Null);
}
public Il2CppVariableWidthIndex GetFieldIndexFromField(Il2CppFieldDefinition fieldDefinition)
{
var indices = BuildOnce(ref _fieldIndices, () =>
{
var result = new Dictionary>();
for (var i = 0; i < Context.Metadata.fieldDefs.Length; i++)
result[Context.Metadata.fieldDefs[i]] = Il2CppVariableWidthIndex.MakeTemporaryForFixedWidthUsage(i);
return result;
});
return indices[fieldDefinition];
}
public int GetPropertyIndexFromProperty(Il2CppPropertyDefinition propertyDefinition)
{
var indices = BuildOnce(ref _propertyIndices, () =>
{
var result = new Dictionary();
for (var i = 0; i < Context.Metadata.propertyDefs.Length; i++)
result[Context.Metadata.propertyDefs[i]] = i;
return result;
});
return indices[propertyDefinition];
}
public Il2CppTypeDefinition GetDeclaringTypeFromField(Il2CppFieldDefinition fieldDefinition)
{
var declaringTypes = BuildOnce(ref _fieldDeclaringTypes, () =>
{
var result = new Dictionary();
foreach (var declaringType in Context.Metadata.typeDefs)
foreach (var field in declaringType.Fields ?? [])
result[field] = declaringType;
return result;
});
return declaringTypes[fieldDefinition];
}
public Il2CppType? GetTypeFromDefinition(Il2CppTypeDefinition definition)
{
switch (definition.FullName)
{
case "System.SByte": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_I1];
case "System.Int16": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_I2];
case "System.Int32": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_I4];
case "System.Int64": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_I8];
case "System.Byte": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_U1];
case "System.UInt16": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_U2];
case "System.UInt32": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_U4];
case "System.UInt64": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_U8];
case "System.IntPtr": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_I];
case "System.UIntPtr": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_U];
case "System.Single": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_R4];
case "System.Double": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_R8];
case "System.Boolean": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN];
case "System.Char": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_CHAR];
case "System.String": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_STRING];
case "System.Void": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_VOID];
case "System.TypedReference": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_TYPEDBYREF];
case "System.Object": return _primitiveTypeCache[Il2CppTypeEnum.IL2CPP_TYPE_OBJECT];
}
var index = definition.TypeIndex;
if (_il2CppTypeCache.TryGetValue(index, out var cachedType))
return cachedType;
foreach (var type in Context.Binary.AllTypes)
{
if (type.Type is not Il2CppTypeEnum.IL2CPP_TYPE_CLASS and not Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE)
continue;
if (type.Data.ClassIndex.Value == index.Value && type.Byref == 0)
{
lock (_il2CppTypeCache)
_il2CppTypeCache[index] = type;
return type;
}
}
return null;
}
}