private static void ApplyModHackfixes(MonoModder modder) { if (_Relinking == null && !( // Some mods require additional special care. _Relinking.Name == "AdventureHelper" // Don't check the version for this mod as the hackfix is harmless. )) { return; // No hackfixes necessary. } void CrawlMethod(MethodDefinition method) { string methodID = method.GetID(); if (_ModHackfixNoAtlasFallback.Contains(methodID)) { using (ILContext ctx = new ILContext(method)) { ctx.Invoke(ctx => { ILCursor c = new ILCursor(ctx); c.Emit(OpCodes.Ldsfld, typeof(GFX).GetField("Game")); c.Emit(OpCodes.Ldnull); c.Emit(OpCodes.Callvirt, typeof(patch_Atlas).GetMethod("PushFallback")); while (c.TryGotoNext(MoveType.AfterLabel, i => i.MatchRet())) { c.Emit(OpCodes.Ldsfld, typeof(GFX).GetField("Game")); c.Emit(OpCodes.Callvirt, typeof(patch_Atlas).GetMethod("PopFallback")); c.Emit(OpCodes.Pop); c.Index++; } }); } } } void CrawlType(TypeDefinition type) { foreach (MethodDefinition method in type.Methods) { CrawlMethod(method); } foreach (TypeDefinition nested in type.NestedTypes) { CrawlType(nested); } } foreach (TypeDefinition type in modder.Module.Types) { CrawlType(type); } }
private bool InvokeManipulator(MethodDefinition def, Delegate cb) { if (cb.TryCastDelegate(out ILContext.Manipulator manip)) { // The callback is an ILManipulator, or compatible to it out of the box. ILContext il = new ILContext(def); il.ReferenceBag = RuntimeILReferenceBag.Instance; il.Invoke(manip); if (il.IsReadOnly) { return(false); } ActiveMMILs.Add(il); return(true); } // Check if the method accepts a HookIL from another assembly. ParameterInfo[] args = cb.GetMethodInfo().GetParameters(); if (args.Length == 1 && args[0].ParameterType.FullName == typeof(ILContext).FullName) { // Instantiate it. We should rather pass a "proxy" of some sorts, but eh. object hookIL = args[0].ParameterType.GetConstructors()[0].Invoke(new object[] { def }); Type t_hookIL = hookIL.GetType(); // TODO: Set the reference bag. t_hookIL.GetMethod("Invoke").Invoke(hookIL, new object[] { cb }); if (t_hookIL.GetField("_ReadOnly", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(hookIL) as bool? ?? false) { return(false); } if (hookIL is IDisposable disp) { ActiveMMILs.Add(disp); } return(true); } // Fallback - body and IL processor. cb.DynamicInvoke(def.Body, def.Body.GetILProcessor()); def.ConvertShortLongOps(); return(true); }
public void TestDMD() { Counter = 0; // Run the original method. Assert.Equal(Tuple.Create(StringOriginal, 1), ExampleMethod(1)); MethodInfo original = typeof(DynamicMethodDefinitionTest).GetMethod(nameof(ExampleMethod)); MethodBase patched; using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(original)) { Assert.Equal("i", dmd.Definition.Parameters[0].Name); // Modify the MethodDefinition. foreach (Instruction instr in dmd.Definition.Body.Instructions) { if (instr.Operand as string == StringOriginal) { instr.Operand = StringPatched; } else if (instr.MatchCallOrCallvirt <DynamicMethodDefinitionTest>(nameof(ExampleMethod))) { instr.Operand = dmd.Definition; } } // Generate a DynamicMethod from the modified MethodDefinition. patched = dmd.Generate(); } // Generate an entirely new method that just returns a stack trace for further testing. DynamicMethod stacker; using (DynamicMethodDefinition dmd = new DynamicMethodDefinition("Stacker", typeof(StackTrace), new Type[0])) { using (ILContext il = new ILContext(dmd.Definition)) il.Invoke(_ => { ILCursor c = new ILCursor(il); for (int i = 0; i < 32; i++) { c.Emit(OpCodes.Nop); } c.Emit(OpCodes.Newobj, typeof(StackTrace).GetConstructor(new Type[0])); for (int i = 0; i < 32; i++) { c.Emit(OpCodes.Nop); } c.Emit(OpCodes.Ret); }); stacker = (DynamicMethod)DMDEmitDynamicMethodGenerator.Generate(dmd, null); } using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(ExampleGenericClass <int>).GetMethod(nameof(ExampleMethod)))) { Assert.Equal(0, ((Func <int>)dmd.Generate().CreateDelegate(typeof(Func <int>)))()); Assert.Equal(0, ((Func <int>)DMDCecilGenerator.Generate(dmd).CreateDelegate(typeof(Func <int>)))()); Assert.Equal(dmd.Name = "SomeManualDMDName", dmd.Generate().Name); Counter -= 2; // This was indirectly provided by Pathoschild (SMAPI). // Microsoft.GeneratedCode can be loaded multiple times and have different contents. // This tries to recreate that scenario... and this is the best place to test it at the time of writing. #if NETFRAMEWORK && true AssemblyBuilder abDupeA = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName() { Name = "MonoMod.UnitTest.AssemblyDupe" }, AssemblyBuilderAccess.RunAndSave ); ModuleBuilder mbDupeA = abDupeA.DefineDynamicModule($"{abDupeA.GetName().Name}.dll"); TypeBuilder tbDupeA = mbDupeA.DefineType( "DupeA", System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Abstract | System.Reflection.TypeAttributes.Sealed | System.Reflection.TypeAttributes.Class ); Type tDupeA = tbDupeA.CreateType(); AssemblyBuilder abDupeB = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName() { Name = abDupeA.GetName().Name }, AssemblyBuilderAccess.RunAndSave ); ModuleBuilder mbDupeB = abDupeB.DefineDynamicModule($"{abDupeB.GetName().Name}.dll"); TypeBuilder tbDupeB = mbDupeB.DefineType( "DupeB", System.Reflection.TypeAttributes.Public | System.Reflection.TypeAttributes.Abstract | System.Reflection.TypeAttributes.Sealed | System.Reflection.TypeAttributes.Class ); Type tDupeB = tbDupeB.CreateType(); Assert.Equal(tDupeA.Assembly.FullName, tDupeB.Assembly.FullName); Assert.NotEqual(tDupeA.Assembly, tDupeB.Assembly); TypeReference trDupeA = dmd.Module.ImportReference(tDupeA); TypeReference trDupeB = dmd.Module.ImportReference(tDupeB); Assert.Equal(trDupeA.Scope.Name, trDupeB.Scope.Name); // "Fun" fact: both share matching AssemblyNames, so the first scope gets reused! // Assert.NotEqual(trDupeA.Scope, trDupeB.Scope); Assert.Equal(tDupeA, trDupeA.ResolveReflection()); Assert.Equal(tDupeB, trDupeB.ResolveReflection()); #endif } // Run the DynamicMethod. Assert.Equal(Tuple.Create(StringPatched, 3), patched.Invoke(null, new object[] { 2 })); Assert.Equal(Tuple.Create(StringPatched, 3), patched.Invoke(null, new object[] { 1337 })); // Detour the original method to the patched DynamicMethod, then run the patched method. using (new Detour(original, patched)) { // The detour is only active in this block. Assert.Equal(Tuple.Create(StringPatched, 6), ExampleMethod(3)); } // Run the original method again. Assert.Equal(Tuple.Create(StringOriginal, 10), ExampleMethod(4)); // Verify that we can still obtain the real DynamicMethod. // .NET uses a wrapping RTDynamicMethod to avoid leaking the mutable DynamicMethod. // Mono uses RuntimeMethodInfo without any link to the original DynamicMethod. if (Type.GetType("Mono.Runtime") != null) { stacker.Pin(); } StackTrace stack = ((Func <StackTrace>)stacker.CreateDelegate(typeof(Func <StackTrace>)))(); MethodBase stacked = stack.GetFrames().First(f => f.GetMethod()?.IsDynamicMethod() ?? false).GetMethod(); Assert.NotEqual(stacker, stacked); Assert.Equal(stacker, stacked.GetIdentifiable()); Assert.Equal(stacker.GetNativeStart(), stacked.GetNativeStart()); // This will always be true on .NET and only be true on Mono if the method is still pinned. Assert.IsAssignableFrom <DynamicMethod>(stacked.GetIdentifiable()); if (Type.GetType("Mono.Runtime") != null) { stacker.Unpin(); } }
public static bool Init(bool forceLoad = true, Type type = Type.Auto) { if (_HarmonyASM == null) _HarmonyASM = _FindHarmony(); if (_HarmonyASM == null && forceLoad) _HarmonyASM = Assembly.Load(new AssemblyName() { Name = "0Harmony" }); if (_HarmonyASM == null) return false; if (Initialized) return true; Initialized = true; if (type == Type.Auto) type = Type.AsOriginal; _DetourConfig = new DetourConfig() { Priority = type == Type.AsOriginal ? int.MinValue / 4 : type == Type.Override ? int.MaxValue / 4 : 0 }; CurrentType = type; try { foreach (MethodInfo methodRD in typeof(HarmonyDetourBridge).GetMethods(BindingFlags.NonPublic | BindingFlags.Static)) { bool critical = methodRD.GetCustomAttributes(typeof(CriticalAttribute), false).Any(); foreach (DetourToRDAttribute info in methodRD.GetCustomAttributes(typeof(DetourToRDAttribute), false)) foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, info.SkipParams, info.Name)) { critical = false; _Detours.Add(new Hook(methodH, methodRD)); } foreach (DetourToHAttribute info in methodRD.GetCustomAttributes(typeof(DetourToHAttribute), false)) foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, info.SkipParams, info.Name)) { critical = false; _Detours.Add(new Detour(methodRD, methodH)); } foreach (TranspileAttribute info in methodRD.GetCustomAttributes(typeof(TranspileAttribute), false)) { foreach (MethodInfo methodH in GetHarmonyMethod(methodRD, info.Type, -1, info.Name)) { using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(methodH)) { critical = false; ILContext il = new ILContext(dmd.Definition) { ReferenceBag = RuntimeILReferenceBag.Instance }; _Detours.Add(il); il.Invoke((ILContext.Manipulator) methodRD.CreateDelegate<ILContext.Manipulator>()); if (il.IsReadOnly) { il.Dispose(); _Detours.Remove(il); continue; } _Detours.Add(new Detour(methodH, dmd.Generate())); } } } if (critical) throw new Exception($"Cannot apply HarmonyDetourBridge rule {methodRD.Name}"); } } catch { _EarlyReset(); throw; } return true; }
private static void ApplyModHackfixes(MonoModder modder) { // See Coroutine.ForceDelayedSwap for more info. // Older mods are built with this delay in mind, except for hooks. MethodInfo coroutineWrapper = (_Relinking?.Dependencies?.Find(dep => dep.Name == CoreModule.Instance.Metadata.Name) ?.Version ?? new Version(0, 0, 0, 0)) < new Version(1, 2563, 0) ? typeof(CoroutineDelayHackfixHelper).GetMethod("Wrap") : null; if (coroutineWrapper == null && _Relinking == null && !( // Some mods require additional special care. _Relinking.Name == "AdventureHelper" // Don't check the version for this mod as the hackfix is harmless. )) { return; // No hackfixes necessary. } void CrawlMethod(MethodDefinition method) { string methodID = method.GetID(); if (coroutineWrapper != null && method.HasBody && method.ReturnType.FullName == "System.Collections.IEnumerator") { using (ILContext ctx = new ILContext(method)) { ctx.Invoke(ctx => { ILCursor c = new ILCursor(ctx); while (c.TryGotoNext(i => i.MatchRet())) { c.Next.OpCode = OpCodes.Ldstr; c.Next.Operand = methodID; c.Index++; // Scan the nested IEnumerator type if it's already getting rid of the delay itself. c.Emit( method.GetCustomAttribute("System.Runtime.CompilerServices.IteratorStateMachineAttribute") is CustomAttribute ca_IteratorStateMachine && ca_IteratorStateMachine.ConstructorArguments[0].Value is TypeReference iteratorType && iteratorType.SafeResolve()?.FindMethod("MoveNext") is MethodDefinition iteratorMethod && (iteratorMethod.Body?.Instructions.Any(i => i.Operand is MethodReference callee && callee.Name == "MoveNext") ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); c.Emit(OpCodes.Call, coroutineWrapper); c.Emit(OpCodes.Ret); } }); } } if (_ModHackfixNoAtlasFallback.Contains(methodID)) { using (ILContext ctx = new ILContext(method)) { ctx.Invoke(ctx => { ILCursor c = new ILCursor(ctx); c.Emit(OpCodes.Ldsfld, typeof(GFX).GetField("Game")); c.Emit(OpCodes.Ldnull); c.Emit(OpCodes.Callvirt, typeof(patch_Atlas).GetMethod("PushFallback")); while (c.TryGotoNext(MoveType.AfterLabel, i => i.MatchRet())) { c.Emit(OpCodes.Ldsfld, typeof(GFX).GetField("Game")); c.Emit(OpCodes.Callvirt, typeof(patch_Atlas).GetMethod("PopFallback")); c.Emit(OpCodes.Pop); c.Index++; } }); } } } void CrawlType(TypeDefinition type) { foreach (MethodDefinition method in type.Methods) { CrawlMethod(method); } foreach (TypeDefinition nested in type.NestedTypes) { CrawlType(nested); } } foreach (TypeDefinition type in modder.Module.Types) { CrawlType(type); } }