Files
gregExtractor/Il2CppMetadataScanner.cs

209 lines
7.2 KiB
C#

using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text;
namespace gregExtractor;
public sealed class Il2CppMetadataScanner
{
public SourceSnapshot Scan(string il2CppAssembliesRoot)
{
if (string.IsNullOrWhiteSpace(il2CppAssembliesRoot) || !Directory.Exists(il2CppAssembliesRoot))
throw new DirectoryNotFoundException($"Il2Cpp assemblies directory not found: {il2CppAssembliesRoot}");
string assemblyCSharpPath = Path.Combine(il2CppAssembliesRoot, "Assembly-CSharp.dll");
if (!File.Exists(assemblyCSharpPath))
throw new FileNotFoundException("Assembly-CSharp.dll not found in Il2Cpp assemblies directory.", assemblyCSharpPath);
string runtimeDir = Path.GetDirectoryName(typeof(object).Assembly.Location)
?? throw new InvalidOperationException("Runtime directory could not be resolved.");
string[] runtimeAssemblies = Directory.EnumerateFiles(runtimeDir, "*.dll", SearchOption.TopDirectoryOnly).ToArray();
string[] gameAssemblies = Directory.EnumerateFiles(il2CppAssembliesRoot, "*.dll", SearchOption.TopDirectoryOnly).ToArray();
string[] resolverPaths = runtimeAssemblies
.Concat(gameAssemblies)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
var methods = new List<MethodSnapshot>(capacity: 8192);
var resolver = new PathAssemblyResolver(resolverPaths);
using var metadataContext = new MetadataLoadContext(resolver);
foreach (string assemblyPath in gameAssemblies)
{
Assembly? assembly = null;
try
{
assembly = metadataContext.LoadFromAssemblyPath(assemblyPath);
}
catch
{
continue;
}
string normalizedAssembly = NormalizeAssemblyName(assembly.GetName().Name);
Type[] types;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types.Where(t => t != null).Cast<Type>().ToArray();
}
foreach (Type type in types)
{
if (type.IsGenericTypeDefinition)
continue;
string normalizedTypeName;
try
{
normalizedTypeName = NormalizeTypeName(type);
}
catch
{
continue;
}
MethodInfo[] declaredMethods;
try
{
declaredMethods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
}
catch
{
continue;
}
foreach (MethodInfo method in declaredMethods)
{
if (method.IsSpecialName)
continue;
string argList;
try
{
argList = string.Join(", ", method.GetParameters().Select(p => NormalizeParameterTypeName(p.ParameterType)));
}
catch
{
argList = string.Empty;
}
string patchSignature = $"{normalizedTypeName}::{method.Name}({argList})";
string bodyHash = ComputeMetadataHash(assembly.ManifestModule.ModuleVersionId, method.MetadataToken, patchSignature);
methods.Add(new MethodSnapshot
{
Assembly = normalizedAssembly,
TypeName = normalizedTypeName,
MethodName = method.Name,
SignatureKey = $"{normalizedAssembly}|{patchSignature}",
BodyHash = bodyHash,
});
}
}
}
return new SourceSnapshot
{
CreatedUtc = DateTime.UtcNow,
SourceRoot = il2CppAssembliesRoot,
FileCount = gameAssemblies.Length,
Methods = methods
.GroupBy(m => m.SignatureKey, StringComparer.Ordinal)
.Select(g => g.First())
.OrderBy(m => m.SignatureKey, StringComparer.Ordinal)
.ToList(),
};
}
private static string ComputeMetadataHash(Guid moduleVersionId, int metadataToken, string signature)
{
string raw = $"{moduleVersionId:N}:{metadataToken:X8}:{signature}";
byte[] data = SHA256.HashData(Encoding.UTF8.GetBytes(raw));
return Convert.ToHexString(data);
}
private static string NormalizeAssemblyName(string? assemblyName)
{
if (string.IsNullOrWhiteSpace(assemblyName))
return "UNKNOWN";
return assemblyName.Equals("Assembly-CSharp", StringComparison.OrdinalIgnoreCase)
? "Il2Cpp"
: assemblyName;
}
private static string NormalizeTypeName(Type type)
{
string fullName;
try
{
fullName = type.FullName?.Replace('+', '.') ?? type.Name;
}
catch
{
fullName = type.Name;
}
if (string.IsNullOrWhiteSpace(fullName))
fullName = type.Name;
if (!fullName.StartsWith("Il2Cpp.", StringComparison.Ordinal))
fullName = $"Il2Cpp.{fullName}";
return fullName;
}
private static string NormalizeParameterTypeName(Type parameterType)
{
if (parameterType.IsByRef)
parameterType = parameterType.GetElementType() ?? parameterType;
if (parameterType.IsArray)
{
Type elementType = parameterType.GetElementType() ?? parameterType;
return $"{NormalizeParameterTypeName(elementType)}[]";
}
if (parameterType.IsGenericType)
{
string genericName = parameterType.Name;
int tickIndex = genericName.IndexOf('`');
if (tickIndex >= 0)
genericName = genericName[..tickIndex];
string genericArgs = string.Join(", ", parameterType.GetGenericArguments().Select(NormalizeParameterTypeName));
return $"{genericName}<{genericArgs}>";
}
return parameterType.FullName switch
{
"System.Void" => "void",
"System.Boolean" => "bool",
"System.Byte" => "byte",
"System.SByte" => "sbyte",
"System.Int16" => "short",
"System.UInt16" => "ushort",
"System.Int32" => "int",
"System.UInt32" => "uint",
"System.Int64" => "long",
"System.UInt64" => "ulong",
"System.Single" => "float",
"System.Double" => "double",
"System.Decimal" => "decimal",
"System.String" => "string",
"System.Char" => "char",
"System.Object" => "object",
_ => parameterType.FullName?.Replace('+', '.') ?? parameterType.Name,
};
}
}