예제 #1
0
        void ReportException(Exception exception, MethodBase original)
        {
            if (exception == null)
            {
                return;
            }
            if ((containerAttributes.debug ?? false) || Harmony.DEBUG)
            {
                var originalInfo    = original != null ? $" patching {original.FullDescription()}" : "";
                var exceptionString = $"Exception from Harmony \"{instance.Id}\"{originalInfo} processing patch class {containerType.FullDescription()}: {exception}";

                FileLog.indentLevel = 0;
                FileLog.Log(exceptionString);
                return;
            }
            if (exception is HarmonyException)
            {
                throw exception;
            }
            throw new HarmonyException($"Patching exception in method {original.FullDescription()}", exception);
        }
예제 #2
0
        /// <summary>Gets a type by name. Prefers a full name with namespace but falls back to the first type matching the name otherwise</summary>
        /// <param name="name">The name</param>
        /// <returns>A Type</returns>
        ///
        public static Type TypeByName(string name)
        {
            var type = Type.GetType(name, false);

            if (type == null)
            {
                type = AppDomain.CurrentDomain.GetAssemblies()
                       .SelectMany(x => x.GetTypes())
                       .FirstOrDefault(x => x.FullName == name);
            }
            if (type == null)
            {
                type = AppDomain.CurrentDomain.GetAssemblies()
                       .SelectMany(x => x.GetTypes())
                       .FirstOrDefault(x => x.Name == name);
            }
            if (type == null && Harmony.DEBUG)
            {
                FileLog.Log("AccessTools.TypeByName: Could not find type named " + name);
            }
            return(type);
        }
예제 #3
0
        internal MethodPatcher(MethodBase original, MethodBase source, List <MethodInfo> prefixes, List <MethodInfo> postfixes, List <MethodInfo> transpilers, List <MethodInfo> finalizers, bool debug)
        {
            if (original is null)
            {
                throw new ArgumentNullException(nameof(original));
            }

            this.debug       = debug;
            this.original    = original;
            this.source      = source;
            this.prefixes    = prefixes;
            this.postfixes   = postfixes;
            this.transpilers = transpilers;
            this.finalizers  = finalizers;

            Memory.MarkForNoInlining(original);

            if (debug)
            {
                FileLog.LogBuffered($"### Patch: {original.FullDescription()}");
                FileLog.FlushBuffer();
            }

            idx = prefixes.Count() + postfixes.Count() + finalizers.Count();
            useStructReturnBuffer = StructReturnBuffer.NeedsFix(original);
            if (debug && useStructReturnBuffer)
            {
                FileLog.Log($"### Note: A buffer for the returned struct is used. That requires an extra IntPtr argument before the first real argument");
            }
            returnType = AccessTools.GetReturnedType(original);
            patch      = CreateDynamicMethod(original, $"_Patch{idx}", debug);
            if (patch is null)
            {
                throw new Exception("Could not create replacement method");
            }

            il      = patch.GetILGenerator();
            emitter = new Emitter(il, debug);
        }
예제 #4
0
        internal MethodPatcher(MethodBase original, MethodBase source, List <MethodInfo> prefixes, List <MethodInfo> postfixes, List <MethodInfo> transpilers, List <MethodInfo> finalizers, bool debug)
        {
            if (original == null)
            {
                throw new ArgumentNullException(nameof(original));
            }

            this.debug       = debug;
            this.original    = original;
            this.source      = source;
            this.prefixes    = prefixes;
            this.postfixes   = postfixes;
            this.transpilers = transpilers;
            this.finalizers  = finalizers;

            Memory.MarkForNoInlining(original);

            if (debug)
            {
                FileLog.LogBuffered($"### Patch {original.FullDescription()}");
                FileLog.FlushBuffer();
            }

            idx = prefixes.Count() + postfixes.Count() + finalizers.Count();
            firstArgIsReturnBuffer = NativeThisPointer.NeedsNativeThisPointerFix(original);
            if (debug && firstArgIsReturnBuffer)
            {
                FileLog.Log($"### Special case: extra argument after 'this' is pointer to valuetype (simulate return value)");
            }
            returnType = AccessTools.GetReturnedType(original);
            patch      = CreateDynamicMethod(original, $"_Patch{idx}", debug);
            if (patch == null)
            {
                throw new Exception("Could not create replacement method");
            }

            il      = patch.GetILGenerator();
            emitter = new Emitter(il, debug);
        }
