private void PatchPrefix(MethodInfo original, EPatchBehaviour eBehaviour) { MethodInfo dispatcher = AccessTools.Method( typeof(MethodPatch), nameof(DispatchPrefixExecution)); m_Access.Add(MethodPatchFactory.AddPrefix(original, dispatcher, eBehaviour)); }
public PropertyPatch( [NotNull] Type declaringType, EPatchBehaviour eBehaviour = EPatchBehaviour.AlwaysCallOriginal) { m_Declaring = declaringType; m_Behaviour = eBehaviour; m_SetterPatch = new MethodPatch(m_Declaring); m_GetterPatch = new MethodPatch(m_Declaring); }
public MethodPatch InterceptAll( BindingFlags flags, EPatchBehaviour eBehaviour = EPatchBehaviour.NeverCallOriginal) { foreach (MethodInfo method in m_Declaring.GetMethods(flags)) { Intercept(method, eBehaviour); } return(this); }
public static MethodAccess AddPrefix( MethodInfo original, MethodInfo dispatcher, EPatchBehaviour eBehaviour) { lock (Patcher.HarmonyLock) { MethodAccess sync = new MethodAccess(original); AddPrefix(sync, dispatcher, eBehaviour); return(sync); } }
/// <summary> /// Creates a <see cref="MethodAccess" /> and patches in a prefix that relays all calls to /// <see cref="MethodAccess.InvokeOnBeforeCallHandler" />. /// </summary> /// <param name="method">Method to track.</param> /// <param name="eBehaviour"></param> /// <returns>this</returns> /// <exception cref="ArgumentException"> /// If the method is not declared in class /// <see cref="m_Declaring" /> /// </exception> public MethodPatch Intercept( MethodInfo method, EPatchBehaviour eBehaviour = EPatchBehaviour.NeverCallOriginal) { if (method.DeclaringType != m_Declaring) { throw new ArgumentException( $"Provided method {method} is not declared in {m_Declaring}", nameof(method)); } PatchPrefix(method, eBehaviour); return(this); }
/// <summary> /// Creates a <see cref="MethodAccess" /> and patches in a prefix that relays all calls to /// <see cref="MethodAccess.InvokeOnBeforeCallHandler" />. /// </summary> /// <param name="sMethodName">Name of the method</param> /// <param name="eBehaviour"></param> /// <returns></returns> /// <exception cref="ArgumentException">If no method with that name exists.</exception> public MethodPatch Intercept( string sMethodName, EPatchBehaviour eBehaviour = EPatchBehaviour.NeverCallOriginal) { MethodInfo method = AccessTools.Method(m_Declaring, sMethodName); if (method == null) { throw new ArgumentException( $"Method {m_Declaring}.{sMethodName} not found.", nameof(sMethodName)); } PatchPrefix(method, eBehaviour); return(this); }
public static void AddPrefix( MethodAccess access, MethodInfo dispatcher, EPatchBehaviour eBehaviour) { lock (Patcher.HarmonyLock) { if (Prefixes.ContainsKey(access.MemberInfo)) { throw new Exception("Patch already initialized."); } Prefixes[access.MemberInfo] = GeneratePrefix(access, dispatcher, eBehaviour); MethodInfo factoryMethod = typeof(MethodPatchFactory).GetMethod(nameof(GetPrefix)); HarmonyMethod patch = new HarmonyMethod(factoryMethod) { priority = SyncPriority.MethodPatchGeneratedPrefix }; Patcher.HarmonyInstance.Patch(access.MemberInfo, patch); } }
/// <summary> /// Generates a <see cref="DynamicMethod" /> to be used as a harmony prefix. The method /// signature exactly matches the original method with an additional and automatically /// captures the instance for non-static functions. /// The generated Prefix captures the <paramref name="method" /> and calls the /// <paramref name="dispatcher" /> with the following arguments: /// `dispatcher(MethodAccess access, object instance, object [] args)`. /// With `args` containing the original method arguments (excluding __instance). /// </summary> /// <param name="methodAccess">Method that is to be prefixed.</param> /// <param name="dispatcher">Dispatcher to be called in the prefix.</param> /// <param name="eBehaviour">Return value behaviour of the generated prefix.</param> /// <returns></returns> /// <exception cref="Exception"></exception> /// <exception cref="ArgumentOutOfRangeException"></exception> public static DynamicMethod GeneratePrefix( MethodAccess methodAccess, MethodInfo dispatcher, EPatchBehaviour eBehaviour) { List <SMethodParameter> parameters = methodAccess.MemberInfo.GetParameters() .Select( p => new SMethodParameter { Info = p, ParameterType = p.ParameterType, Name = p.Name }) .ToList(); if (!methodAccess.MemberInfo.IsStatic) { parameters.Insert( 0, new SMethodParameter { Info = null, ParameterType = methodAccess.MemberInfo.DeclaringType, Name = "__instance" }); // Inject an __instance } DynamicMethod dyn = new DynamicMethod( "Prefix", typeof(bool), parameters.Select(p => p.ParameterType).ToArray(), methodAccess.MemberInfo.DeclaringType, true); for (int i = 0; i < parameters.Count; ++i) { SMethodParameter parameter = parameters[i]; ParameterAttributes attr; if (parameter.Info != null) { attr = parameter.Info.Attributes; } else { // Injected parameter attr = ParameterAttributes.In; } int iArgIndex = i + 1; // +1 because 0 is the return value dyn.DefineParameter(iArgIndex, attr, parameter.Name); } // Generate a dispatcher call ILGenerator il = dyn.GetILGenerator(); // We want to embed the SyncMethod instance into the DynamicMethod. Unsafe code ahead! // https://stackoverflow.com/questions/4989681/place-an-object-on-top-of-stack-in-ilgenerator GCHandle gcHandle = GCHandle.Alloc(methodAccess); IntPtr pMethod = GCHandle.ToIntPtr(gcHandle); // Arg0: SyncMethod instance if (IntPtr.Size == 4) { il.Emit(OpCodes.Ldc_I4, pMethod.ToInt32()); } else { il.Emit(OpCodes.Ldc_I8, pMethod.ToInt64()); } il.Emit(OpCodes.Ldobj, typeof(MethodAccess)); // Arg1: The instance. bool isStatic = methodAccess.MemberInfo.IsStatic; if (isStatic) { il.Emit(OpCodes.Ldnull); } else { // Forwarded the injected "__instance" field il.Emit(OpCodes.Ldarg_0); // Remove the injected instance from the parameters parameters.RemoveAt(0); } // Arg2: object[] of all args. Prepare the array LocalBuilder args = il.DeclareLocal(typeof(object[])); // start off by creating an object[] with correct size il.Emit(OpCodes.Ldc_I4, parameters.Count); il.Emit(OpCodes.Newarr, typeof(object)); il.Emit(OpCodes.Stloc, args); // store into local var `args` // Place argument in array for (int i = 0; i < parameters.Count; ++i) { int iArgIndex = isStatic ? i : i + 1; // +1 because of the injected __instance il.Emit(OpCodes.Ldloc, args); // Object reference to `args` il.Emit(OpCodes.Ldc_I4, i); // Array index into `args` il.Emit(OpCodes.Ldarg, iArgIndex); // value to put at index if (parameters[i].ParameterType.IsValueType) { il.Emit(OpCodes.Box, parameters[i].ParameterType); } il.Emit(OpCodes.Stelem_Ref); // pops value, index and array reference from stack. } // Arg2 done, push it to the stack il.Emit(OpCodes.Ldloc, args); // Object reference to `args` // Call dispatcher il.EmitCall(OpCodes.Call, dispatcher, null); switch (eBehaviour) { case EPatchBehaviour.AlwaysCallOriginal: if (dispatcher.ReturnType != typeof(void)) { il.Emit(OpCodes.Pop); } il.Emit(OpCodes.Ldc_I4_1); break; case EPatchBehaviour.NeverCallOriginal: if (dispatcher.ReturnType != typeof(void)) { il.Emit(OpCodes.Pop); } il.Emit(OpCodes.Ldc_I4_0); break; case EPatchBehaviour.CallOriginalBaseOnDispatcherReturn: if (dispatcher.ReturnType != typeof(bool)) { throw new Exception( "Invalid dispatcher. Dispatcher function required to return a bool to decided if the original function should be called."); } // Correct value is already on the stack break; default: throw new ArgumentOutOfRangeException(nameof(eBehaviour), eBehaviour, null); } il.Emit(OpCodes.Ret); return(dyn); }