Example #1
0
        public static void DoPass(RewriteGlobalContext context)
        {
            var typesUnstripped = 0;

            foreach (var unityAssembly in context.UnityAssemblies.Assemblies)
            {
                var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name.Name);
                if (processedAssembly == null)
                {
                    var newAssembly = new AssemblyRewriteContext(context, unityAssembly,
                                                                 AssemblyDefinition.CreateAssembly(unityAssembly.Name, unityAssembly.MainModule.Name,
                                                                                                   ModuleKind.Dll));
                    context.AddAssemblyContext(unityAssembly.Name.Name, newAssembly);
                    processedAssembly = newAssembly;
                }
                var imports = processedAssembly.Imports;

                foreach (var unityType in unityAssembly.MainModule.Types)
                {
                    ProcessType(processedAssembly, unityType, null, imports, ref typesUnstripped);
                }
            }

            LogSupport.Trace(""); // end the progress message
            LogSupport.Trace($"{typesUnstripped} types restored");
        }
        public IEnumerable <IntPtr> JumpTargets()
        {
            var formatter = new IntelFormatter();
            var builder   = new StringBuilder();

            while (true)
            {
                myDecoder.Decode(out var instruction);
                builder.Clear();
                formatter.Format(in instruction, new StringOutput(builder));
                LogSupport.Trace($"Decoded instruction: {builder}");
                if (myDecoder.InvalidNoMoreBytes)
                {
                    yield break;
                }
                if (instruction.FlowControl == FlowControl.Return)
                {
                    yield break;
                }

                if (instruction.FlowControl == FlowControl.UnconditionalBranch || instruction.FlowControl == FlowControl.Call)
                {
                    yield return((IntPtr)ExtractTargetAddress(in instruction));

                    if (instruction.FlowControl == FlowControl.UnconditionalBranch)
                    {
                        yield break;
                    }
                }
            }
        }