예제 #5
0
        /// <summary>Creates a new Harmony instance</summary>
        /// <param name="id">A unique identifier</param>
        /// <returns>A Harmony instance</returns>
        ///
        public Harmony(string id)
        {
            if (string.IsNullOrEmpty(id))
            {
                throw new ArgumentException(nameof(id) + " cannot be null or empty");
            }

            if (DEBUG)
            {
                var assembly = typeof(Harmony).Assembly;
                var version  = assembly.GetName().Version;
                var location = assembly.Location;
                if (string.IsNullOrEmpty(location))
                {
                    location = new Uri(assembly.CodeBase).LocalPath;
                }
                FileLog.Log("### Harmony id=" + id + ", version=" + version + ", location=" + location);
                var callingMethod   = AccessTools.GetOutsideCaller();
                var callingAssembly = callingMethod.DeclaringType.Assembly;
                location = callingAssembly.Location;
                if (string.IsNullOrEmpty(location))
                {
                    location = new Uri(callingAssembly.CodeBase).LocalPath;
                }
                FileLog.Log("### Started from " + callingMethod.FullDescription() + ", location " + location);
                FileLog.Log("### At " + DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss"));
            }

            Id = id;

            if (!selfPatchingDone)
            {
                selfPatchingDone = true;
                if (SELF_PATCHING)
                {
                    SelfPatching.PatchOldHarmonyMethods();
                }
            }
        }
예제 #6
0
        /// <summary>Gets a type by name. Prefers a full name with namespace but falls back to the first type matching the name otherwise</summary>
        /// <param name="name">The name</param>
        /// <returns>A Type</returns>
        ///
        public static Type TypeByName(string name)
        {
            var type       = Type.GetType(name, false);
            var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName.StartsWith("Microsoft.VisualStudio") == false);

            if (type == null)
            {
                type = assemblies
                       .SelectMany(a => GetTypesFromAssembly(a))
                       .FirstOrDefault(t => t.FullName == name);
            }
            if (type == null)
            {
                type = assemblies
                       .SelectMany(a => GetTypesFromAssembly(a))
                       .FirstOrDefault(t => t.Name == name);
            }
            if (type == null && Harmony.DEBUG)
            {
                FileLog.Log("AccessTools.TypeByName: Could not find type named " + name);
            }
            return(type);
        }
예제 #7
0
        internal static DynamicMethodDefinition CreateDynamicMethod(MethodBase original, string suffix, bool debug)
        {
            if (original is null)
            {
                throw new ArgumentNullException(nameof(original));
            }
            var useStructReturnBuffer = StructReturnBuffer.NeedsFix(original);

            var patchName = $"{original.DeclaringType?.FullName}.{original.Name}{suffix}";

            patchName = patchName.Replace("<>", "");

            var parameters     = original.GetParameters();
            var parameterTypes = new List <Type>();

            parameterTypes.AddRange(parameters.Types());
            if (useStructReturnBuffer)
            {
                parameterTypes.Insert(0, typeof(IntPtr));
            }
            if (original.IsStatic is false)
            {
                if (AccessTools.IsStruct(original.DeclaringType))
                {
                    parameterTypes.Insert(0, original.DeclaringType.MakeByRefType());
                }
                else
                {
                    parameterTypes.Insert(0, original.DeclaringType);
                }
            }

            var returnType = useStructReturnBuffer ? typeof(void) : AccessTools.GetReturnedType(original);

            var method = new DynamicMethodDefinition(
                patchName,
                returnType,
                parameterTypes.ToArray()
                )
            {
                OwnerType = original.DeclaringType
            };

            var offset = (original.IsStatic ? 0 : 1) + (useStructReturnBuffer ? 1 : 0);

            if (useStructReturnBuffer)
            {
                method.Definition.Parameters[original.IsStatic ? 0 : 1].Name = "retbuf";
            }
            if (!original.IsStatic)
            {
                method.Definition.Parameters[0].Name = "this";
            }
            for (var i = 0; i < parameters.Length; i++)
            {
                var param = method.Definition.Parameters[i + offset];
                param.Attributes = (Mono.Cecil.ParameterAttributes)parameters[i].Attributes;
                param.Name       = parameters[i].Name;
            }

            if (debug)
            {
                var parameterStrings = parameterTypes.Select(p => p.FullDescription()).ToList();
                if (parameterTypes.Count == method.Definition.Parameters.Count)
                {
                    for (var i = 0; i < parameterTypes.Count; i++)
                    {
                        parameterStrings[i] += $" {method.Definition.Parameters[i].Name}";
                    }
                }
                FileLog.Log($"### Replacement: static {returnType.FullDescription()} {original.DeclaringType.FullName}::{patchName}({parameterStrings.Join()})");
            }

            return(method);
        }
