public static void ConfigureHierarchy(Il2CppMetadata metadata, PE.PE theDll) { //Iterate through all types defined in the metadata for (var typeIndex = 0; typeIndex < metadata.typeDefs.Length; typeIndex++) { var type = metadata.typeDefs[typeIndex]; var definition = SharedState.TypeDefsByIndex[typeIndex]; //Resolve this type's base class and import if required. if (type.parentIndex >= 0) { var parent = theDll.types[type.parentIndex]; var parentRef = Utils.ImportTypeInto(definition, parent, theDll, metadata); definition.BaseType = parentRef; } //Resolve this type's interfaces and import each if required. for (var i = 0; i < type.interfaces_count; i++) { var interfaceType = theDll.types[metadata.interfaceIndices[type.interfacesStart + i]]; var interfaceTypeRef = Utils.ImportTypeInto(definition, interfaceType, theDll, metadata); definition.Interfaces.Add(new InterfaceImplementation(interfaceTypeRef)); } SharedState.TypeDefsByIndex[typeIndex] = definition; } }
internal static string GetTypeName(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppTypeDefinition typeDef) { var ret = String.Empty; if (typeDef.declaringTypeIndex != -1) { ret += GetTypeName(metadata, cppAssembly, cppAssembly.types[typeDef.declaringTypeIndex]) + "."; } ret += metadata.GetStringFromIndex(typeDef.nameIndex); var names = new List <string>(); if (typeDef.genericContainerIndex >= 0) { var genericContainer = metadata.genericContainers[typeDef.genericContainerIndex]; for (int i = 0; i < genericContainer.type_argc; i++) { var genericParameterIndex = genericContainer.genericParameterStart + i; var param = metadata.genericParameters[genericParameterIndex]; names.Add(metadata.GetStringFromIndex(param.nameIndex)); } ret = ret.Replace($"`{genericContainer.type_argc}", ""); ret += $"<{String.Join(", ", names)}>"; } return(ret); }
public PlusSearch(PE.PE pe, int methodCount, int typeDefinitionsCount, long maxMetadataUsages) { _pe = pe; this.methodCount = methodCount; this.typeDefinitionsCount = typeDefinitionsCount; this.maxMetadataUsages = maxMetadataUsages; }
internal static string GetGenericTypeParams(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppGenericInst genericInst) { var typeNames = new List <string>(); var pointers = cppAssembly.ReadClassArrayAtVirtualAddress <ulong>(genericInst.type_argv, (long)genericInst.type_argc); for (uint i = 0; i < genericInst.type_argc; ++i) { var oriType = cppAssembly.GetIl2CppType(pointers[i]); typeNames.Add(GetTypeName(metadata, cppAssembly, oriType)); } return($"<{String.Join(", ", typeNames)}>"); }
public static TypeReference ImportTypeInto(MemberReference importInto, Il2CppType toImport, PE.PE theDll, Il2CppMetadata metadata) { var moduleDefinition = importInto.Module; switch (toImport.type) { case Il2CppTypeEnum.IL2CPP_TYPE_OBJECT: return(moduleDefinition.ImportReference(typeof(object))); case Il2CppTypeEnum.IL2CPP_TYPE_VOID: return(moduleDefinition.ImportReference(typeof(void))); case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: return(moduleDefinition.ImportReference(typeof(bool))); case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: return(moduleDefinition.ImportReference(typeof(char))); case Il2CppTypeEnum.IL2CPP_TYPE_I1: return(moduleDefinition.ImportReference(typeof(sbyte))); case Il2CppTypeEnum.IL2CPP_TYPE_U1: return(moduleDefinition.ImportReference(typeof(byte))); case Il2CppTypeEnum.IL2CPP_TYPE_I2: return(moduleDefinition.ImportReference(typeof(short))); case Il2CppTypeEnum.IL2CPP_TYPE_U2: return(moduleDefinition.ImportReference(typeof(ushort))); case Il2CppTypeEnum.IL2CPP_TYPE_I4: return(moduleDefinition.ImportReference(typeof(int))); case Il2CppTypeEnum.IL2CPP_TYPE_U4: return(moduleDefinition.ImportReference(typeof(uint))); case Il2CppTypeEnum.IL2CPP_TYPE_I: return(moduleDefinition.ImportReference(typeof(IntPtr))); case Il2CppTypeEnum.IL2CPP_TYPE_U: return(moduleDefinition.ImportReference(typeof(UIntPtr))); case Il2CppTypeEnum.IL2CPP_TYPE_I8: return(moduleDefinition.ImportReference(typeof(long))); case Il2CppTypeEnum.IL2CPP_TYPE_U8: return(moduleDefinition.ImportReference(typeof(ulong))); case Il2CppTypeEnum.IL2CPP_TYPE_R4: return(moduleDefinition.ImportReference(typeof(float))); case Il2CppTypeEnum.IL2CPP_TYPE_R8: return(moduleDefinition.ImportReference(typeof(double))); case Il2CppTypeEnum.IL2CPP_TYPE_STRING: return(moduleDefinition.ImportReference(typeof(string))); case Il2CppTypeEnum.IL2CPP_TYPE_TYPEDBYREF: return(moduleDefinition.ImportReference(typeof(TypedReference))); case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: { var typeDefinition = SharedState.TypeDefsByIndex[toImport.data.classIndex]; return(moduleDefinition.ImportReference(typeDefinition)); } case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: { var arrayType = theDll.ReadClassAtVirtualAddress <Il2CppArrayType>(toImport.data.array); var oriType = theDll.GetIl2CppType(arrayType.etype); return(new ArrayType(ImportTypeInto(importInto, oriType, theDll, metadata), arrayType.rank)); } case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: { var genericClass = theDll.ReadClassAtVirtualAddress <Il2CppGenericClass>(toImport.data.generic_class); var typeDefinition = SharedState.TypeDefsByIndex[genericClass.typeDefinitionIndex]; var genericInstanceType = new GenericInstanceType(moduleDefinition.ImportReference(typeDefinition)); var genericInst = theDll.ReadClassAtVirtualAddress <Il2CppGenericInst>(genericClass.context.class_inst); var pointers = theDll.GetPointers(genericInst.type_argv, (long)genericInst.type_argc); foreach (var pointer in pointers) { var oriType = theDll.GetIl2CppType(pointer); genericInstanceType.GenericArguments.Add(ImportTypeInto(importInto, oriType, theDll, metadata)); } return(genericInstanceType); } case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: { var oriType = theDll.GetIl2CppType(toImport.data.type); return(new ArrayType(ImportTypeInto(importInto, oriType, theDll, metadata))); } case Il2CppTypeEnum.IL2CPP_TYPE_VAR: { if (SharedState.GenericParamsByIndex.TryGetValue(toImport.data.genericParameterIndex, out var genericParameter)) { return(genericParameter); } var param = metadata.genericParameters[toImport.data.genericParameterIndex]; var genericName = metadata.GetStringFromIndex(param.nameIndex); if (importInto is MethodDefinition methodDefinition) { genericParameter = new GenericParameter(genericName, methodDefinition.DeclaringType); methodDefinition.DeclaringType.GenericParameters.Add(genericParameter); SharedState.GenericParamsByIndex.Add(toImport.data.genericParameterIndex, genericParameter); return(genericParameter); } var typeDefinition = (TypeDefinition)importInto; genericParameter = new GenericParameter(genericName, typeDefinition); typeDefinition.GenericParameters.Add(genericParameter); SharedState.GenericParamsByIndex.Add(toImport.data.genericParameterIndex, genericParameter); return(genericParameter); } case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: { if (SharedState.GenericParamsByIndex.TryGetValue(toImport.data.genericParameterIndex, out var genericParameter)) { return(genericParameter); } var methodDefinition = (MethodDefinition)importInto; var param = metadata.genericParameters[toImport.data.genericParameterIndex]; var genericName = metadata.GetStringFromIndex(param.nameIndex); genericParameter = new GenericParameter(genericName, methodDefinition); methodDefinition.GenericParameters.Add(genericParameter); SharedState.GenericParamsByIndex.Add(toImport.data.genericParameterIndex, genericParameter); return(genericParameter); } case Il2CppTypeEnum.IL2CPP_TYPE_PTR: { var oriType = theDll.GetIl2CppType(toImport.data.type); return(new PointerType(ImportTypeInto(importInto, oriType, theDll, metadata))); } default: return(moduleDefinition.ImportReference(typeof(object))); } }
internal static string GetTypeName(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppType type, bool fullName = false) { string ret; switch (type.type) { case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: { var typeDef = metadata.typeDefs[type.data.classIndex]; ret = String.Empty; if (fullName) { ret = metadata.GetStringFromIndex(typeDef.namespaceIndex); if (ret != String.Empty) { ret += "."; } } ret += GetTypeName(metadata, cppAssembly, typeDef); break; } case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: { var genericClass = cppAssembly.ReadClassAtVirtualAddress <Il2CppGenericClass>(type.data.generic_class); var typeDef = metadata.typeDefs[genericClass.typeDefinitionIndex]; ret = metadata.GetStringFromIndex(typeDef.nameIndex); var genericInst = cppAssembly.ReadClassAtVirtualAddress <Il2CppGenericInst>(genericClass.context.class_inst); ret = ret.Replace($"`{genericInst.type_argc}", ""); ret += GetGenericTypeParams(metadata, cppAssembly, genericInst); break; } case Il2CppTypeEnum.IL2CPP_TYPE_VAR: case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: { var param = metadata.genericParameters[type.data.genericParameterIndex]; ret = metadata.GetStringFromIndex(param.nameIndex); break; } case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: { var arrayType = cppAssembly.ReadClassAtVirtualAddress <Il2CppArrayType>(type.data.array); var oriType = cppAssembly.GetIl2CppType(arrayType.etype); ret = $"{GetTypeName(metadata, cppAssembly, oriType)}[{new string(',', arrayType.rank - 1)}]"; break; } case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: { var oriType = cppAssembly.GetIl2CppType(type.data.type); ret = $"{GetTypeName(metadata, cppAssembly, oriType)}[]"; break; } case Il2CppTypeEnum.IL2CPP_TYPE_PTR: { var oriType = cppAssembly.GetIl2CppType(type.data.type); ret = $"{GetTypeName(metadata, cppAssembly, oriType)}*"; break; } default: ret = TypeString[(int)type.type]; break; } return(ret); }
internal static object GetDefaultValue(int dataIndex, int typeIndex, Il2CppMetadata metadata, PE.PE theDll) { var pointer = metadata.GetDefaultValueFromIndex(dataIndex); if (pointer > 0) { var defaultValueType = theDll.types[typeIndex]; metadata.Position = pointer; switch (defaultValueType.type) { case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: return(metadata.ReadBoolean()); case Il2CppTypeEnum.IL2CPP_TYPE_U1: return(metadata.ReadByte()); case Il2CppTypeEnum.IL2CPP_TYPE_I1: return(metadata.ReadSByte()); case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: return(BitConverter.ToChar(metadata.ReadBytes(2), 0)); case Il2CppTypeEnum.IL2CPP_TYPE_U2: return(metadata.ReadUInt16()); case Il2CppTypeEnum.IL2CPP_TYPE_I2: return(metadata.ReadInt16()); case Il2CppTypeEnum.IL2CPP_TYPE_U4: return(metadata.ReadUInt32()); case Il2CppTypeEnum.IL2CPP_TYPE_I4: return(metadata.ReadInt32()); case Il2CppTypeEnum.IL2CPP_TYPE_U8: return(metadata.ReadUInt64()); case Il2CppTypeEnum.IL2CPP_TYPE_I8: return(metadata.ReadInt64()); case Il2CppTypeEnum.IL2CPP_TYPE_R4: return(metadata.ReadSingle()); case Il2CppTypeEnum.IL2CPP_TYPE_R8: return(metadata.ReadDouble()); case Il2CppTypeEnum.IL2CPP_TYPE_STRING: var len = metadata.ReadInt32(); return(Encoding.UTF8.GetString(metadata.ReadBytes(len))); } } return(null); }
internal static List <GlobalIdentifier> MapGlobalIdentifiers(Il2CppMetadata metadata, PE.PE cppAssembly) { //Classes var ret = metadata.metadataUsageDic[1] .Select(kvp => new { kvp, type = cppAssembly.types[kvp.Value] }) .Select(t => new GlobalIdentifier { Name = Utils.GetTypeName(metadata, cppAssembly, t.type, true), Offset = cppAssembly.metadataUsages[t.kvp.Key], IdentifierType = GlobalIdentifier.Type.TYPE }).ToList(); //Idx 2 is exactly the same thing ret.AddRange(metadata.metadataUsageDic[2] .Select(kvp => new { kvp, type = cppAssembly.types[kvp.Value] }) .Select(t => new GlobalIdentifier { Name = Utils.GetTypeName(metadata, cppAssembly, t.type, true), Offset = cppAssembly.metadataUsages[t.kvp.Key], IdentifierType = GlobalIdentifier.Type.TYPE }) ); //Methods is idx 3 //Don't @ me, i prefer LINQ to foreach loops. //But that said this could be optimised to less t-ing ret.AddRange(metadata.metadataUsageDic[3] .Select(kvp => new { kvp, method = metadata.methodDefs[kvp.Value] }) .Select(t => new { t.kvp, t.method, type = metadata.typeDefs[t.method.declaringType] }) .Select(t => new { t.kvp, t.method, typeName = Utils.GetTypeName(metadata, cppAssembly, t.type) }) .Select(t => new { t.kvp, methodName = t.typeName + "." + metadata.GetStringFromIndex(t.method.nameIndex) + "()" }) .Select(t => new GlobalIdentifier { IdentifierType = GlobalIdentifier.Type.METHOD, Name = t.methodName, Offset = cppAssembly.metadataUsages[t.kvp.Key] }) ); //Fields is idx 4 ret.AddRange(metadata.metadataUsageDic[4] .Select(kvp => new { kvp, fieldRef = metadata.fieldRefs[kvp.Value] }) .Select(t => new { t.kvp, t.fieldRef, type = cppAssembly.types[t.fieldRef.typeIndex] }) .Select(t => new { t.type, t.kvp, t.fieldRef, typeDef = metadata.typeDefs[t.type.data.classIndex] }) .Select(t => new { t.type, t.kvp, fieldDef = metadata.fieldDefs[t.typeDef.firstFieldIdx + t.fieldRef.fieldIndex] }) .Select(t => new { t.kvp, fieldName = Utils.GetTypeName(metadata, cppAssembly, t.type, true) + "." + metadata.GetStringFromIndex(t.fieldDef.nameIndex) }) .Select(t => new GlobalIdentifier { IdentifierType = GlobalIdentifier.Type.FIELD, Name = t.fieldName, Offset = cppAssembly.metadataUsages[t.kvp.Key] }) ); //Literals ret.AddRange(metadata.metadataUsageDic[5] .Select(kvp => new GlobalIdentifier { IdentifierType = GlobalIdentifier.Type.LITERAL, Offset = cppAssembly.metadataUsages[kvp.Key], Name = $"{metadata.GetStringLiteralFromIndex(kvp.Value)}" }) ); foreach (var kvp in metadata.metadataUsageDic[6]) //kIl2CppMetadataUsageMethodRef { var methodSpec = cppAssembly.methodSpecs[kvp.Value]; var methodDef = metadata.methodDefs[methodSpec.methodDefinitionIndex]; var typeDef = metadata.typeDefs[methodDef.declaringType]; var typeName = Utils.GetTypeName(metadata, cppAssembly, typeDef); if (methodSpec.classIndexIndex != -1) { var classInst = cppAssembly.genericInsts[methodSpec.classIndexIndex]; typeName += Utils.GetGenericTypeParams(metadata, cppAssembly, classInst); } var methodName = typeName + "." + metadata.GetStringFromIndex(methodDef.nameIndex) + "()"; if (methodSpec.methodIndexIndex != -1) { var methodInst = cppAssembly.genericInsts[methodSpec.methodIndexIndex]; methodName += Utils.GetGenericTypeParams(metadata, cppAssembly, methodInst); } ret.Add(new GlobalIdentifier { Name = methodName, IdentifierType = GlobalIdentifier.Type.METHOD, Offset = cppAssembly.metadataUsages[kvp.Key] }); } return(ret); }
private static List <CppMethodData> ProcessTypeContents(Il2CppMetadata metadata, PE.PE cppAssembly, Il2CppTypeDefinition cppTypeDefinition, TypeDefinition ilTypeDefinition) { var typeMetaText = new StringBuilder(); typeMetaText.Append($"Type: {ilTypeDefinition.FullName}:") .Append($"\n\tBase Class: \n\t\t{ilTypeDefinition.BaseType}\n") .Append("\n\tInterfaces:\n"); foreach (var iface in ilTypeDefinition.Interfaces) { typeMetaText.Append($"\t\t{iface.InterfaceType.FullName}\n"); } //field var fields = new List <FieldInType>(); var baseFields = new List <FieldDefinition>(); var current = ilTypeDefinition; while (current.BaseType != null) { var targetName = current.BaseType.FullName; if (targetName.Contains("<")) // types with generic parameters (Type'1<T>) are stored as Type'1, so I just removed the part that causes trouble and called it a day { targetName = targetName.Substring(0, targetName.IndexOf("<")); } current = SharedState.AllTypeDefinitions.Find(t => t.FullName == targetName); if (current == null) { typeMetaText.Append("WARN: Type " + targetName + " is not defined yet\n"); break; } baseFields.InsertRange(0, current.Fields.Where(f => !f.IsStatic)); // each loop we go one inheritage level deeper, so these "new" fields should be inserted before the previous ones } //Handle base fields var fieldOffset = baseFields.Aggregate((ulong)(ilTypeDefinition.MetadataType == MetadataType.Class ? 0x10 : 0x0), (currentOffset, baseField) => HandleField(baseField.FieldType, currentOffset, baseField.Name, baseField, ref fields, typeMetaText)); var lastFieldIdx = cppTypeDefinition.firstFieldIdx + cppTypeDefinition.field_count; for (var fieldIdx = cppTypeDefinition.firstFieldIdx; fieldIdx < lastFieldIdx; ++fieldIdx) { var fieldDef = metadata.fieldDefs[fieldIdx]; var fieldType = cppAssembly.types[fieldDef.typeIndex]; var fieldName = metadata.GetStringFromIndex(fieldDef.nameIndex); var fieldTypeRef = Utils.ImportTypeInto(ilTypeDefinition, fieldType, cppAssembly, metadata); var fieldDefinition = new FieldDefinition(fieldName, (FieldAttributes)fieldType.attrs, fieldTypeRef); ilTypeDefinition.Fields.Add(fieldDefinition); //Field default values if (fieldDefinition.HasDefault) { var fieldDefault = metadata.GetFieldDefaultValueFromIndex(fieldIdx); if (fieldDefault != null && fieldDefault.dataIndex != -1) { fieldDefinition.Constant = Utils.GetDefaultValue(fieldDefault.dataIndex, fieldDefault.typeIndex, metadata, cppAssembly); } } if (!fieldDefinition.IsStatic) { fieldOffset = HandleField(fieldTypeRef, fieldOffset, fieldName, fieldDefinition, ref fields, typeMetaText); } } fields.Sort(); //By offset SharedState.FieldsByType[ilTypeDefinition] = fields; //Methods var lastMethodId = cppTypeDefinition.firstMethodId + cppTypeDefinition.method_count; var typeMethods = new List <CppMethodData>(); Il2CppGenericContainer genericContainer; for (var methodId = cppTypeDefinition.firstMethodId; methodId < lastMethodId; ++methodId) { var methodDef = metadata.methodDefs[methodId]; var methodReturnType = cppAssembly.types[methodDef.returnType]; var methodName = metadata.GetStringFromIndex(methodDef.nameIndex); var methodDefinition = new MethodDefinition(methodName, (MethodAttributes)methodDef.flags, ilTypeDefinition.Module.ImportReference(typeof(void))); //TODO: For Unity 2019 we'll need to fix the imageindex param from 0 to the actual index var offsetInRam = cppAssembly.GetMethodPointer(methodDef.methodIndex, methodId, 0, methodDef.token); long offsetInFile = offsetInRam == 0 ? 0 : cppAssembly.MapVirtualAddressToRaw(offsetInRam); typeMetaText.Append($"\n\tMethod: {methodName}:\n") .Append($"\t\tFile Offset 0x{offsetInFile:X8}\n") .Append($"\t\tRam Offset 0x{offsetInRam:x8}\n") .Append($"\t\tVirtual Method Slot: {methodDef.slot}\n"); var bytes = new List <byte>(); var offset = offsetInFile; while (true) { var b = cppAssembly.raw[offset]; if (b == 0xC3 && cppAssembly.raw[offset + 1] == 0xCC) { break; } if (b == 0xCC && bytes.Count > 0 && (bytes.Last() == 0xcc || bytes.Last() == 0xc3)) { break; } bytes.Add(b); offset++; } typeMetaText.Append($"\t\tMethod Length: {bytes.Count} bytes\n"); typeMethods.Add(new CppMethodData { MethodName = methodName, MethodId = methodId, MethodBytes = bytes.ToArray(), MethodOffsetRam = offsetInRam }); ilTypeDefinition.Methods.Add(methodDefinition); methodDefinition.ReturnType = Utils.ImportTypeInto(methodDefinition, methodReturnType, cppAssembly, metadata); if (methodDefinition.HasBody && ilTypeDefinition.BaseType?.FullName != "System.MulticastDelegate") { var ilprocessor = methodDefinition.Body.GetILProcessor(); ilprocessor.Append(ilprocessor.Create(OpCodes.Nop)); } SharedState.MethodsByIndex.Add(methodId, methodDefinition); //Method Params for (var paramIdx = 0; paramIdx < methodDef.parameterCount; ++paramIdx) { var parameterDef = metadata.parameterDefs[methodDef.parameterStart + paramIdx]; var parameterName = metadata.GetStringFromIndex(parameterDef.nameIndex); var parameterType = cppAssembly.types[parameterDef.typeIndex]; var parameterTypeRef = Utils.ImportTypeInto(methodDefinition, parameterType, cppAssembly, metadata); var parameterDefinition = new ParameterDefinition(parameterName, (ParameterAttributes)parameterType.attrs, parameterTypeRef); methodDefinition.Parameters.Add(parameterDefinition); //Default values for params if (parameterDefinition.HasDefault) { var parameterDefault = metadata.GetParameterDefaultValueFromIndex(methodDef.parameterStart + paramIdx); if (parameterDefault != null && parameterDefault.dataIndex != -1) { parameterDefinition.Constant = Utils.GetDefaultValue(parameterDefault.dataIndex, parameterDefault.typeIndex, metadata, cppAssembly); } } typeMetaText.Append($"\n\t\tParameter {paramIdx}:\n") .Append($"\t\t\tName: {parameterName}\n") .Append($"\t\t\tType: {(parameterTypeRef.Namespace == "" ? "<None>" : parameterTypeRef.Namespace)}.{parameterTypeRef.Name}\n") .Append($"\t\t\tDefault Value: {parameterDefinition.Constant}"); } if (methodDef.genericContainerIndex >= 0) { genericContainer = metadata.genericContainers[methodDef.genericContainerIndex]; if (genericContainer.type_argc > methodDefinition.GenericParameters.Count) { for (var j = 0; j < genericContainer.type_argc; j++) { var genericParameterIndex = genericContainer.genericParameterStart + j; var param = metadata.genericParameters[genericParameterIndex]; var genericName = metadata.GetStringFromIndex(param.nameIndex); if (!SharedState.GenericParamsByIndex.TryGetValue(genericParameterIndex, out var genericParameter)) { genericParameter = new GenericParameter(genericName, methodDefinition); methodDefinition.GenericParameters.Add(genericParameter); SharedState.GenericParamsByIndex.Add(genericParameterIndex, genericParameter); } else { if (!methodDefinition.GenericParameters.Contains(genericParameter)) { methodDefinition.GenericParameters.Add(genericParameter); } } } } } if (methodDef.slot < ushort.MaxValue) { SharedState.VirtualMethodsBySlot[methodDef.slot] = methodDefinition; } SharedState.MethodsByAddress[offsetInRam] = methodDefinition; } //Properties var lastPropertyId = cppTypeDefinition.firstPropertyId + cppTypeDefinition.propertyCount; for (var propertyId = cppTypeDefinition.firstPropertyId; propertyId < lastPropertyId; ++propertyId) { var propertyDef = metadata.propertyDefs[propertyId]; var propertyName = metadata.GetStringFromIndex(propertyDef.nameIndex); TypeReference propertyType = null; MethodDefinition getter = null; MethodDefinition setter = null; if (propertyDef.get >= 0) { getter = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + propertyDef.get]; propertyType = getter.ReturnType; } if (propertyDef.set >= 0) { setter = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + propertyDef.set]; if (propertyType == null) { propertyType = setter.Parameters[0].ParameterType; } } var propertyDefinition = new PropertyDefinition(propertyName, (PropertyAttributes)propertyDef.attrs, propertyType) { GetMethod = getter, SetMethod = setter }; ilTypeDefinition.Properties.Add(propertyDefinition); } //Events var lastEventId = cppTypeDefinition.firstEventId + cppTypeDefinition.eventCount; for (var eventId = cppTypeDefinition.firstEventId; eventId < lastEventId; ++eventId) { var eventDef = metadata.eventDefs[eventId]; var eventName = metadata.GetStringFromIndex(eventDef.nameIndex); var eventType = cppAssembly.types[eventDef.typeIndex]; var eventTypeRef = Utils.ImportTypeInto(ilTypeDefinition, eventType, cppAssembly, metadata); var eventDefinition = new EventDefinition(eventName, (EventAttributes)eventType.attrs, eventTypeRef); if (eventDef.add >= 0) { eventDefinition.AddMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + eventDef.add]; } if (eventDef.remove >= 0) { eventDefinition.RemoveMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + eventDef.remove]; } if (eventDef.raise >= 0) { eventDefinition.InvokeMethod = SharedState.MethodsByIndex[cppTypeDefinition.firstMethodId + eventDef.raise]; } ilTypeDefinition.Events.Add(eventDefinition); } File.WriteAllText(Path.Combine(Path.GetFullPath("cpp2il_out"), "types", ilTypeDefinition.Module.Assembly.Name.Name, ilTypeDefinition.Name.Replace("<", "_").Replace(">", "_") + "_metadata.txt"), typeMetaText.ToString()); if (cppTypeDefinition.genericContainerIndex < 0) { return(typeMethods); //Finished processing if not generic } genericContainer = metadata.genericContainers[cppTypeDefinition.genericContainerIndex]; if (genericContainer.type_argc <= ilTypeDefinition.GenericParameters.Count) { return(typeMethods); //Finished processing } for (var i = 0; i < genericContainer.type_argc; i++) { var genericParameterIndex = genericContainer.genericParameterStart + i; var param = metadata.genericParameters[genericParameterIndex]; var genericName = metadata.GetStringFromIndex(param.nameIndex); if (!SharedState.GenericParamsByIndex.TryGetValue(genericParameterIndex, out var genericParameter)) { genericParameter = new GenericParameter(genericName, ilTypeDefinition); ilTypeDefinition.GenericParameters.Add(genericParameter); SharedState.GenericParamsByIndex.Add(genericParameterIndex, genericParameter); } else { if (ilTypeDefinition.GenericParameters.Contains(genericParameter)) { continue; } ilTypeDefinition.GenericParameters.Add(genericParameter); } } return(typeMethods); }
public static List <(TypeDefinition type, List <CppMethodData> methods)> ProcessAssemblyTypes(Il2CppMetadata metadata, PE.PE theDll, Il2CppAssemblyDefinition imageDef) { var firstTypeDefinition = SharedState.TypeDefsByIndex[imageDef.firstTypeIndex]; var currentAssembly = firstTypeDefinition.Module.Assembly; //Ensure type directory exists Directory.CreateDirectory(Path.Combine(Path.GetFullPath("cpp2il_out"), "types", currentAssembly.Name.Name)); var lastTypeIndex = imageDef.firstTypeIndex + imageDef.typeCount; var methods = new List <(TypeDefinition type, List <CppMethodData> methods)>(); for (var index = imageDef.firstTypeIndex; index < lastTypeIndex; index++) { var typeDef = metadata.typeDefs[index]; var typeDefinition = SharedState.TypeDefsByIndex[index]; SharedState.AllTypeDefinitions.Add(typeDefinition); SharedState.MonoToCppTypeDefs[typeDefinition] = typeDef; methods.Add((type: typeDefinition, methods: ProcessTypeContents(metadata, theDll, typeDef, typeDefinition))); } return(methods); }
public static void Main(string[] args) { Console.WriteLine("===Cpp2IL by Samboy063==="); Console.WriteLine("A Tool to Reverse Unity's \"il2cpp\" Build Process."); Console.WriteLine("Running on " + Environment.OSVersion.Platform); Options commandLineOptions = null; Parser.Default.ParseArguments <Options>(args).WithParsed(options => { commandLineOptions = options; }); if (commandLineOptions == null) { return; } string loc; //TODO: No longer needed // if (Environment.OSVersion.Platform == PlatformID.Win32Windows || Environment.OSVersion.Platform == PlatformID.Win32NT) // { // loc = Registry.GetValue( // "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Steam App 1020340", // "InstallLocation", null) as string; // } // else if (Environment.OSVersion.Platform == PlatformID.Unix) // { // // $HOME/.local/share/Steam/steamapps/common/Audica // loc = Environment.GetEnvironmentVariable("HOME") + "/.local/share/Steam/steamapps/common/Audica"; // } // else // { // loc = null; // } // // if (args.Length != 1 && loc == null) // { // Console.WriteLine( // "Couldn't auto-detect Audica installation folder (via steam), and you didn't tell me where it is."); // PrintUsage(); // return; // } var baseGamePath = commandLineOptions.GamePath; Console.WriteLine("Using path: " + baseGamePath); if (!Directory.Exists(baseGamePath)) { Console.WriteLine("Specified path does not exist: " + baseGamePath); PrintUsage(); return; } var assemblyPath = Path.Combine(baseGamePath, "GameAssembly.dll"); var exeName = Path.GetFileNameWithoutExtension(Directory.GetFiles(baseGamePath).First(f => f.EndsWith(".exe") && !blacklistedExecutableFilenames.Contains(f))); if (commandLineOptions.ExeName != null) { exeName = commandLineOptions.ExeName; Console.WriteLine($"Using OVERRIDDEN game name: {exeName}"); } else { Console.WriteLine($"Auto-detected game name: {exeName}"); } var unityPlayerPath = Path.Combine(baseGamePath, $"{exeName}.exe"); var metadataPath = Path.Combine(baseGamePath, $"{exeName}_Data", "il2cpp_data", "Metadata", "global-metadata.dat"); if (!File.Exists(assemblyPath) || !File.Exists(unityPlayerPath) || !File.Exists(metadataPath)) { Console.WriteLine("Invalid path specified. Failed to find one of the following:\n" + $"\t{assemblyPath}\n" + $"\t{unityPlayerPath}\n" + $"\t{metadataPath}\n"); PrintUsage(); return; } Console.WriteLine($"Located game EXE: {unityPlayerPath}"); Console.WriteLine($"Located global-metadata: {metadataPath}"); Console.WriteLine("\nAttempting to determine Unity version..."); var unityVer = FileVersionInfo.GetVersionInfo(unityPlayerPath); var unityVerUseful = new[] { unityVer.FileMajorPart, unityVer.FileMinorPart, unityVer.FileBuildPart }; Console.WriteLine("This game is built with Unity version " + string.Join(".", unityVerUseful)); if (unityVerUseful[0] <= 4) { Console.WriteLine("Unable to determine a valid unity version. Aborting."); return; } Console.WriteLine("Reading metadata..."); Metadata = Il2CppMetadata.ReadFrom(metadataPath, unityVerUseful); Console.WriteLine("Reading binary / game assembly..."); var PEBytes = File.ReadAllBytes(assemblyPath); Console.WriteLine($"\t-Initializing MemoryStream of {PEBytes.Length} bytes, parsing sections, and initializing with auto+ mode."); ThePE = new PE.PE(new MemoryStream(PEBytes, 0, PEBytes.Length, false, true), Metadata.maxMetadataUsages); if (!ThePE.PlusSearch(Metadata.methodDefs.Count(x => x.methodIndex >= 0), Metadata.typeDefs.Length)) { Console.WriteLine("Initialize failed. Aborting."); return; } //Dump DLLs #region Assembly Generation var resolver = new RegistryAssemblyResolver(); var moduleParams = new ModuleParameters { Kind = ModuleKind.Dll, AssemblyResolver = resolver, MetadataResolver = new MetadataResolver(resolver) }; Console.WriteLine("Building assemblies..."); Console.WriteLine("\tPass 1: Creating types..."); Assemblies = AssemblyBuilder.CreateAssemblies(Metadata, resolver, moduleParams); Console.WriteLine("\tPass 2: Setting parents and handling inheritance..."); //Stateful method, no return value AssemblyBuilder.ConfigureHierarchy(Metadata, ThePE); Console.WriteLine("\tPass 3: Handling Fields, methods, and properties (THIS MAY TAKE A WHILE)..."); var methods = new List <(TypeDefinition type, List <CppMethodData> methods)>(); for (var imageIndex = 0; imageIndex < Metadata.assemblyDefinitions.Length; imageIndex++) { Console.WriteLine($"\t\tProcessing DLL {imageIndex + 1} of {Metadata.assemblyDefinitions.Length}..."); methods.AddRange(AssemblyBuilder.ProcessAssemblyTypes(Metadata, ThePE, Metadata.assemblyDefinitions[imageIndex])); } //Invert dict for CppToMono SharedState.CppToMonoTypeDefs = SharedState.MonoToCppTypeDefs.ToDictionary(i => i.Value, i => i.Key); Console.WriteLine("\tPass 4: Handling SerializeFields..."); //Add serializefield to monobehaviors #region SerializeFields var unityEngineAssembly = Assemblies.Find(x => x.MainModule.Types.Any(t => t.Namespace == "UnityEngine" && t.Name == "SerializeField")); if (unityEngineAssembly != null) { var serializeFieldMethod = unityEngineAssembly.MainModule.Types.First(x => x.Name == "SerializeField").Methods.First(); foreach (var imageDef in Metadata.assemblyDefinitions) { var lastTypeIndex = imageDef.firstTypeIndex + imageDef.typeCount; for (var typeIndex = imageDef.firstTypeIndex; typeIndex < lastTypeIndex; typeIndex++) { var typeDef = Metadata.typeDefs[typeIndex]; var typeDefinition = SharedState.TypeDefsByIndex[typeIndex]; //Fields var lastFieldIdx = typeDef.firstFieldIdx + typeDef.field_count; for (var fieldIdx = typeDef.firstFieldIdx; fieldIdx < lastFieldIdx; ++fieldIdx) { var fieldDef = Metadata.fieldDefs[fieldIdx]; var fieldName = Metadata.GetStringFromIndex(fieldDef.nameIndex); var fieldDefinition = typeDefinition.Fields.First(x => x.Name == fieldName); //Get attributes and look for the serialize field attribute. var attributeIndex = Metadata.GetCustomAttributeIndex(imageDef, fieldDef.customAttributeIndex, fieldDef.token); if (attributeIndex < 0) { continue; } var attributeTypeRange = Metadata.attributeTypeRanges[attributeIndex]; for (var attributeIdxIdx = 0; attributeIdxIdx < attributeTypeRange.count; attributeIdxIdx++) { var attributeTypeIndex = Metadata.attributeTypes[attributeTypeRange.start + attributeIdxIdx]; var attributeType = ThePE.types[attributeTypeIndex]; if (attributeType.type != Il2CppTypeEnum.IL2CPP_TYPE_CLASS) { continue; } var cppAttribType = Metadata.typeDefs[attributeType.data.classIndex]; var attributeName = Metadata.GetStringFromIndex(cppAttribType.nameIndex); if (attributeName != "SerializeField") { continue; } var customAttribute = new CustomAttribute(typeDefinition.Module.ImportReference(serializeFieldMethod)); fieldDefinition.CustomAttributes.Add(customAttribute); } } } } } #endregion KeyFunctionAddresses keyFunctionAddresses = null; if (!commandLineOptions.SkipAnalysis) { Console.WriteLine("\tPass 5: Locating Globals..."); var globals = AssemblyBuilder.MapGlobalIdentifiers(Metadata, ThePE); Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.TYPE)} type globals"); Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.METHOD)} method globals"); Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.FIELD)} field globals"); Console.WriteLine($"\t\tFound {globals.Count(g => g.IdentifierType == GlobalIdentifier.Type.LITERAL)} string literals"); SharedState.Globals.AddRange(globals); foreach (var globalIdentifier in globals) { SharedState.GlobalsDict[globalIdentifier.Offset] = globalIdentifier; } Console.WriteLine("\tPass 6: Looking for key functions..."); //This part involves decompiling known functions to search for other function calls Disassembler.Translator.IncludeAddress = true; Disassembler.Translator.IncludeBinary = true; keyFunctionAddresses = KeyFunctionAddresses.Find(methods, ThePE); } #endregion Utils.BuildPrimitiveMappings(); var outputPath = Path.GetFullPath("cpp2il_out"); if (!Directory.Exists(outputPath)) { Directory.CreateDirectory(outputPath); } var methodOutputDir = Path.Combine(outputPath, "types"); if (!Directory.Exists(methodOutputDir)) { Directory.CreateDirectory(methodOutputDir); } Console.WriteLine("Saving Header DLLs to " + outputPath + "..."); GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency; foreach (var assembly in Assemblies) { var dllPath = Path.Combine(outputPath, assembly.MainModule.Name); assembly.Write(dllPath); if (assembly.Name.Name != "Assembly-CSharp" || commandLineOptions.SkipAnalysis) { continue; } Console.WriteLine("Dumping method bytes to " + methodOutputDir); Directory.CreateDirectory(Path.Combine(methodOutputDir, assembly.Name.Name)); //Write methods var imageIndex = Assemblies.IndexOf(assembly); var allUsedMnemonics = new List <ud_mnemonic_code>(); var counter = 0; var toProcess = methods.Where(tuple => tuple.type.Module.Assembly == assembly).ToList(); //Sort alphabetically by type. toProcess.Sort((a, b) => String.Compare(a.type.FullName, b.type.FullName, StringComparison.Ordinal)); var thresholds = new[] { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }.ToList(); var nextThreshold = thresholds.First(); var successfullyProcessed = 0; var failedProcess = 0; var startTime = DateTime.Now; var methodTaintDict = new ConcurrentDictionary <string, AsmDumper.TaintReason>(); thresholds.RemoveAt(0); toProcess .AsParallel() .ForAll(tuple => { var(type, methodData) = tuple; counter++; var pct = 100 * ((decimal)counter / toProcess.Count); if (pct > nextThreshold) { lock (thresholds) { //Check again to prevent races if (pct > nextThreshold) { var elapsedSoFar = DateTime.Now - startTime; var rate = counter / elapsedSoFar.TotalSeconds; var remaining = toProcess.Count - counter; Console.WriteLine($"{nextThreshold}% ({counter} classes in {Math.Round(elapsedSoFar.TotalSeconds)} sec, ~{Math.Round(rate)} classes / sec, {remaining} classes remaining, approx {Math.Round(remaining / rate + 5)} sec remaining)"); nextThreshold = thresholds.First(); thresholds.RemoveAt(0); } } } // Console.WriteLine($"\t-Dumping methods in type {counter}/{methodBytes.Count}: {type.Key}"); try { var filename = Path.Combine(methodOutputDir, assembly.Name.Name, type.Name.Replace("<", "_").Replace(">", "_") + "_methods.txt"); var typeDump = new StringBuilder("Type: " + type.Name + "\n\n"); foreach (var method in methodData) { var methodDef = Metadata.methodDefs[method.MethodId]; var methodStart = method.MethodOffsetRam; var methodDefinition = SharedState.MethodsByIndex[method.MethodId]; var taintResult = new AsmDumper(methodDefinition, method, methodStart, keyFunctionAddresses, ThePE) .AnalyzeMethod(typeDump, ref allUsedMnemonics); var key = new StringBuilder(); key.Append(methodDefinition.DeclaringType.FullName).Append("::").Append(methodDefinition.Name); methodDefinition.MethodSignatureFullName(key); methodTaintDict[key.ToString()] = taintResult; if (taintResult != AsmDumper.TaintReason.UNTAINTED) { Interlocked.Increment(ref failedProcess); } else { Interlocked.Increment(ref successfullyProcessed); } } lock (type) File.WriteAllText(filename, typeDump.ToString()); } catch (Exception e) { Console.WriteLine("Failed to dump methods for type " + type.Name + " " + e); } }); var total = successfullyProcessed + failedProcess; var elapsed = DateTime.Now - startTime; Console.WriteLine($"Finished method processing in {elapsed.Ticks} ticks (about {Math.Round(elapsed.TotalSeconds, 1)} seconds), at an overall rate of about {Math.Round(toProcess.Count / elapsed.TotalSeconds)} methods/sec"); Console.WriteLine($"Processed {total} methods, {successfullyProcessed} ({Math.Round(successfullyProcessed * 100.0 / total, 2)}%) successfully, {failedProcess} ({Math.Round(failedProcess * 100.0 / total, 2)}%) with errors."); Console.WriteLine("Breakdown By Taint Reason:"); foreach (var reason in Enum.GetValues(typeof(AsmDumper.TaintReason))) { var count = (decimal)methodTaintDict.Values.Count(v => v == (AsmDumper.TaintReason)reason); Console.WriteLine($"{reason}: {count} (about {Math.Round(count * 100 / total, 1)}%)"); } var summary = new StringBuilder(); foreach (var keyValuePair in methodTaintDict) { summary.Append("\t") .Append(keyValuePair.Key) .Append(Utils.Repeat(" ", 250 - keyValuePair.Key.Length)) .Append(keyValuePair.Value) .Append(" (") .Append((int)keyValuePair.Value) .Append(")") .Append("\n"); } if (false) { Console.WriteLine("By Package:"); var keys = methodTaintDict .Select(kvp => kvp.Key) .GroupBy( GetPackageName, className => className, (packageName, keys) => new { package = packageName, classes = keys.ToList() }) .ToList(); foreach (var key in keys) { var resultLine = new StringBuilder(); var totalClassCount = key.classes.Count; resultLine.Append($"\tIn package {key.package} ({totalClassCount} classes): "); foreach (var reason in Enum.GetValues(typeof(AsmDumper.TaintReason))) { var count = (decimal)methodTaintDict.Where(kvp => key.classes.Contains(kvp.Key)).Count(v => v.Value == (AsmDumper.TaintReason)reason); resultLine.Append(reason).Append(":").Append(count).Append($" ({Math.Round(count * 100 / totalClassCount, 1)}%) "); } Console.WriteLine(resultLine.ToString()); } } File.WriteAllText(Path.Combine(outputPath, "method_statuses.txt"), summary.ToString()); Console.WriteLine($"Wrote file: {Path.Combine(outputPath, "method_statuses.txt")}"); // Console.WriteLine("Assembly uses " + allUsedMnemonics.Count + " mnemonics"); } // Console.WriteLine("[Finished. Press enter to exit]"); // Console.ReadLine(); }