Example #3
0
        public static void DoPass(RewriteGlobalContext context)
        {
            var unityAssemblyFiles = Directory.EnumerateFiles(context.Options.UnityBaseLibsDir, "*.dll");
            var loadedAssemblies   = unityAssemblyFiles.Select(it =>
                                                               AssemblyDefinition.ReadAssembly(it, new ReaderParameters(ReadingMode.Deferred))).ToList();

            var typesUnstripped = 0;

            foreach (var unityAssembly in loadedAssemblies)
            {
                var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name.Name);
                if (processedAssembly == null)
                {
                    continue;
                }
                var imports = processedAssembly.Imports;

                foreach (var unityType in unityAssembly.MainModule.Types)
                {
                    ProcessType(processedAssembly, unityType, null, imports, ref typesUnstripped);
                }
            }

            LogSupport.Trace(""); // end the progress message
            LogSupport.Trace($"{typesUnstripped} types restored");
        }
        public static void DoPass(RewriteGlobalContext context)
        {
            foreach (var assemblyContext in context.Assemblies)
            {
                foreach (var typeContext in assemblyContext.Types)
                {
                    GenerateStaticProxy(assemblyContext, typeContext);
                }
            }

            LogSupport.Trace($"\nTokenless method count: {ourTokenlessMethods}");
        }
        public unsafe Unity2018_0NativeClassStructHandler()
        {
            Il2CppClassU2018_0 ex = new Il2CppClassU2018_0();
            byte *addr            = (byte *)&ex;

            LogSupport.Trace($"Size:                         {sizeof(Il2CppClassU2018_0)}");
            LogSupport.Trace($"typeHierarchyDepth Offset:    {&ex.typeHierarchyDepth - addr}");
            LogSupport.Trace($"genericRecursionDepth Offset: {&ex.genericRecursionDepth - addr}");
            LogSupport.Trace($"rank Offset:                  {&ex.rank - addr}");
            LogSupport.Trace($"minimumAlignment Offset:      {&ex.minimumAlignment - addr}");
            //LogSupport.Trace($"naturalAligment Offset:       {&ex.Part2.naturalAligment - addr}");
            LogSupport.Trace($"packingSize Offset:           {&ex.packingSize - addr}");
            LogSupport.Trace($"bitfield_1 Offset:            {(byte*)&ex.bitfield_1 - addr}");
            LogSupport.Trace($"bitfield_2 Offset:            {(byte*)&ex.bitfield_2 - addr}");
        }
        private static void HookClassFromType()
        {
            var lib = LoadLibrary("GameAssembly.dll");
            var classFromTypeEntryPoint = GetProcAddress(lib, nameof(IL2CPP.il2cpp_class_from_il2cpp_type));

            LogSupport.Trace($"il2cpp_class_from_il2cpp_type entry address: {classFromTypeEntryPoint}");

            var targetMethod = XrefScannerLowLevel.JumpTargets(classFromTypeEntryPoint).Single();

            LogSupport.Trace($"Xref scan target: {targetMethod}");

            if (targetMethod == IntPtr.Zero)
            {
                return;
            }

            ourOriginalTypeToClassMethod = Detour.Detour(targetMethod, new TypeToClassDelegate(ClassFromTypePatch));
            LogSupport.Trace("il2cpp_class_from_il2cpp_type patched");
        }
        public static void GenerateInvokerMethodBody(MethodDefinition newMethod, FieldDefinition delegateField, TypeDefinition delegateType, TypeRewriteContext enclosingType, AssemblyKnownImports imports)
        {
            var body = newMethod.Body.GetILProcessor();

            body.Emit(OpCodes.Ldsfld, delegateField);
            if (newMethod.HasThis)
            {
                body.Emit(OpCodes.Ldarg_0);
                body.Emit(OpCodes.Call, imports.Il2CppObjectBaseToPointerNotNull);
            }

            var argOffset = newMethod.HasThis ? 1 : 0;

            for (var i = 0; i < newMethod.Parameters.Count; i++)
            {
                var param     = newMethod.Parameters[i];
                var paramType = param.ParameterType;
                if (paramType.IsValueType || paramType.IsByReference && paramType.GetElementType().IsValueType)
                {
                    body.Emit(OpCodes.Ldarg, i + argOffset);
                }
                else
                {
                    body.EmitObjectToPointer(param.ParameterType, param.ParameterType, enclosingType, i + argOffset, false, true, true, out var refVar);
                    if (refVar != null)
                    {
                        LogSupport.Trace($"Method {newMethod} has a reference-typed ref parameter, this will be ignored");
                    }
                }
            }

            body.Emit(OpCodes.Call, delegateType.Methods.Single(it => it.Name == "Invoke"));
            if (!newMethod.ReturnType.IsValueType)
            {
                var pointerVar = new VariableDefinition(imports.IntPtr);
                newMethod.Body.Variables.Add(pointerVar);
                body.Emit(OpCodes.Stloc, pointerVar);
                var loadInstr = body.Create(OpCodes.Ldloc, pointerVar);
                body.EmitPointerToObject(newMethod.ReturnType, newMethod.ReturnType, enclosingType, loadInstr, false, false);
            }
            body.Emit(OpCodes.Ret);
        }