예제 #8
0
        public static DynamicMethod CreatePatchedMethod(MethodBase original, MethodBase source, string harmonyInstanceID, List <MethodInfo> prefixes, List <MethodInfo> postfixes, List <MethodInfo> transpilers, List <MethodInfo> finalizers)
        {
            try
            {
                if (original == null)
                {
                    throw new ArgumentNullException(nameof(original));
                }

                Memory.MarkForNoInlining(original);

                if (Harmony.DEBUG)
                {
                    FileLog.LogBuffered("### Patch " + original.FullDescription());
                    FileLog.FlushBuffer();
                }

                var idx = prefixes.Count() + postfixes.Count() + finalizers.Count();
                var firstArgIsReturnBuffer = NativeThisPointer.NeedsNativeThisPointerFix(original);
                var returnType             = AccessTools.GetReturnedType(original);
                var hasFinalizers          = finalizers.Any();
                var patch = DynamicTools.CreateDynamicMethod(original, "_Patch" + idx);
                if (patch == null)
                {
                    return(null);
                }

                var il = patch.GetILGenerator();

                var originalVariables = DynamicTools.DeclareLocalVariables(source ?? original, il);
                var privateVars       = new Dictionary <string, LocalBuilder>();

                LocalBuilder resultVariable = null;
                if (idx > 0)
                {
                    resultVariable          = DynamicTools.DeclareLocalVariable(il, returnType);
                    privateVars[RESULT_VAR] = resultVariable;
                }

                prefixes.Union(postfixes).Union(finalizers).ToList().ForEach(fix =>
                {
                    if (privateVars.ContainsKey(fix.DeclaringType?.FullName ?? "") == false)
                    {
                        fix.GetParameters()
                        .Where(patchParam => patchParam.Name == STATE_VAR)
                        .Do(patchParam =>
                        {
                            var privateStateVariable = DynamicTools.DeclareLocalVariable(il, patchParam.ParameterType);
                            privateVars[fix.DeclaringType?.FullName ?? ""] = privateStateVariable;
                        });
                    }
                });

                LocalBuilder finalizedVariable = null;
                if (hasFinalizers)
                {
                    finalizedVariable = DynamicTools.DeclareLocalVariable(il, typeof(bool));

                    privateVars[EXCEPTION_VAR] = DynamicTools.DeclareLocalVariable(il, typeof(Exception));

                    // begin try
                    Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock), out _);
                }

                if (firstArgIsReturnBuffer)
                {
                    Emitter.Emit(il, OpCodes.Ldarg_1);                     // load ref to return value
                }
                var skipOriginalLabel = il.DefineLabel();
                var canHaveJump       = AddPrefixes(il, original, prefixes, privateVars, skipOriginalLabel);

                var copier = new MethodCopier(source ?? original, il, originalVariables);
                foreach (var transpiler in transpilers)
                {
                    copier.AddTranspiler(transpiler);
                }
                if (firstArgIsReturnBuffer)
                {
                    copier.AddTranspiler(NativeThisPointer.m_ArgumentShiftTranspiler);
                }

                var endLabels = new List <Label>();
                copier.Finalize(endLabels);

                foreach (var label in endLabels)
                {
                    Emitter.MarkLabel(il, label);
                }
                if (resultVariable != null)
                {
                    Emitter.Emit(il, OpCodes.Stloc, resultVariable);
                }
                if (canHaveJump)
                {
                    Emitter.MarkLabel(il, skipOriginalLabel);
                }

                AddPostfixes(il, original, postfixes, privateVars, false);

                if (resultVariable != null)
                {
                    Emitter.Emit(il, OpCodes.Ldloc, resultVariable);
                }

                AddPostfixes(il, original, postfixes, privateVars, true);

                if (hasFinalizers)
                {
                    AddFinalizers(il, original, finalizers, privateVars, false);
                    Emitter.Emit(il, OpCodes.Ldc_I4_1);
                    Emitter.Emit(il, OpCodes.Stloc, finalizedVariable);
                    var noExceptionLabel1 = il.DefineLabel();
                    Emitter.Emit(il, OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                    Emitter.Emit(il, OpCodes.Brfalse, noExceptionLabel1);
                    Emitter.Emit(il, OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                    Emitter.Emit(il, OpCodes.Throw);
                    Emitter.MarkLabel(il, noExceptionLabel1);

                    // end try, begin catch
                    Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginCatchBlock), out var label);
                    Emitter.Emit(il, OpCodes.Stloc, privateVars[EXCEPTION_VAR]);

                    Emitter.Emit(il, OpCodes.Ldloc, finalizedVariable);
                    var endFinalizerLabel = il.DefineLabel();
                    Emitter.Emit(il, OpCodes.Brtrue, endFinalizerLabel);

                    var rethrowPossible = AddFinalizers(il, original, finalizers, privateVars, true);

                    Emitter.MarkLabel(il, endFinalizerLabel);

                    var noExceptionLabel2 = il.DefineLabel();
                    Emitter.Emit(il, OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                    Emitter.Emit(il, OpCodes.Brfalse, noExceptionLabel2);
                    if (rethrowPossible)
                    {
                        Emitter.Emit(il, OpCodes.Rethrow);
                    }
                    else
                    {
                        Emitter.Emit(il, OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                        Emitter.Emit(il, OpCodes.Throw);
                    }
                    Emitter.MarkLabel(il, noExceptionLabel2);

                    // end catch
                    Emitter.MarkBlockAfter(il, new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));

                    if (resultVariable != null)
                    {
                        Emitter.Emit(il, OpCodes.Ldloc, resultVariable);
                    }
                }

                if (firstArgIsReturnBuffer)
                {
                    Emitter.Emit(il, OpCodes.Stobj, returnType);                     // store result into ref
                }
                Emitter.Emit(il, OpCodes.Ret);

                if (Harmony.DEBUG)
                {
                    FileLog.LogBuffered("DONE");
                    FileLog.LogBuffered("");
                    FileLog.FlushBuffer();
                }

                DynamicTools.PrepareDynamicMethod(patch);
                return(patch);
            }
            catch (Exception ex)
            {
                var exceptionString = "Exception from HarmonyInstance \"" + harmonyInstanceID + "\" patching " + original.FullDescription() + ": " + ex;
                if (Harmony.DEBUG)
                {
                    var savedIndentLevel = FileLog.indentLevel;
                    FileLog.indentLevel = 0;
                    FileLog.Log(exceptionString);
                    FileLog.indentLevel = savedIndentLevel;
                }

                throw new Exception(exceptionString, ex);
            }
            finally
            {
                if (Harmony.DEBUG)
                {
                    FileLog.FlushBuffer();
                }
            }
        }
