Exemplo n.º 1
0
        /// <summary>
        ///     Searches for a method that can be used to inject into the specified target.
        /// </summary>
        /// <param name="type">This type in which the possible injection method lies.</param>
        /// <param name="name">Name of the injection method.</param>
        /// <param name="target">The target method which to inject.</param>
        /// <param name="flags">
        ///     Injection flags that specify what values to pass to the injection method and how to inject it. This
        ///     method attempts to find the hook method that satisfies all the specified flags.
        /// </param>
        /// <param name="localVarIDs">
        ///     An array of indicies of local variables to pass to the injection method. Used only if
        ///     <see cref="InjectFlags.PassLocals" /> is specified, otherwise ignored.
        /// </param>
        /// <param name="memberReferences">
        ///     An array of class fields from the type the target lies in to pass to the injection
        ///     method. Used only if <see cref="InjectFlags.PassFields" /> is specified, otherwise ignored.
        /// </param>
        /// <returns>
        ///     An instance of <see cref="InjectionDefinition" />, if a suitable injection method with the given name has been
        ///     found. Otherwise, null.
        /// </returns>
        public static InjectionDefinition GetInjectionMethod(this TypeDefinition type,
                                                             string name,
                                                             MethodDefinition target,
                                                             InjectFlags flags,
                                                             int[] localVarIDs = null,
                                                             params FieldDefinition[] memberReferences)
        {
            Logger.LogLine(LogMask.GetInjectionMethod, "##### GET INJECTION METHOD BEGIN #####");
            Logger.LogLine(
                LogMask.GetInjectionMethod,
                $"Attempting to get a suitable injection method for {type.Name}.{target?.Name}");

            if (string.IsNullOrEmpty(name))
            {
                Logger.LogLine(LogMask.GetInjectionMethod, "No injection method name specified");
                return(null);
            }
            if (target == null || !target.HasBody)
            {
                Logger.LogLine(LogMask.GetInjectionMethod, "No target specified or the target has no definition");
                return(null);
            }

            InjectValues hFlags = flags.ToValues();

#if DEBUG
            Logger.LogLine(LogMask.GetInjectionMethod, "Patch parameters:");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Pass tag: {hFlags.PassTag}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Modify return value: {hFlags.ModifyReturn}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Pass THIS: {hFlags.PassInvokingInstance}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Pass method locals: {hFlags.PassLocals}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Pass member fields: {hFlags.PassFields}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Pass member parameters: {hFlags.PassParameters}");
            if (hFlags.PassParameters)
            {
                Logger.LogLine(
                    LogMask.GetInjectionMethod,
                    $"Member parameters are passed by {(hFlags.PassParametersByRef ? "reference" : "value")}");
            }
#endif

            if (hFlags.PassInvokingInstance && target.IsStatic)
            {
                Logger.LogLine(
                    LogMask.GetInjectionMethod,
                    $"{nameof(hFlags.PassInvokingInstance)} is true, but target is static!");
                return(null);
            }
            if (hFlags.PassFields && (target.IsStatic || memberReferences == null || memberReferences.Length == 0))
            {
                Logger.LogLine(
                    LogMask.GetInjectionMethod,
                    $"{nameof(hFlags.PassFields)} is true, but target is either static or no member references were specified");
                return(null);
            }
            if (hFlags.PassLocals && (!target.Body.HasVariables || localVarIDs == null || localVarIDs.Length == 0))
            {
                Logger.LogLine(
                    LogMask.GetInjectionMethod,
                    $"{nameof(hFlags.PassLocals)} is true, but target either doesn't have any locals or no local IDs were specified");
                return(null);
            }

            bool isVoid = target.ReturnType.FullName == "System.Void";

            int offsetSelf       = Convert.ToInt32(hFlags.PassTag);
            int offsetReturn     = Convert.ToInt32(hFlags.PassTag) + Convert.ToInt32(hFlags.PassInvokingInstance);
            int customParamCount = Convert.ToInt32(hFlags.ModifyReturn && !isVoid) + offsetReturn;
            int memberRefCount   = hFlags.PassFields ? memberReferences.Length : 0;
            int localRefCount    = hFlags.PassLocals ? localVarIDs.Length : 0;
            int paramCount       = (hFlags.PassParameters ? target.Parameters.Count : 0) + customParamCount + memberRefCount
                                   + localRefCount;

            Logger.LogLine(LogMask.GetInjectionMethod, "Needed parameter count:");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Custom parameters (tag + return): {customParamCount}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Member references: {memberRefCount}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Local references: {localRefCount}");
            Logger.LogLine(LogMask.GetInjectionMethod, $"Member parameters: {paramCount}");

            IEnumerable <TypeReference> v;
            TypeComparer comparer = new TypeComparer();

            MethodDefinition injection =
                type.Methods.FirstOrDefault(
                    m =>
                    m.Name == name && m.Parameters.Count == paramCount && m.IsStatic && m.IsPublic && m.HasBody &&
                    (!hFlags.PassTag || m.Parameters[0].ParameterType.FullName == "System.Int32") &&
                    (!hFlags.PassInvokingInstance ||
                     comparer.Equals(m.Parameters[offsetSelf].ParameterType, target.DeclaringType)) &&
                    (!hFlags.ModifyReturn ||
                     (m.ReturnType.FullName == "System.Boolean" &&
                      (isVoid ||
                       comparer.Equals(
                           m.Parameters[offsetReturn].ParameterType,
                           new ByReferenceType(target.ReturnType))))) &&
                    (!hFlags.PassLocals ||
                     localVarIDs.All(i => 0 <= i && i < target.Body.Variables.Count) &&
                     m.Parameters.Slice(customParamCount, localRefCount)
                     .Select((p, i) => new { param = p, index = i })
                     .All(
                         t =>
                         t.param.ParameterType.IsByReference &&
                         comparer.Equals(
                             t.param.ParameterType,
                             new ByReferenceType(target.Body.Variables[t.index].VariableType)))) &&
                    (!hFlags.PassFields ||
                     (v =
                          m.Parameters.Slice(customParamCount + localRefCount, memberRefCount).Select(p => p.ParameterType))
                     .All(p => p.IsByReference) &&
                     v.SequenceEqual(
                         memberReferences.Select(p => (TypeReference) new ByReferenceType(p.FieldType)),
                         comparer)) &&
                    (!hFlags.PassParameters ||
                     (m.HasGenericParameters == target.HasGenericParameters &&
                      (!m.HasGenericParameters || m.GenericParameters.Count <= target.GenericParameters.Count) &&
                      (!hFlags.PassParametersByRef ||
                       m.Parameters.Skip(customParamCount + localRefCount + memberRefCount)
                       .All(p => p.ParameterType.IsByReference)) &&
                      m.Parameters.Skip(customParamCount + localRefCount + memberRefCount)
                      .Select(p => p.ParameterType)
                      .SequenceEqual(
                          target.Parameters.Select(
                              p => (hFlags.PassParametersByRef ? new ByReferenceType(p.ParameterType) : p.ParameterType)),
                          comparer))));

            if (injection == null)
            {
                Logger.LogLine(LogMask.GetInjectionMethod, "Did not find any matching methods!");
                return(null);
            }
            Logger.LogLine(LogMask.GetInjectionMethod, "Found injection method.");
            Logger.LogLine(LogMask.GetInjectionMethod, "##### GET INJECTION METHOD END #####");
            return(new InjectionDefinition
            {
                InjectMethod = injection,
                InjectTarget = target,
                Flags = flags,
                MemberReferences = memberReferences,
                LocalVarIDs = localVarIDs,
                _PrefixCount = customParamCount,
                _MemeberRefCount = hFlags.PassFields ? memberReferences.Length : 0,
                _ParameterCount = hFlags.PassParameters ? target.Parameters.Count : 0
            });
        }
        private static int VerifyInjectionDefinition(MethodDefinition injectMethod,
                                                     MethodDefinition injectTarget,
                                                     InjectFlags flags,
                                                     int[] localVarIDs = null,
                                                     params FieldDefinition[] memberReferences)
        {
            Assert(injectMethod.IsStatic,
                   $"{nameof(injectMethod)} must be static in order to be used as the injection.");
            Assert(injectMethod.IsPublic, $"{nameof(injectMethod)} must be public.");
            Assert(injectMethod.HasBody,
                   $"{nameof(injectMethod)} must have a definition in order to be used as the injecton.");

            InjectValues hFlags = flags.ToValues();

            bool isVoid = injectTarget.ReturnType.FullName == "System.Void";

            Assert(!hFlags.PassLocals || localVarIDs != null,
                   $"Supposed to pass local references, but {nameof(localVarIDs)} is empty");
            Assert(!hFlags.PassFields || memberReferences != null,
                   $"Supposed to pass member fields, but {nameof(memberReferences)} is empty!");

            int prefixCount = Convert.ToInt32(hFlags.PassTag)
                              + Convert.ToInt32(hFlags.PassInvokingInstance)
                              + Convert.ToInt32(hFlags.ModifyReturn && !isVoid);
            int localsCount    = hFlags.PassLocals ? localVarIDs.Length : 0;
            int memberRefCount = hFlags.PassFields ? memberReferences.Length : 0;
            int paramCount     = hFlags.PassParameters ? injectTarget.Parameters.Count : 0;
            int parameters     = injectMethod.Parameters.Count - prefixCount - localsCount - memberRefCount;

            Assert(hFlags.PassParameters && 0 < parameters && parameters <= injectTarget.Parameters.Count ||
                   !hFlags.PassParameters && parameters == 0,
                   $@"The injection method has a wrong number of parameters! Check that the provided target method, local variables, member references and injection flags add up to the right number of parameters.
Needed parameters: Prefix: {prefixCount}, Locals: {localsCount}, Members: {memberRefCount}, Parameters: {
                               (hFlags.PassParameters ? "between 1 and " + paramCount : "0")
                           }, TOTAL: {
                               (hFlags.PassParameters
                                        ? $"between {prefixCount + localsCount + memberRefCount + 1} and "
                                        : "")
                           }{prefixCount + localsCount + memberRefCount + paramCount}.
Injection has {injectMethod.Parameters.Count} parameters.");

            TypeComparer comparer = TypeComparer.Instance;

            if (hFlags.PassTag)
            {
                string typeName = Enum.GetName(typeof(InjectValues.PassTagType), hFlags.TagType);
                Assert(injectMethod.Parameters[0].ParameterType.FullName == $"System.{typeName}",
                       $"Supposed to pass a tag, but the provided tag must be of type System.{typeName}.");
            }

            if (hFlags.PassInvokingInstance)
            {
                Assert(!injectTarget.IsStatic,
                       "Supposed to pass invoking instance, but the target method is static and thus is not bound to any instances.");
                Assert(comparer.Equals(injectMethod.Parameters[Convert.ToInt32(hFlags.PassTag)].ParameterType,
                                       injectTarget.DeclaringType),
                       "Supposed to pass invoking instance, but the type of the instance does not match with the type declared in the injection method.");
            }

            if (hFlags.ModifyReturn)
            {
                Assert(injectMethod.ReturnType.FullName == "System.Boolean",
                       "The injection method must return a boolean in order to alter the return value.");
                Assert(isVoid ||
                       comparer.Equals(injectMethod
                                       .Parameters[Convert.ToInt32(hFlags.PassTag)
                                                   + Convert.ToInt32(hFlags.PassInvokingInstance)].ParameterType,
                                       new ByReferenceType(injectTarget.ReturnType)),
                       "Supposed to modify the return value, but the provided return type does not match with the return type of the target method! Also make sure the type is passed by reference (out/ref).");
            }

            if (hFlags.PassLocals)
            {
                Assert(injectTarget.Body.HasVariables, "The target method does not have any locals.");
                Assert(localVarIDs.Length != 0,
                       "Supposed to pass method locals, but the IDs of the locals were not specified.");
                Assert(localVarIDs.All(i => 0 <= i && i < injectTarget.Body.Variables.Count),
                       "Supposed to receive local references, but the provided local variable index/indices do not exist in the target method!");
                Assert(injectMethod.Parameters.Slice(prefixCount, localsCount).Select((p, i) => new
                {
                    param = p,
                    index = localVarIDs[i]
                }).All(t => t.param.ParameterType.IsByReference &&
                       comparer.Equals(t.param.ParameterType,
                                       new ByReferenceType(injectTarget.Body.Variables[t.index]
                                                           .VariableType))),
                       "Supposed to receive local references, but the types between injection method and target method mismatch. Also make sure they are passed by reference (ref/out).");
            }

            if (hFlags.PassFields)
            {
                Assert(!injectTarget.IsStatic, "Cannot pass member references if the injection method is static!");
                Assert(memberReferences.Length != 0,
                       "Supposed to pass member references, but no members were specified.");
                Assert(memberReferences.All(m => m.DeclaringType.FullName == injectTarget.DeclaringType.FullName &&
                                            m.DeclaringType.BaseType.FullName
                                            == injectTarget.DeclaringType.BaseType.FullName),
                       $"The provided member fields do not belong to {injectTarget.DeclaringType}");

                IEnumerable <TypeReference> paramRefs = injectMethod
                                                        .Parameters.Slice(prefixCount + localsCount, memberRefCount)
                                                        .Select(p => p.ParameterType);
                IEnumerable <TypeReference> typeReferences = paramRefs as TypeReference[] ?? paramRefs.ToArray();

                Assert(typeReferences.All(p => p.IsByReference),
                       "Supposed to pass class members, but the provided parameters in the injection method are not of a reference type (ref)!");
                Assert(typeReferences.SequenceEqual(memberReferences.Select(f => (TypeReference)
                                                                            new ByReferenceType(f.FieldType)),
                                                    comparer),
                       "Supposed to pass class members, but the existing members are of a different type than the ones specified in the injection method.");
            }

            if (hFlags.PassParameters)
            {
                Assert(injectMethod.HasGenericParameters
                       == injectTarget.Parameters.Any(p => p.ParameterType.IsGenericParameter),
                       "The injection and target methods have mismatching specification of generic parameters!");

                Assert(!injectMethod.HasGenericParameters ||
                       injectMethod.GenericParameters.Count
                       <= injectTarget.GenericParameters.Count + injectTarget.DeclaringType.GenericParameters.Count,
                       "The injection and target methods have a mismatching number of generic parameters! The injection method must have less or the same number of generic parameters as the target!");

                Assert(!hFlags.PassParametersByRef ||
                       injectMethod.Parameters.Skip(prefixCount + localsCount + memberRefCount)
                       .All(p => p.ParameterType.IsByReference),
                       "Supposed to pass target method parameters by reference, but the provided parameters in the injection method are not of a reference type (ref).");

                Assert(injectMethod.Parameters.Skip(prefixCount + localsCount + memberRefCount)
                       .Select(p => p.ParameterType)
                       .SequenceEqual(injectTarget
                                      .Parameters.Take(parameters)
                                      .Select(p => hFlags.PassParametersByRef
                                                                       ? new ByReferenceType(p.ParameterType)
                                                                       : p.ParameterType),
                                      comparer),
                       "Supposed to pass target method parameters by reference, but the types specified in injection and target methods do not match.");
            }

            return(parameters);
        }