private static ConstructorInfo Il2CppConstuctor(Type type) => AccessTools.DeclaredConstructor(type, new Type[] { typeof(IntPtr) });
private static DynamicMethod CreateIl2CppShim(DynamicMethod patch, MethodBase original) { var patchName = patch.Name + "_il2cpp"; var parameters = patch.GetParameters(); var result = parameters.Types().ToList(); var origParamTypes = result.ToArray(); var paramTypes = new Type[origParamTypes.Length]; for (int i = 0; i < paramTypes.Length; ++i) { paramTypes[i] = Il2CppTypeForPatchType(origParamTypes[i]); } var origReturnType = AccessTools.GetReturnedType(patch); var returnType = Il2CppTypeForPatchType(origReturnType); DynamicMethod method = new DynamicMethod( patchName, MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, returnType, paramTypes, original.DeclaringType, true ); for (var i = 0; i < parameters.Length; i++) { method.DefineParameter(i + 1, parameters[i].Attributes, parameters[i].Name); } var il = method.GetILGenerator(); LocalBuilder[] byRefValues = new LocalBuilder[parameters.Length]; LocalBuilder returnLocal = null; if (origReturnType != typeof(void)) { returnLocal = il.DeclareLocal(origReturnType); Emitter.LogLocalVariable(il, returnLocal); } LocalBuilder exceptionLocal = il.DeclareLocal(typeof(Exception)); Emitter.LogLocalVariable(il, exceptionLocal); // Start a try-block for the call to the original patch Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginExceptionBlock, null), out _); // Load arguments, invoking the IntPrt -> Il2CppObject constructor for IL2CPP types for (int i = 0; i < origParamTypes.Length; ++i) { Emitter.Emit(il, OpCodes.Ldarg, i); ConvertArgument(il, origParamTypes[i], ref byRefValues[i]); if (byRefValues[i] != null) { Emitter.LogLocalVariable(il, byRefValues[i]); } } // Call the original patch with the now-correct types Emitter.Emit(il, OpCodes.Call, patch); // Store the result, if any if (returnLocal != null) { Emitter.Emit(il, OpCodes.Stloc, returnLocal); } // Catch any exceptions that may have been thrown Emitter.MarkBlockBefore(il, new ExceptionBlock(ExceptionBlockType.BeginCatchBlock, typeof(Exception)), out _); // CumLogger.LogError("Exception in ...\n" + exception.ToString()); Emitter.Emit(il, OpCodes.Stloc, exceptionLocal); Emitter.Emit(il, OpCodes.Ldstr, $"Exception in Harmony patch of method {original.FullDescription()}:\n"); Emitter.Emit(il, OpCodes.Ldloc, exceptionLocal); Emitter.Emit(il, OpCodes.Call, AccessTools.DeclaredMethod(typeof(Exception), "ToString", new Type[0])); Emitter.Emit(il, OpCodes.Call, AccessTools.DeclaredMethod(typeof(string), "Concat", new Type[] { typeof(string), typeof(string) })); Emitter.Emit(il, OpCodes.Call, AccessTools.DeclaredMethod(typeof(CumLogger), "LogError", new Type[] { typeof(string) })); // Close the exception block Emitter.MarkBlockAfter(il, new ExceptionBlock(ExceptionBlockType.EndExceptionBlock, null)); // Write back the pointers of ref arguments for (int i = 0; i < parameters.Length; ++i) { if (byRefValues[i] == null) { continue; } Emitter.Emit(il, OpCodes.Ldarg, i); // -> [intptr*] Emitter.Emit(il, OpCodes.Ldloc, byRefValues[i]); // -> [intptr*, obj] if (origParamTypes[i].GetElementType() == typeof(string)) { Emitter.Emit(il, OpCodes.Call, UnhollowerSupport.ManagedStringToIl2CppMethod); // -> [intptr*, intptr] } else { Emitter.Emit(il, OpCodes.Call, UnhollowerSupport.Il2CppObjectBaseToPtrMethod); // -> [intptr*, intptr] } Emitter.Emit(il, OpCodes.Stind_I); // -> [] } // Load the return value, if any, and unwrap it if required if (returnLocal != null) { Emitter.Emit(il, OpCodes.Ldloc, returnLocal); ConvertReturnValue(il, origReturnType); } Emitter.Emit(il, OpCodes.Ret); DynamicTools.PrepareDynamicMethod(method); return(method); }
public static DynamicMethod CreatePatchedMethod(MethodBase original, string harmonyInstanceID, List <MethodInfo> prefixes, List <MethodInfo> postfixes, List <MethodInfo> transpilers) { if (original == null) { throw new ArgumentNullException(nameof(original), "Original method is null. Did you specify it correctly?"); } try { if (HarmonyInstance.DEBUG) { FileLog.LogBuffered("### Patch " + original.DeclaringType + ", " + original); } var idx = prefixes.Count() + postfixes.Count(); var patch = DynamicTools.CreateDynamicMethod(original, "_Patch" + idx); if (patch == null) { return(null); } var il = patch.GetILGenerator(); // for debugging AssemblyBuilder assemblyBuilder = null; TypeBuilder typeBuilder = null; if (DEBUG_METHOD_GENERATION_BY_DLL_CREATION) { il = DynamicTools.CreateSaveableMethod(original, "_Patch" + idx, out assemblyBuilder, out typeBuilder); } var originalVariables = DynamicTools.DeclareLocalVariables(original, il); var privateVars = new Dictionary <string, LocalBuilder>(); LocalBuilder resultVariable = null; if (idx > 0) { resultVariable = DynamicTools.DeclareLocalVariable(il, AccessTools.GetReturnedType(original)); privateVars[RESULT_VAR] = resultVariable; } prefixes.ForEach(prefix => { prefix.GetParameters() .Where(patchParam => patchParam.Name == STATE_VAR) .Do(patchParam => { var privateStateVariable = DynamicTools.DeclareLocalVariable(il, patchParam.ParameterType); privateVars[prefix.DeclaringType.FullName] = privateStateVariable; }); }); var skipOriginalLabel = il.DefineLabel(); var canHaveJump = AddPrefixes(il, original, prefixes, privateVars, skipOriginalLabel); var copier = new MethodCopier(original, il, originalVariables); foreach (var transpiler in transpilers) { copier.AddTranspiler(transpiler); } var endLabels = new List <Label>(); var endBlocks = new List <ExceptionBlock>(); copier.Finalize(endLabels, endBlocks); foreach (var label in endLabels) { Emitter.MarkLabel(il, label); } foreach (var block in endBlocks) { Emitter.MarkBlockAfter(il, block); } 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); Emitter.Emit(il, OpCodes.Ret); if (HarmonyInstance.DEBUG) { FileLog.LogBuffered("DONE"); FileLog.LogBuffered(""); FileLog.FlushBuffer(); } // for debugging if (DEBUG_METHOD_GENERATION_BY_DLL_CREATION) { DynamicTools.SaveMethod(assemblyBuilder, typeBuilder); return(null); } DynamicTools.PrepareDynamicMethod(patch); return(patch); } catch (Exception ex) { var exceptionString = "Exception from HarmonyInstance \"" + harmonyInstanceID + "\" patching " + original.FullDescription(); if (HarmonyInstance.DEBUG) { FileLog.Log("Exception: " + exceptionString); } throw new Exception(exceptionString, ex); } finally { if (HarmonyInstance.DEBUG) { FileLog.FlushBuffer(); } } }
void PrepareType() { var mainPrepareResult = RunMethod <HarmonyPrepare, bool>(true); if (mainPrepareResult == false) { return; } var customOriginals = RunMethod <HarmonyTargetMethods, IEnumerable <MethodBase> >(null); if (customOriginals != null) { originals = customOriginals.ToList(); } else { var originalMethodType = containerAttributes.methodType; // MethodType default is Normal if (containerAttributes.methodType == null) { containerAttributes.methodType = MethodType.Normal; } var isPatchAll = Attribute.GetCustomAttribute(container, typeof(HarmonyPatchAll)) != null; if (isPatchAll) { var type = containerAttributes.declaringType; originals.AddRange(AccessTools.GetDeclaredConstructors(type).Cast <MethodBase>()); originals.AddRange(AccessTools.GetDeclaredMethods(type).Cast <MethodBase>()); var props = AccessTools.GetDeclaredProperties(type); originals.AddRange(props.Select(prop => prop.GetGetMethod(true)).Where(method => method != null).Cast <MethodBase>()); originals.AddRange(props.Select(prop => prop.GetSetMethod(true)).Where(method => method != null).Cast <MethodBase>()); } else { var original = RunMethod <HarmonyTargetMethod, MethodBase>(null); if (original == null) { original = GetOriginalMethod(); } if (original == null) { var info = "("; info += "declaringType=" + containerAttributes.declaringType + ", "; info += "methodName =" + containerAttributes.methodName + ", "; info += "methodType=" + originalMethodType + ", "; info += "argumentTypes=" + containerAttributes.argumentTypes.Description(); info += ")"; throw new ArgumentException("No target method specified for class " + container.FullName + " " + info); } originals.Add(original); } } PatchTools.GetPatches(container, out prefix.method, out postfix.method, out transpiler.method); if (prefix.method != null) { if (prefix.method.IsStatic == false) { throw new ArgumentException("Patch method " + prefix.method.FullDescription() + " must be static"); } var prefixAttributes = prefix.method.GetHarmonyMethods(); containerAttributes.Merge(HarmonyMethod.Merge(prefixAttributes)).CopyTo(prefix); } if (postfix.method != null) { if (postfix.method.IsStatic == false) { throw new ArgumentException("Patch method " + postfix.method.FullDescription() + " must be static"); } var postfixAttributes = postfix.method.GetHarmonyMethods(); containerAttributes.Merge(HarmonyMethod.Merge(postfixAttributes)).CopyTo(postfix); } if (transpiler.method != null) { if (transpiler.method.IsStatic == false) { throw new ArgumentException("Patch method " + transpiler.method.FullDescription() + " must be static"); } var infixAttributes = transpiler.method.GetHarmonyMethods(); containerAttributes.Merge(HarmonyMethod.Merge(infixAttributes)).CopyTo(transpiler); } }
static void EmitCallParameter(ILGenerator il, MethodBase original, MethodInfo patch, Dictionary <string, LocalBuilder> variables, bool allowFirsParamPassthrough) { var isInstance = original.IsStatic == false; var originalParameters = original.GetParameters(); var originalParameterNames = originalParameters.Select(p => p.Name).ToArray(); // check for passthrough using first parameter (which must have same type as return type) var parameters = patch.GetParameters().ToList(); if (allowFirsParamPassthrough && patch.ReturnType != typeof(void) && parameters.Count > 0 && parameters[0].ParameterType == patch.ReturnType) { parameters.RemoveRange(0, 1); } foreach (var patchParam in parameters) { if (patchParam.Name == ORIGINAL_METHOD_PARAM) { var constructorInfo = original as ConstructorInfo; if (constructorInfo != null) { Emitter.Emit(il, OpCodes.Ldtoken, constructorInfo); Emitter.Emit(il, OpCodes.Call, getMethodMethod); continue; } var methodInfo = original as MethodInfo; if (methodInfo != null) { Emitter.Emit(il, OpCodes.Ldtoken, methodInfo); Emitter.Emit(il, OpCodes.Call, getMethodMethod); continue; } Emitter.Emit(il, OpCodes.Ldnull); continue; } if (patchParam.Name == INSTANCE_PARAM) { if (original.IsStatic) { Emitter.Emit(il, OpCodes.Ldnull); } else if (patchParam.ParameterType.IsByRef) { Emitter.Emit(il, OpCodes.Ldarga, 0); // probably won't work or will be useless } else { Emitter.Emit(il, OpCodes.Ldarg_0); } continue; } if (patchParam.Name.StartsWith(INSTANCE_FIELD_PREFIX)) { var fieldName = patchParam.Name.Substring(INSTANCE_FIELD_PREFIX.Length); FieldInfo fieldInfo; if (fieldName.All(char.IsDigit)) { fieldInfo = AccessTools.Field(original.DeclaringType, int.Parse(fieldName)); if (fieldInfo == null) { throw new ArgumentException("No field found at given index in class " + original.DeclaringType.FullName, fieldName); } } else { fieldInfo = AccessTools.Field(original.DeclaringType, fieldName); if (fieldInfo == null) { throw new ArgumentException("No such field defined in class " + original.DeclaringType.FullName, fieldName); } } if (fieldInfo.IsStatic) { if (patchParam.ParameterType.IsByRef) { Emitter.Emit(il, OpCodes.Ldsflda, fieldInfo); } else { Emitter.Emit(il, OpCodes.Ldsfld, fieldInfo); } } else { if (patchParam.ParameterType.IsByRef) { Emitter.Emit(il, OpCodes.Ldarg_0); Emitter.Emit(il, OpCodes.Ldflda, fieldInfo); } else { Emitter.Emit(il, OpCodes.Ldarg_0); Emitter.Emit(il, OpCodes.Ldfld, fieldInfo); } } continue; } if (patchParam.Name == STATE_VAR) { var ldlocCode = patchParam.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc; Emitter.Emit(il, ldlocCode, variables[patch.DeclaringType.FullName]); continue; } if (patchParam.Name == RESULT_VAR) { if (AccessTools.GetReturnedType(original) == typeof(void)) { throw new Exception("Cannot get result from void method " + original.FullDescription()); } var ldlocCode = patchParam.ParameterType.IsByRef ? OpCodes.Ldloca : OpCodes.Ldloc; Emitter.Emit(il, ldlocCode, variables[RESULT_VAR]); continue; } int idx; if (patchParam.Name.StartsWith(PARAM_INDEX_PREFIX)) { var val = patchParam.Name.Substring(PARAM_INDEX_PREFIX.Length); if (!int.TryParse(val, out idx)) { throw new Exception("Parameter " + patchParam.Name + " does not contain a valid index"); } if (idx < 0 || idx >= originalParameters.Length) { throw new Exception("No parameter found at index " + idx); } } else { idx = GetArgumentIndex(patch, originalParameterNames, patchParam); if (idx == -1) { throw new Exception("Parameter \"" + patchParam.Name + "\" not found in method " + original.FullDescription()); } } // original -> patch opcode // -------------------------------------- // 1 normal -> normal : LDARG // 2 normal -> ref/out : LDARGA // 3 ref/out -> normal : LDARG, LDIND_x // 4 ref/out -> ref/out : LDARG // var originalIsNormal = originalParameters[idx].IsOut == false && originalParameters[idx].ParameterType.IsByRef == false; var patchIsNormal = patchParam.IsOut == false && patchParam.ParameterType.IsByRef == false; var patchArgIndex = idx + (isInstance ? 1 : 0); // Case 1 + 4 if (originalIsNormal == patchIsNormal) { Emitter.Emit(il, OpCodes.Ldarg, patchArgIndex); continue; } // Case 2 if (originalIsNormal && patchIsNormal == false) { Emitter.Emit(il, OpCodes.Ldarga, patchArgIndex); continue; } // Case 3 Emitter.Emit(il, OpCodes.Ldarg, patchArgIndex); Emitter.Emit(il, LoadIndOpCodeFor(originalParameters[idx].ParameterType)); } }