Beispiel #1
0
            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);
                }
            }
Beispiel #2
0
        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();
            }
        }
Beispiel #4
0
        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);
                }
            }