예제 #9
0
        internal MethodInfo CreateReplacement()
        {
            try
            {
                var originalVariables = DeclareLocalVariables(source ?? original);
                var privateVars       = new Dictionary <string, LocalBuilder>();

                LocalBuilder resultVariable = null;
                if (idx > 0)
                {
                    resultVariable          = DeclareLocalVariable(returnType);
                    privateVars[RESULT_VAR] = resultVariable;
                }

                prefixes.Union(postfixes).Union(finalizers).ToList().ForEach(fix =>
                {
                    if (fix.DeclaringType != null && privateVars.ContainsKey(fix.DeclaringType.FullName) == false)
                    {
                        fix.GetParameters()
                        .Where(patchParam => patchParam.Name == STATE_VAR)
                        .Do(patchParam =>
                        {
                            var privateStateVariable = DeclareLocalVariable(patchParam.ParameterType);
                            privateVars[fix.DeclaringType.FullName] = privateStateVariable;
                        });
                    }
                });

                LocalBuilder finalizedVariable = null;
                if (finalizers.Any())
                {
                    finalizedVariable = DeclareLocalVariable(typeof(bool));

                    privateVars[EXCEPTION_VAR] = DeclareLocalVariable(typeof(Exception));

                    // begin try
                    emitter.MarkBlockBefore(new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock), out _);
                }

                if (firstArgIsReturnBuffer)
                {
                    emitter.Emit(OpCodes.Ldarg_1);                     // load ref to return value
                }
                var skipOriginalLabel = il.DefineLabel();
                var canHaveJump       = AddPrefixes(privateVars, skipOriginalLabel);

                var copier = new MethodCopier(source ?? original, il, originalVariables);
                foreach (var transpiler in transpilers)
                {
                    copier.AddTranspiler(transpiler);
                }
                if (firstArgIsReturnBuffer)
                {
                    copier.AddTranspiler(NativeThisPointer.m_ArgumentShiftTranspiler);
                }

                var endLabels = new List <Label>();
                copier.Finalize(emitter, endLabels);

                foreach (var label in endLabels)
                {
                    emitter.MarkLabel(label);
                }
                if (resultVariable != null)
                {
                    emitter.Emit(OpCodes.Stloc, resultVariable);
                }
                if (canHaveJump)
                {
                    emitter.MarkLabel(skipOriginalLabel);
                }

                AddPostfixes(privateVars, false);

                if (resultVariable != null)
                {
                    emitter.Emit(OpCodes.Ldloc, resultVariable);
                }

                AddPostfixes(privateVars, true);

                if (finalizers.Any())
                {
                    _ = AddFinalizers(privateVars, false);
                    emitter.Emit(OpCodes.Ldc_I4_1);
                    emitter.Emit(OpCodes.Stloc, finalizedVariable);
                    var noExceptionLabel1 = il.DefineLabel();
                    emitter.Emit(OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                    emitter.Emit(OpCodes.Brfalse, noExceptionLabel1);
                    emitter.Emit(OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                    emitter.Emit(OpCodes.Throw);
                    emitter.MarkLabel(noExceptionLabel1);

                    // end try, begin catch
                    emitter.MarkBlockBefore(new ExceptionBlock(ExceptionBlockType.BeginCatchBlock), out var label);
                    emitter.Emit(OpCodes.Stloc, privateVars[EXCEPTION_VAR]);

                    emitter.Emit(OpCodes.Ldloc, finalizedVariable);
                    var endFinalizerLabel = il.DefineLabel();
                    emitter.Emit(OpCodes.Brtrue, endFinalizerLabel);

                    var rethrowPossible = AddFinalizers(privateVars, true);

                    emitter.MarkLabel(endFinalizerLabel);

                    var noExceptionLabel2 = il.DefineLabel();
                    emitter.Emit(OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                    emitter.Emit(OpCodes.Brfalse, noExceptionLabel2);
                    if (rethrowPossible)
                    {
                        emitter.Emit(OpCodes.Rethrow);
                    }
                    else
                    {
                        emitter.Emit(OpCodes.Ldloc, privateVars[EXCEPTION_VAR]);
                        emitter.Emit(OpCodes.Throw);
                    }
                    emitter.MarkLabel(noExceptionLabel2);

                    // end catch
                    emitter.MarkBlockAfter(new ExceptionBlock(ExceptionBlockType.EndExceptionBlock));

                    if (resultVariable != null)
                    {
                        emitter.Emit(OpCodes.Ldloc, resultVariable);
                    }
                }

                if (firstArgIsReturnBuffer)
                {
                    emitter.Emit(OpCodes.Stobj, returnType);                     // store result into ref
                }
                emitter.Emit(OpCodes.Ret);

                if (Harmony.DEBUG)
                {
                    FileLog.LogBuffered("DONE");
                    FileLog.LogBuffered("");
                    FileLog.FlushBuffer();
                }

                return(patch.Generate().Pin());
            }
            catch (Exception ex)
            {
                var exceptionString = $"Exception from HarmonyInstance \"{harmonyInstanceID}\" patching {original.FullDescription()}: {ex}";
                if (Harmony.DEBUG)
                {
                    var savedIndentLevel = FileLog.indentLevel;
                    FileLog.indentLevel = 0;
                    FileLog.Log(exceptionString);
                    FileLog.indentLevel = savedIndentLevel;
                }

                throw new Exception(exceptionString, ex);
            }
            finally
            {
                if (Harmony.DEBUG)
                {
                    FileLog.FlushBuffer();
                }
            }
        }
예제 #10
0
        internal static void PatchOldHarmonyMethods()
        {
            var watch = new Stopwatch();

            watch.Start();

            var ourAssembly = new StackTrace(true).GetFrame(1).GetMethod().DeclaringType.Assembly;

            if (Harmony.DEBUG)
            {
                var originalVersion = ourAssembly.GetName().Version;
                var runningVersion  = typeof(SelfPatching).Assembly.GetName().Version;
                if (runningVersion > originalVersion)
                {
                    // log start because FileLog has not done it
                    FileLog.Log("### Harmony v" + originalVersion + " started");
                    FileLog.Log("### Self-patching unnecessary because we are already patched by v" + runningVersion);
                    FileLog.Log("### At " + DateTime.Now.ToString("yyyy-MM-dd hh.mm.ss"));
                    return;
                }
                FileLog.Log("Self-patching started (v" + originalVersion + ")");
            }

            var potentialMethodsToUpgrade = new Dictionary <string, MethodBase>();

            GetAllMethods(ourAssembly)
            .Where(method => method != null && method.GetCustomAttributes(false).Any(attr => IsUpgrade(attr)))
            .Do(method => potentialMethodsToUpgrade.Add(MethodKey(method), method));

            var otherHarmonyAssemblies = AppDomain.CurrentDomain.GetAssemblies()
                                         .Where(a => a.FullName.StartsWith("Microsoft.VisualStudio") == false)
                                         .Where(assembly => IsHarmonyAssembly(assembly) && assembly != ourAssembly)
                                         .ToList();

            if (Harmony.DEBUG)
            {
                otherHarmonyAssemblies.Do(assembly => FileLog.Log("Found Harmony " + AssemblyInfo(assembly)));

                FileLog.Log("Potential methods to upgrade:");
                potentialMethodsToUpgrade.Values.OrderBy(method => method.FullDescription()).Do(method => FileLog.Log("- " + method.FullDescription()));
            }

            var totalCounter     = 0;
            var potentialCounter = 0;
            var patchedCounter   = 0;

            foreach (var assembly in otherHarmonyAssemblies)
            {
                foreach (var oldMethod in GetAllMethods(assembly))
                {
                    totalCounter++;

                    if (potentialMethodsToUpgrade.TryGetValue(MethodKey(oldMethod), out var newMethod))
                    {
                        var newVersion = GetVersion(newMethod);
                        potentialCounter++;

                        var oldVersion = GetVersion(oldMethod);
                        if (oldVersion < newVersion)
                        {
                            var generics = GetGenericTypes(newMethod);
                            if (generics != null)
                            {
                                foreach (var generic in generics)
                                {
                                    var oldMethodInfo = (oldMethod as MethodInfo).MakeGenericMethod(generic);
                                    var newMethodInfo = (newMethod as MethodInfo).MakeGenericMethod(generic);
                                    if (Harmony.DEBUG)
                                    {
                                        FileLog.Log("Self-patching " + oldMethodInfo.FullDescription() + " with <" + generic.FullName + "> in " + AssemblyInfo(assembly));
                                    }
                                    Memory.DetourMethod(oldMethodInfo, newMethodInfo);
                                }
                                patchedCounter++;
                            }
                            else
                            {
                                if (Harmony.DEBUG)
                                {
                                    FileLog.Log("Self-patching " + oldMethod.FullDescription() + " in " + AssemblyInfo(assembly));
                                }
                                patchedCounter++;
                                Memory.DetourMethod(oldMethod, newMethod);
                            }
                        }
                    }
                }
            }

            if (Harmony.DEBUG)
            {
                FileLog.Log("Self-patched " + patchedCounter + " out of " + totalCounter + " methods (" + (potentialCounter - patchedCounter) + " skipped) in " + watch.ElapsedMilliseconds + "ms");
            }
        }