Example #8
0
 /// <summary>
 /// Initializes Unity interface for specified Unity version.
 /// </summary>
 /// <example>For Unity 2018.4.20, call <c>Initialize(2018, 4, 20)</c></example>
 public static void Initialize(int majorVersion, int minorVersion, int patchVersion)
 {
     if (majorVersion <= 2018)
     {
         if (minorVersion < 4)
         {
             LogSupport.Trace("Using Unity2018.0 handler");
             ourHandler = new Unity2018_0NativeClassStructHandler();
         }
         else
         {
             LogSupport.Trace("Using Unity2018.4 handler");
             ourHandler = new Unity2018_4NativeClassStructHandler();
         }
     }
     else
     {
         LogSupport.Trace("Using Unity2019 handler");
         ourHandler = new Unity2019NativeClassStructHandler();
     }
 }
        public unsafe Unity2019NativeClassStructHandler()
        {
            Il2CppClassU2019_32 ex = new Il2CppClassU2019_32();
            byte *addr             = (byte *)&ex;

            LogSupport.Trace($"Size:                         {sizeof(Il2CppClassU2019_32)}");
            LogSupport.Trace($"klass Offset:       {(byte*)&ex.Part1.klass - addr}");
            LogSupport.Trace($"typeHierarchy Offset:       {(byte*)&ex.Part1.typeHierarchy - addr}");
            LogSupport.Trace($"unity_user_data Offset:       {(byte*)&ex.unity_user_data - addr}");
            LogSupport.Trace($"cctor_finished Offset:       {(byte*)&ex.Part2.cctor_finished - addr}");
            LogSupport.Trace($"cctor_thread Offset:       {(byte*)&ex.Part2.cctor_thread - addr}");
            LogSupport.Trace($"genericContainerIndex Offset:       {(byte*)&ex.Part2.genericContainerIndex - addr}");
            LogSupport.Trace($"field_count Offset:       {(byte*)&ex.Part2.field_count - addr}");
            LogSupport.Trace($"interface_offsets_count Offset:       {(byte*)&ex.Part2.interface_offsets_count - addr}");
            LogSupport.Trace($"typeHierarchyDepth Offset:    {&ex.typeHierarchyDepth - addr}");
            LogSupport.Trace($"genericRecursionDepth Offset: {&ex.genericRecursionDepth - addr}");
            LogSupport.Trace($"rank Offset:                  {&ex.rank - addr}");
            LogSupport.Trace($"minimumAlignment Offset:      {&ex.minimumAlignment - addr}");
            LogSupport.Trace($"naturalAligmnent Offset:       {&ex.naturalAlignment - addr}");
            LogSupport.Trace($"packingSize Offset:           {&ex.packingSize - addr}");
            LogSupport.Trace($"bitfield_1 Offset:            {(byte*)&ex.bitfield_1 - addr}");
            LogSupport.Trace($"bitfield_2 Offset:            {(byte*)&ex.bitfield_2 - addr}");
        }
        private static void HookClassFromType()
        {
            var lib = LoadLibrary("UserAssembly.dll");
            var classFromTypeEntryPoint = GetProcAddress(lib, nameof(IL2CPP.il2cpp_class_from_il2cpp_type));

            LogSupport.Trace($"il2cpp_class_from_il2cpp_type entry address: {classFromTypeEntryPoint}");

            var targetMethod = XrefScannerLowLevel.JumpTargets(classFromTypeEntryPoint).Single();

            LogSupport.Trace($"Xref scan target: {targetMethod}");

            if (targetMethod == IntPtr.Zero)
            {
                return;
            }

            IntPtr *targetVarPointer = &targetMethod;

            DoHook((IntPtr)targetVarPointer,
                   Marshal.GetFunctionPointerForDelegate(new TypeToClassDelegate(ClassFromTypePatch)));
            ourOriginalTypeToClassMethod = Marshal.GetDelegateForFunctionPointer <TypeToClassDelegate>(targetMethod);

            LogSupport.Trace("il2cpp_class_from_il2cpp_type patched");
        }
        public static void DoPass(RewriteGlobalContext context)
        {
            int methodsUnstripped = 0;
            int methodsIgnored    = 0;

            foreach (var unityAssembly in context.UnityAssemblies.Assemblies)
            {
                var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name.Name);
                if (processedAssembly == null)
                {
                    continue;
                }
                var imports = processedAssembly.Imports;

                foreach (var unityType in unityAssembly.MainModule.Types)
                {
                    var processedType = processedAssembly.TryGetTypeByName(unityType.FullName);
                    if (processedType == null)
                    {
                        continue;
                    }

                    foreach (var unityMethod in unityType.Methods)
                    {
                        if (unityMethod.Name == ".cctor" || unityMethod.Name == ".ctor")
                        {
                            continue;
                        }
                        if (unityMethod.IsAbstract)
                        {
                            continue;
                        }

                        var processedMethod = processedType.TryGetMethodByUnityAssemblyMethod(unityMethod);
                        if (processedMethod != null)
                        {
                            continue;
                        }

                        var returnType = ResolveTypeInNewAssemblies(context, unityMethod.ReturnType, imports);
                        if (returnType == null)
                        {
                            LogSupport.Trace($"Method {unityMethod} has unsupported return type {unityMethod.ReturnType}");
                            methodsIgnored++;
                            continue;
                        }

                        var newMethod       = new MethodDefinition(unityMethod.Name, unityMethod.Attributes & ~MethodAttributes.MemberAccessMask | MethodAttributes.Public, returnType);
                        var hadBadParameter = false;
                        foreach (var unityMethodParameter in unityMethod.Parameters)
                        {
                            var convertedType = ResolveTypeInNewAssemblies(context, unityMethodParameter.ParameterType, imports);
                            if (convertedType == null)
                            {
                                hadBadParameter = true;
                                LogSupport.Trace($"Method {unityMethod} has unsupported parameter type {unityMethodParameter.ParameterType}");
                                break;
                            }

                            newMethod.Parameters.Add(new ParameterDefinition(unityMethodParameter.Name, unityMethodParameter.Attributes, convertedType));
                        }

                        if (hadBadParameter)
                        {
                            methodsIgnored++;
                            continue;
                        }

                        foreach (var unityMethodGenericParameter in unityMethod.GenericParameters)
                        {
                            var newParameter = new GenericParameter(unityMethodGenericParameter.Name, newMethod);
                            newParameter.Attributes = unityMethodGenericParameter.Attributes;
                            foreach (var genericParameterConstraint in unityMethodGenericParameter.Constraints)
                            {
                                if (genericParameterConstraint.ConstraintType.FullName == "System.ValueType")
                                {
                                    continue;
                                }
                                if (genericParameterConstraint.ConstraintType.Resolve().IsInterface)
                                {
                                    continue;
                                }

                                var newType = ResolveTypeInNewAssemblies(context, genericParameterConstraint.ConstraintType, imports);
                                if (newType != null)
                                {
                                    newParameter.Constraints.Add(new GenericParameterConstraint(newType));
                                }
                            }

                            newMethod.GenericParameters.Add(newParameter);
                        }

                        if ((unityMethod.ImplAttributes & MethodImplAttributes.InternalCall) != 0)
                        {
                            var delegateType = UnstripGenerator.CreateDelegateTypeForICallMethod(unityMethod, newMethod, imports);
                            processedType.NewType.NestedTypes.Add(delegateType);
                            delegateType.DeclaringType = processedType.NewType;

                            processedType.NewType.Methods.Add(newMethod);

                            var delegateField = UnstripGenerator.GenerateStaticCtorSuffix(processedType.NewType, delegateType, unityMethod, imports);
                            UnstripGenerator.GenerateInvokerMethodBody(newMethod, delegateField, delegateType, processedType, imports);
                        }
                        else
                        {
                            Pass81FillUnstrippedMethodBodies.PushMethod(unityMethod, newMethod, processedType, imports);
                            processedType.NewType.Methods.Add(newMethod);
                        }

                        if (unityMethod.IsGetter)
                        {
                            GetOrCreateProperty(unityMethod, newMethod).GetMethod = newMethod;
                        }
                        else if (unityMethod.IsSetter)
                        {
                            GetOrCreateProperty(unityMethod, newMethod).SetMethod = newMethod;
                        }

                        var paramsMethod = context.CreateParamsMethod(unityMethod, newMethod, imports, type => ResolveTypeInNewAssemblies(context, type, imports));
                        if (paramsMethod != null)
                        {
                            processedType.NewType.Methods.Add(paramsMethod);
                        }

                        methodsUnstripped++;
                    }
                }
            }

            LogSupport.Info(""); // finish the progress line
            LogSupport.Info($"{methodsUnstripped} methods restored");
            LogSupport.Info($"{methodsIgnored} methods failed to restore");
        }
