Example #1
0
        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;
            }
        }
Example #2
0
        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);
        }
Example #3
0
 public PlusSearch(PE.PE pe, int methodCount, int typeDefinitionsCount, long maxMetadataUsages)
 {
     _pe = pe;
     this.methodCount          = methodCount;
     this.typeDefinitionsCount = typeDefinitionsCount;
     this.maxMetadataUsages    = maxMetadataUsages;
 }
Example #4
0
        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)}>");
        }
Example #5
0
        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)));
            }
        }
Example #6
0
        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);
        }
Example #7
0
        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);
        }
Example #8
0
        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);
        }
Example #9
0
        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);
        }
Example #10
0
        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);
        }
Example #11
0
        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();
        }