Example #12
0
        public static void DoPass(RewriteGlobalContext context)
        {
            var pdmNested0Caller  = 0;
            var pdmNestedNZCaller = 0;
            var pdmTop0Caller     = 0;
            var pdmTopNZCaller    = 0;

            foreach (var assemblyContext in context.Assemblies)
            {
                foreach (var typeContext in assemblyContext.Types)
                {
                    foreach (var methodContext in typeContext.Methods)
                    {
                        methodContext.CtorPhase2();

                        int callerCount = 0;
                        if (Pass16ScanMethodRefs.MapOfCallers.TryGetValue(methodContext.Rva, out var callers))
                        {
                            callerCount = callers.Count;
                        }

                        methodContext.NewMethod.CustomAttributes.Add(
                            new CustomAttribute(assemblyContext.Imports.CallerCountAttributeCtor)
                        {
                            ConstructorArguments =
                            { new CustomAttributeArgument(assemblyContext.Imports.Int, callerCount) }
                        });

                        if (!Pass15GenerateMemberContexts.HasObfuscatedMethods)
                        {
                            continue;
                        }
                        if (!methodContext.UnmangledName.Contains("_PDM_"))
                        {
                            continue;
                        }
                        TotalPotentiallyDeadMethods++;

                        var hasZeroCallers = callerCount == 0;
                        if (methodContext.DeclaringType.OriginalType.IsNested)
                        {
                            if (hasZeroCallers)
                            {
                                pdmNested0Caller++;
                            }
                            else
                            {
                                pdmNestedNZCaller++;
                            }
                        }
                        else
                        {
                            if (hasZeroCallers)
                            {
                                pdmTop0Caller++;
                            }
                            else
                            {
                                pdmTopNZCaller++;
                            }
                        }
                    }
                }
            }

            LogSupport.Trace("");
            LogSupport.Trace($"Dead method statistics: 0t={pdmTop0Caller} mt={pdmTopNZCaller} 0n={pdmNested0Caller} mn={pdmNestedNZCaller}");
        }
Example #13
0
        public static void DoPass(RewriteGlobalContext context)
        {
            int fieldsUnstripped = 0;
            int fieldsIgnored    = 0;

            foreach (var unityAssembly in context.UnityAssemblies.Assemblies)
            {
                var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name.Name);
                if (processedAssembly == null)
                {
                    continue;
                }
                var imports = processedAssembly.Imports;

                foreach (var unityType in unityAssembly.MainModule.Types)
                {
                    var processedType = processedAssembly.TryGetTypeByName(unityType.FullName);
                    if (processedType == null)
                    {
                        continue;
                    }

                    if (!unityType.IsValueType || unityType.IsEnum)
                    {
                        continue;
                    }

                    foreach (var unityField in unityType.Fields)
                    {
                        if (unityField.IsStatic && !unityField.HasConstant)
                        {
                            continue;
                        }
                        if (processedType.NewType.IsExplicitLayout && !unityField.IsStatic)
                        {
                            continue;
                        }

                        var processedField = processedType.TryGetFieldByUnityAssemblyField(unityField);
                        if (processedField != null)
                        {
                            continue;
                        }

                        var fieldType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(context, unityField.FieldType, imports);
                        if (fieldType == null)
                        {
                            LogSupport.Trace($"Field {unityField} on type {unityType.FullName} has unsupported type {unityField.FieldType}, the type will be unusable");
                            fieldsIgnored++;
                            continue;
                        }

                        var newField = new FieldDefinition(unityField.Name, unityField.Attributes & ~FieldAttributes.FieldAccessMask | FieldAttributes.Public, fieldType);

                        if (unityField.HasConstant)
                        {
                            newField.Constant = unityField.Constant;
                        }

                        processedType.NewType.Fields.Add(newField);

                        fieldsUnstripped++;
                    }
                }
            }

            LogSupport.Info(""); // finish the progress line
            LogSupport.Info($"{fieldsUnstripped} fields restored");
            LogSupport.Info($"{fieldsIgnored} fields failed to restore");
        }
Example #14
0
        public static void DoPass(RewriteGlobalContext context)
        {
            var unityAssemblyFiles = Directory.EnumerateFiles(context.Options.UnityBaseLibsDir, "*.dll");
            var loadedAssemblies   = unityAssemblyFiles.Select(it =>
                                                               AssemblyDefinition.ReadAssembly(it, new ReaderParameters(ReadingMode.Deferred))).ToList();

            int methodsUnstripped = 0;

            foreach (var unityAssembly in loadedAssemblies)
            {
                var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name.Name);
                if (processedAssembly == null)
                {
                    continue;
                }
                var imports = processedAssembly.Imports;

                foreach (var unityType in unityAssembly.MainModule.Types)
                {
                    var processedType = processedAssembly.TryGetTypeByName(unityType.FullName);
                    if (processedType == null)
                    {
                        continue;
                    }

                    foreach (var unityMethod in unityType.Methods)
                    {
                        if ((unityMethod.ImplAttributes & MethodImplAttributes.InternalCall) == 0)
                        {
                            continue;
                        }
                        var processedMethod = processedType.TryGetMethodByName(unityMethod.Name);
                        if (processedMethod != null)
                        {
                            continue;
                        }

                        var returnType = ResolveTypeInNewAssemblies(context, unityMethod.ReturnType, imports);
                        if (returnType == null)
                        {
                            LogSupport.Trace($"Method {unityMethod} has unsupported return type {unityMethod.ReturnType}");
                            continue;
                        }

                        var newMethod       = new MethodDefinition(unityMethod.Name, unityMethod.Attributes & ~MethodAttributes.MemberAccessMask | MethodAttributes.Public, returnType);
                        var hadBadParameter = false;
                        foreach (var unityMethodParameter in unityMethod.Parameters)
                        {
                            var convertedType = ResolveTypeInNewAssemblies(context, unityMethodParameter.ParameterType, imports);
                            if (convertedType == null)
                            {
                                hadBadParameter = true;
                                LogSupport.Trace($"Method {unityMethod} has unsupported parameter type {unityMethodParameter.ParameterType}");
                                break;
                            }

                            newMethod.Parameters.Add(new ParameterDefinition(unityMethodParameter.Name, unityMethodParameter.Attributes, convertedType));
                        }

                        if (hadBadParameter)
                        {
                            continue;
                        }
                        var delegateType = UnstripGenerator.CreateDelegateTypeForICallMethod(unityMethod, newMethod, imports);
                        processedType.NewType.NestedTypes.Add(delegateType);
                        delegateType.DeclaringType = processedType.NewType;

                        processedType.NewType.Methods.Add(newMethod);

                        var delegateField = UnstripGenerator.GenerateStaticCtorSuffix(processedType.NewType, delegateType, unityMethod, imports);
                        UnstripGenerator.GenerateInvokerMethodBody(newMethod, delegateField, delegateType, processedType, imports);

                        if (unityMethod.IsGetter)
                        {
                            GetOrCreateProperty(unityMethod, newMethod).GetMethod = newMethod;
                        }
                        else if (unityMethod.IsSetter)
                        {
                            GetOrCreateProperty(unityMethod, newMethod).SetMethod = newMethod;
                        }

                        methodsUnstripped++;
                    }
                }
            }

            LogSupport.Trace(""); // finish the progress line
            LogSupport.Trace($"{methodsUnstripped} methods restored");
        }
        public static void DoPass(RewriteGlobalContext context)
        {
            var unityAssemblyFiles = Directory.EnumerateFiles(context.Options.UnityBaseLibsDir, "*.dll");
            var loadedAssemblies   = unityAssemblyFiles.Select(it =>
                                                               AssemblyDefinition.ReadAssembly(it, new ReaderParameters(ReadingMode.Deferred))).ToList();

            int fieldsUnstripped = 0;
            int fieldsIgnored    = 0;

            foreach (var unityAssembly in loadedAssemblies)
            {
                var processedAssembly = context.TryGetAssemblyByName(unityAssembly.Name.Name);
                if (processedAssembly == null)
                {
                    continue;
                }
                var imports = processedAssembly.Imports;

                foreach (var unityType in unityAssembly.MainModule.Types)
                {
                    var processedType = processedAssembly.TryGetTypeByName(unityType.FullName);
                    if (processedType == null)
                    {
                        continue;
                    }

                    if (!unityType.IsValueType || unityType.IsEnum)
                    {
                        continue;
                    }

                    foreach (var unityField in unityType.Fields)
                    {
                        if (unityField.IsStatic)
                        {
                            continue;
                        }

                        var processedField = processedType.TryGetFieldByUnityAssemblyField(unityField);
                        if (processedField != null)
                        {
                            continue;
                        }

                        var fieldType = Pass80UnstripMethods.ResolveTypeInNewAssemblies(context, unityField.FieldType, imports);
                        if (fieldType == null)
                        {
                            LogSupport.Trace($"Field {unityField} on type {unityType.FullName} has unsupported type {unityField.FieldType}, the type will be unusable");
                            fieldsIgnored++;
                            continue;
                        }

                        var newMethod = new FieldDefinition(unityField.Name, unityField.Attributes & ~FieldAttributes.FieldAccessMask | FieldAttributes.Public, fieldType);

                        processedType.NewType.Fields.Add(newMethod);

                        fieldsUnstripped++;
                    }
                }
            }

            LogSupport.Info(""); // finish the progress line
            LogSupport.Info($"{fieldsUnstripped} fields restored");
            LogSupport.Info($"{fieldsIgnored} fields failed to restore");
        }