コード例 #1
0
        public void TestDetoursExt()
        {
            lock (TestObject.Lock) {
                // The following use cases are not meant to be usage examples.
                // Please take a look at DetourTest and HookTest instead.

                using (NativeDetour d = new NativeDetour(
                           // .GetNativeStart() to enforce a native detour.
                           typeof(TestObject).GetMethod("TestStaticMethod").Pin().GetNativeStart(),
                           typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                           )) {
                    int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(6, staticResult);

                    staticResult = TestObject.TestStaticMethod(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(12, staticResult);
                }

                // We can't create a backup for this.
                MethodBase dm;
                using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) {
                    dm = dmd.Generate();
                }
                using (NativeDetour d = new NativeDetour(
                           dm,
                           typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                           )) {
                    int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(6, staticResult);

                    // FIXME: dm.Invoke can fail with a release build in mono 5.X!
                    // staticResult = (int) dm.Invoke(null, new object[] { 2, 3 });
                    staticResult = ((Func <int, int, int>)dm.CreateDelegate <Func <int, int, int> >())(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(12, staticResult);
                }

                // This was provided by Chicken Bones (tModLoader).
                // GetEncoding behaves differently on .NET Core and even between .NET Framework versions,
                // which is why this test only applies to Mono, preferably on Linux to verify if flagging
                // regions of code as read-writable and then read-executable works for AOT'd code.
#if false
                using (Hook h = new Hook(
                           typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }),
                           new Func <Func <string, Encoding>, string, Encoding>((orig, name) => {
                    if (name == "IBM437")
                    {
                        return(null);
                    }
                    return(orig(name));
                })
                           )) {
                    Assert.Null(Encoding.GetEncoding("IBM437"));
                }
#endif
            }
        }
コード例 #2
0
        public void TestDetours()
        {
            // The following use cases are not meant to be usage examples.
            // Please take a look at DetourTest and HookTest instead.

            using (NativeDetour d = new NativeDetour(
                       // .GetNativeStart() to enforce a native detour.
                       typeof(TestObject).GetMethod("TestStaticMethod").GetNativeStart(),
                       typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                       )) {
                int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                Assert.Equal(6, staticResult);

                staticResult = TestObject.TestStaticMethod(2, 3);
                Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                Assert.Equal(12, staticResult);
            }

            // We can't create a backup for this.
            MethodBase dm;

            using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) {
                dm = dmd.Generate();
            }
            using (NativeDetour d = new NativeDetour(
                       dm,
                       typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                       )) {
                int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                Assert.Equal(6, staticResult);

                staticResult = (int)dm.Invoke(null, new object[] { 2, 3 });
                Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                Assert.Equal(12, staticResult);
            }

            // This was provided by Chicken Bones (tModLoader).
            using (Hook h = new Hook(
                       typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }),
                       new Func <Func <string, Encoding>, string, Encoding>((orig, name) => {
                if (name == "IBM437")
                {
                    return(null);
                }
                return(orig(name));
            })
                       )) {
                Assert.Null(Encoding.GetEncoding("IBM437"));
            }
        }
コード例 #3
0
ファイル: DetourRedoTest.cs プロジェクト: windows1988/MonoMod
        public void TestDetoursRedo()
        {
            lock (TestObject.Lock) {
                // The following use cases are not meant to be usage examples.
                // Please take a look at DetourTest and HookTest instead.

                Step(new NativeDetour(
                         typeof(TestObject).GetMethod("TestStaticMethod"),
                         typeof(DetourRedoTest).GetMethod("TestStaticMethod_A")
                         ));

                Step(new Detour(
                         typeof(TestObject).GetMethod("TestStaticMethod"),
                         typeof(DetourRedoTest).GetMethod("TestStaticMethod_A")
                         ));

                Step(new Hook(
                         typeof(TestObject).GetMethod("TestStaticMethod"),
                         typeof(DetourRedoTest).GetMethod("TestStaticMethod_A")
                         ));

                void Step(IDetour d)
                {
                    using (d) {
                        Assert.True(d.IsValid);
                        Assert.True(d.IsApplied);

                        int staticResult = TestObject.TestStaticMethod(2, 3);
                        Assert.Equal(12, staticResult);

                        d.Undo();
                        Assert.True(d.IsValid);
                        Assert.False(d.IsApplied);

                        staticResult = TestObject.TestStaticMethod(2, 3);
                        Assert.Equal(6, staticResult);

                        d.Apply();
                        Assert.True(d.IsValid);
                        Assert.True(d.IsApplied);

                        staticResult = TestObject.TestStaticMethod(2, 3);
                        Assert.Equal(12, staticResult);
                    }

                    Assert.False(d.IsValid);
                    Assert.False(d.IsApplied);
                }
            }
        }
コード例 #4
0
ファイル: DetourRedoTest.cs プロジェクト: nike4613/MonoMod
        public void TestDetoursRedo()
        {
            lock (TestObject.Lock) {
                // The following use cases are not meant to be usage examples.
                // Please take a look at DetourTest and HookTest instead.

                // Uncomment the following line when you want to run this test isolated and make sure that pins aren't being leaked.
                DetourRuntimeILPlatform runtimeIL = null; // DetourHelper.Runtime as DetourRuntimeILPlatform;

                DetourRuntimeILPlatform.MethodPinInfo[] pinnedPrev = null;
                if (runtimeIL != null)
                {
                    pinnedPrev = runtimeIL.GetPins();
                }

                Step(new NativeDetour(
                         typeof(TestObject).GetMethod("TestStaticMethod"),
                         typeof(DetourRedoTest).GetMethod("TestStaticMethod_A")
                         ));

                Step(new Detour(
                         typeof(TestObject).GetMethod("TestStaticMethod"),
                         typeof(DetourRedoTest).GetMethod("TestStaticMethod_A")
                         ));

                Step(new Hook(
                         typeof(TestObject).GetMethod("TestStaticMethod"),
                         typeof(DetourRedoTest).GetMethod("TestStaticMethod_A")
                         ));

                if (runtimeIL != null)
                {
                    DetourRuntimeILPlatform.MethodPinInfo[] pinned = runtimeIL.GetPins();
                    Assert.Equal(pinnedPrev.Length, pinned.Length);
                    for (int i = 0; i < pinned.Length; i++)
                    {
                        DetourRuntimeILPlatform.MethodPinInfo pinPrev = pinnedPrev[i];
                        DetourRuntimeILPlatform.MethodPinInfo pin     = pinned[i];
                        Assert.Equal(pinPrev.Handle.Value, pin.Handle.Value);
                        Assert.Equal(pinPrev.Count, pin.Count);
                    }
                }

                void Step(IDetour d)
                {
                    using (d) {
                        Assert.True(d.IsValid);
                        Assert.True(d.IsApplied);

                        int staticResult = TestObject.TestStaticMethod(2, 3);
                        Assert.Equal(12, staticResult);

                        d.Undo();
                        Assert.True(d.IsValid);
                        Assert.False(d.IsApplied);

                        staticResult = TestObject.TestStaticMethod(2, 3);
                        Assert.Equal(6, staticResult);

                        d.Apply();
                        Assert.True(d.IsValid);
                        Assert.True(d.IsApplied);

                        staticResult = TestObject.TestStaticMethod(2, 3);
                        Assert.Equal(12, staticResult);
                    }

                    Assert.False(d.IsValid);
                    Assert.False(d.IsApplied);
                }
            }
        }
コード例 #5
0
ファイル: DetourTest.cs プロジェクト: stephenwhittle/MonoMod
        public void TestDetours()
        {
            Console.WriteLine("Detours: none");
            TestObject.TestStep(5, 6, 8);
            Console.WriteLine();

            // Three examples of using Detour.
            // Note that if you need non-layered, low-level hooks, you can use NativeDetour instead.
            // This is also why the variable type is IDetour.
            // System.Linq.Expressions, thanks to leo (HookedMethod) for telling me about how to (ab)use MethodCallExpression!
            IDetour detourTestMethodA = new Detour(
                () => default(TestObject).TestMethod(default(int), default(int)),
                () => TestMethod_A(default(TestObject), default(int), default(int))
                );
            // Method references as delegates.
            IDetour detourTestStaticMethodA = new Detour <Func <int, int, int> >(
                TestObject.TestStaticMethod,
                TestStaticMethod_A
                );
            // MethodBase, old syntax.
            IDetour detourTestVoidMethodA = new Detour(
                typeof(TestObject).GetMethod("TestVoidMethod", BindingFlags.Static | BindingFlags.Public),
                typeof(DetourTest).GetMethod("TestVoidMethod_A", BindingFlags.Static | BindingFlags.Public)
                );

            Console.WriteLine("Detours: A");
            TestObject.TestStep(42, 12, 1);
            Console.WriteLine("Testing trampoline, should invoke orig, TestVoidMethod(2, 3)");
            detourTestVoidMethodA.GenerateTrampoline <Action <int, int> >()(2, 3);
            Console.WriteLine();

            IDetour detourTestMethodB = new Detour(
                () => default(TestObject).TestMethod(default(int), default(int)),
                () => TestMethod_B(default(TestObject), default(int), default(int))
                );
            IDetour detourTestStaticMethodB = new Detour(
                () => TestObject.TestStaticMethod(default(int), default(int)),
                () => TestStaticMethod_B(default(int), default(int))
                );
            IDetour detourTestVoidMethodB = new Detour(
                () => TestObject.TestVoidMethod(default(int), default(int)),
                () => TestVoidMethod_B(default(int), default(int))
                );

            Console.WriteLine("Detours: A + B");
            TestObject.TestStep(120, 8, 2);
            Console.WriteLine("Testing trampoline, should invoke hook A, TestVoidMethod(2, 3)");
            Action <int, int> trampolineTestVoidMethodB = detourTestVoidMethodB.GenerateTrampoline <Action <int, int> >();

            trampolineTestVoidMethodB(2, 3);
            Console.WriteLine();

            detourTestMethodA.Undo();
            detourTestStaticMethodA.Undo();
            detourTestVoidMethodA.Undo();
            Console.WriteLine("Detours: B");
            TestObject.TestStep(120, 8, 2);
            Console.WriteLine("Testing trampoline, should invoke orig, TestVoidMethod(2, 3)");
            trampolineTestVoidMethodB(2, 3);
            Console.WriteLine();

            detourTestMethodB.Undo();
            detourTestStaticMethodB.Undo();
            detourTestVoidMethodB.Undo();
            Console.WriteLine("Detours: none");
            TestObject.TestStep(5, 6, 8);
            Console.WriteLine();
        }
コード例 #6
0
        public void TestDetoursExt()
        {
            lock (TestObject.Lock) {
                // The following use cases are not meant to be usage examples.
                // Please take a look at DetourTest and HookTest instead.

                // Just to verify that having a first chance exception handler doesn't introduce any conflicts.
                AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;

#if true
                using (NativeDetour d = new NativeDetour(
                           // .GetNativeStart() to enforce a native detour.
                           typeof(TestObject).GetMethod("TestStaticMethod").Pin().GetNativeStart(),
                           typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                           )) {
                    int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(6, staticResult);

                    staticResult = TestObject.TestStaticMethod(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(12, staticResult);
                }

                // We can't create a backup for this.
                MethodBase dm;
                using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) {
                    dm = dmd.Generate();
                }
                using (NativeDetour d = new NativeDetour(
                           dm,
                           typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                           )) {
                    int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(6, staticResult);

                    // FIXME: dm.Invoke can fail with a release build in mono 5.X!
                    // staticResult = (int) dm.Invoke(null, new object[] { 2, 3 });
                    staticResult = ((Func <int, int, int>)dm.CreateDelegate <Func <int, int, int> >())(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(12, staticResult);
                }
#endif

                // This wasn't provided by anyone and instead is just an internal test.
#if true
                MethodInfo dummyA    = typeof(DetourExtTest).GetMethod("DummyA").Pin();
                MethodInfo dummyB    = typeof(DetourExtTest).GetMethod("DummyB").Pin();
                MethodInfo dummyC    = (MethodInfo)dm;
                IntPtr     dummyAPtr = dummyA.GetNativeStart();
                Assert.True(DetourHelper.Runtime.TryMemAllocScratchCloseTo(dummyAPtr, out IntPtr allocAPtr, -1) != 0);
                Assert.NotEqual(IntPtr.Zero, allocAPtr);
                IntPtr dummyBPtr = dummyB.GetNativeStart();
                Assert.True(DetourHelper.Runtime.TryMemAllocScratchCloseTo(dummyBPtr, out IntPtr allocBPtr, -1) != 0);
                Assert.NotEqual(IntPtr.Zero, allocBPtr);
                IntPtr dummyCPtr = dummyC.GetNativeStart();
                Assert.True(DetourHelper.Runtime.TryMemAllocScratchCloseTo(dummyCPtr, out IntPtr allocCPtr, -1) != 0);
                Assert.NotEqual(IntPtr.Zero, allocCPtr);
                Console.WriteLine($"dummyAPtr: 0x{(long) dummyAPtr:X16}");
                Console.WriteLine($"allocAPtr: 0x{(long) allocAPtr:X16}");
                Console.WriteLine($"dummyBPtr: 0x{(long) dummyBPtr:X16}");
                Console.WriteLine($"allocBPtr: 0x{(long) allocBPtr:X16}");
                Console.WriteLine($"dummyCPtr: 0x{(long) dummyCPtr:X16}");
                Console.WriteLine($"allocCPtr: 0x{(long) allocCPtr:X16}");
                // Close scratch allocs should ideally be within a 1 GiB range of the original method.
                Assert.True(Math.Abs((long)dummyAPtr - (long)allocAPtr) < 1024 * 1024 * 1024, "dummyAPtr and allocAPtr are too far apart.");
                Assert.True(Math.Abs((long)dummyBPtr - (long)allocBPtr) < 1024 * 1024 * 1024, "dummyBPtr and allocBPtr are too far apart.");
                Assert.True(Math.Abs((long)dummyCPtr - (long)allocCPtr) < 1024 * 1024 * 1024, "dummyCPtr and allocCPtr are too far apart.");
#endif

                // This was provided by Chicken Bones (tModLoader).
                // GetEncoding behaves differently on .NET Core and even between .NET Framework versions,
                // which is why this test only applies to Mono, preferably on Linux to verify if flagging
                // regions of code as read-writable and then read-executable works for AOT'd code.
#if false
                using (Hook h = new Hook(
                           typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }),
                           new Func <Func <string, Encoding>, string, Encoding>((orig, name) => {
                    if (name == "IBM437")
                    {
                        return(null);
                    }
                    return(orig(name));
                })
                           )) {
                    Assert.Null(Encoding.GetEncoding("IBM437"));
                }
#endif

                // This was provided by a Harmony user.
                // TextWriter's methods (including all overrides) were unable to be hooked on some runtimes.
                // FIXME: .NET 5 introduces similar behavior for macOS and Linux, but RD isn't ready for that. See DetourRuntimeNETPlatform for more info.
#if true
                using (MemoryStream ms = new MemoryStream()) {
                    using (StreamWriter writer = new StreamWriter(ms, Encoding.UTF8, 1024, true)) {
                        // In case anyone needs to debug this mess anytime in the future ever again:

                        /*/
                         * MethodBase m = typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) });
                         * Console.WriteLine($"meth: 0x{(long) m?.MethodHandle.Value:X16}");
                         * Console.WriteLine($"getf: 0x{(long) m?.MethodHandle.GetFunctionPointer():X16}");
                         * Console.WriteLine($"fptr: 0x{(long) m?.GetLdftnPointer():X16}");
                         * Console.WriteLine($"nats: 0x{(long) m?.GetNativeStart():X16}");
                         * /**/

                        // Debugger.Break();
                        writer.Write("A");

                        using (Hook h = new Hook(
                                   typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) }),
                                   new Action <Action <StreamWriter, string>, StreamWriter, string>((orig, self, value) => {
                            orig(self, "-");
                        })
                                   )) {
                            // Debugger.Break();
                            writer.Write("B");
                        }

                        writer.Write("C");
                    }

                    ms.Seek(0, SeekOrigin.Begin);

                    using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, 1024, true)) {
                        Assert.Equal("A-C", reader.ReadToEnd());
                    }
                }
#endif

#if NETFRAMEWORK && true
                Assert.Equal("A", new SqlCommand("A").CommandText);

                using (Hook h = new Hook(
                           typeof(SqlCommand).GetConstructor(new Type[] { typeof(string) }),
                           new Action <Action <SqlCommand, string>, SqlCommand, string>((orig, self, value) => {
                    orig(self, "-");
                })
                           )) {
                    Assert.Equal("-", new SqlCommand("B").CommandText);
                }

                Assert.Equal("C", new SqlCommand("C").CommandText);
#endif


                // This was provided by tModLoader.
                // The .NET Framework codepath failed on making the method writable the for a single user.
#if NETFRAMEWORK && true
                try {
                    throw new Exception();
                } catch (Exception e) {
                    Assert.NotEqual("", e.StackTrace.Trim());
                }

                using (Hook h = Type.GetType("Mono.Runtime") != null ?
                                // Mono
                                new Hook(
                           typeof(Exception).GetMethod("GetStackTrace", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance),
                           new Func <Func <Exception, bool, string>, Exception, bool, string>((orig, self, fNeedFileInfo) => {
                    return("");
                })
                           ) :
                                // .NET
                                new Hook(
                           typeof(StackTrace).GetConstructor(new[] { typeof(Exception), typeof(bool) }),
                           new Action <Action <StackTrace, Exception, bool>, StackTrace, Exception, bool>((orig, self, e, fNeedFileInfo) => {
                    orig(self, e, fNeedFileInfo);
                    DynamicData.Set(self, new {
                        frames = new StackFrame[0],
                        m_iNumOfFrames = 0,
                        m_iMethodsToSkip = 0
                    });
                })
                           )) {
                    try {
                        throw new Exception();
                    } catch (Exception e) {
                        Assert.Equal("", e.StackTrace.Trim());
                    }
                }

                try {
                    throw new Exception();
                } catch (Exception e) {
                    Assert.NotEqual("", e.StackTrace.Trim());
                }
#endif

                // This was provided by a Harmony user.
                // Theoretically this should be a DynamicMethodDefinition test but who knows what else this will unearth.
#if true
                try {
                    new Thrower(1);
                } catch (Exception e) {
                    Assert.Equal("1", e.Message);
                }

                using (Hook h = new Hook(
                           typeof(Thrower).GetConstructor(new Type[] { typeof(int) }),
                           new Action <Action <Thrower, int>, Thrower, int>((orig, self, a) => {
                    try {
                        orig(self, a + 2);
                    } catch (Exception e) {
                        throw new Exception($"{a} + 2 = {e.Message}");
                    }
                })
                           )) {
                    try {
                        new Thrower(1);
                    } catch (Exception e) {
                        Assert.Equal("1 + 2 = 3", e.Message);
                    }
                }

                try {
                    new Thrower(1);
                } catch (Exception e) {
                    Assert.Equal("1", e.Message);
                }
#endif

                // This was provided by tModLoader.
#if true
                using (Hook h = new Hook(
                           typeof(Process).GetMethod("Start", BindingFlags.Public | BindingFlags.Instance),
                           new Func <Func <Process, bool>, Process, bool>((orig, self) => {
                    return(orig(self));
                })
                           )) {
                }
#endif

                // This was provided by WEGFan from the Everest team.
                // It should be preferably tested on x86, as it's where the struct size caused problems.
#if true
                Assert.Equal(new TwoInts()
                {
                    A = 11111111,
                    B = 22222222
                }, DummyTwoInts());

                using (Hook h = new Hook(
                           typeof(DetourExtTest).GetMethod("DummyTwoInts", BindingFlags.NonPublic | BindingFlags.Instance),
                           new Func <Func <DetourExtTest, TwoInts>, DetourExtTest, TwoInts>((orig, self) => {
                    TwoInts rv = orig(self);
                    rv.A *= 2;
                    rv.B *= 3;
                    return(rv);
                })
                           )) {
                    Assert.Equal(new TwoInts()
                    {
                        A = 11111111 * 2,
                        B = 22222222 * 3
                    }, DummyTwoInts());
                }

                Assert.Equal(new TwoInts()
                {
                    A = 11111111,
                    B = 22222222
                }, DummyTwoInts());
#endif

                // This was provided by a Harmony user.
                // The "struct virtual override" edge case fix itself has got a weird edge case with "struct interface implementations".
                // Note that .NET Framework differs too heavily and .NET Core 2.1 and older inline the getter.
#if NET5_0_OR_GREATER
                Assert.Equal(
                    new KeyValuePair <int, int>(default, default),
コード例 #7
0
ファイル: DetourExtTest.cs プロジェクト: windows1988/MonoMod
        public void TestDetoursExt()
        {
            lock (TestObject.Lock) {
                // The following use cases are not meant to be usage examples.
                // Please take a look at DetourTest and HookTest instead.

#if true
                using (NativeDetour d = new NativeDetour(
                           // .GetNativeStart() to enforce a native detour.
                           typeof(TestObject).GetMethod("TestStaticMethod").Pin().GetNativeStart(),
                           typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                           )) {
                    int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(6, staticResult);

                    staticResult = TestObject.TestStaticMethod(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(12, staticResult);
                }

                // We can't create a backup for this.
                MethodBase dm;
                using (DynamicMethodDefinition dmd = new DynamicMethodDefinition(typeof(TestObject).GetMethod("TestStaticMethod"))) {
                    dm = dmd.Generate();
                }
                using (NativeDetour d = new NativeDetour(
                           dm,
                           typeof(DetourExtTest).GetMethod("TestStaticMethod_A")
                           )) {
                    int staticResult = d.GenerateTrampoline <Func <int, int, int> >()(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(6, staticResult);

                    // FIXME: dm.Invoke can fail with a release build in mono 5.X!
                    // staticResult = (int) dm.Invoke(null, new object[] { 2, 3 });
                    staticResult = ((Func <int, int, int>)dm.CreateDelegate <Func <int, int, int> >())(2, 3);
                    Console.WriteLine($"TestStaticMethod(2, 3): {staticResult}");
                    Assert.Equal(12, staticResult);
                }
#endif

                // This was provided by Chicken Bones (tModLoader).
                // GetEncoding behaves differently on .NET Core and even between .NET Framework versions,
                // which is why this test only applies to Mono, preferably on Linux to verify if flagging
                // regions of code as read-writable and then read-executable works for AOT'd code.
#if false
                using (Hook h = new Hook(
                           typeof(Encoding).GetMethod("GetEncoding", new Type[] { typeof(string) }),
                           new Func <Func <string, Encoding>, string, Encoding>((orig, name) => {
                    if (name == "IBM437")
                    {
                        return(null);
                    }
                    return(orig(name));
                })
                           )) {
                    Assert.Null(Encoding.GetEncoding("IBM437"));
                }
#endif

                // This was provided by a Harmony user.
                // TextWriter's methods (including all overrides) were unable to be hooked on some runtimes.
                // FIXME: .NET 5 introduces similar behavior for macOS and Linux, but RD isn't ready for that. See DetourRuntimeNETPlatform for more info.
#if true
                using (MemoryStream ms = new MemoryStream()) {
                    using (StreamWriter writer = new StreamWriter(ms, Encoding.UTF8, 1024, true)) {
                        // In case anyone needs to debug this mess anytime in the future ever again:

                        /*/
                         * MethodBase m = typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) });
                         * Console.WriteLine($"meth: 0x{(long) m?.MethodHandle.Value:X16}");
                         * Console.WriteLine($"getf: 0x{(long) m?.MethodHandle.GetFunctionPointer():X16}");
                         * Console.WriteLine($"fptr: 0x{(long) m?.GetLdftnPointer():X16}");
                         * Console.WriteLine($"nats: 0x{(long) m?.GetNativeStart():X16}");
                         * /**/

                        // Debugger.Break();
                        writer.Write("A");

                        using (Hook h = new Hook(
                                   typeof(StreamWriter).GetMethod("Write", new Type[] { typeof(string) }),
                                   new Action <Action <StreamWriter, string>, StreamWriter, string>((orig, self, value) => {
                            orig(self, "-");
                        })
                                   )) {
                            // Debugger.Break();
                            writer.Write("B");
                        }

                        writer.Write("C");
                    }

                    ms.Seek(0, SeekOrigin.Begin);

                    using (StreamReader reader = new StreamReader(ms, Encoding.UTF8, false, 1024, true)) {
                        Assert.Equal("A-C", reader.ReadToEnd());
                    }
                }
#endif

#if NETFRAMEWORK && true
                Assert.Equal("A", new SqlCommand("A").CommandText);

                using (Hook h = new Hook(
                           typeof(SqlCommand).GetConstructor(new Type[] { typeof(string) }),
                           new Action <Action <SqlCommand, string>, SqlCommand, string>((orig, self, value) => {
                    orig(self, "-");
                })
                           )) {
                    Assert.Equal("-", new SqlCommand("B").CommandText);
                }

                Assert.Equal("C", new SqlCommand("C").CommandText);
#endif


                // This was provided by tModLoader.
                // The .NET Framework codepath failed on making the method writable the for a single user.
#if NETFRAMEWORK && true
                try {
                    throw new Exception();
                } catch (Exception e) {
                    Assert.NotEqual("", e.StackTrace.Trim());
                }

                using (Hook h = Type.GetType("Mono.Runtime") != null ?
                                // Mono
                                new Hook(
                           typeof(Exception).GetMethod("GetStackTrace", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance),
                           new Func <Func <Exception, bool, string>, Exception, bool, string>((orig, self, fNeedFileInfo) => {
                    return("");
                })
                           ) :
                                // .NET
                                new Hook(
                           typeof(StackTrace).GetConstructor(new[] { typeof(Exception), typeof(bool) }),
                           new Action <Action <StackTrace, Exception, bool>, StackTrace, Exception, bool>((orig, self, e, fNeedFileInfo) => {
                    orig(self, e, fNeedFileInfo);
                    DynamicData.Set(self, new {
                        frames = new StackFrame[0],
                        m_iNumOfFrames = 0,
                        m_iMethodsToSkip = 0
                    });
                })
                           )) {
                    try {
                        throw new Exception();
                    } catch (Exception e) {
                        Assert.Equal("", e.StackTrace.Trim());
                    }
                }

                try {
                    throw new Exception();
                } catch (Exception e) {
                    Assert.NotEqual("", e.StackTrace.Trim());
                }
#endif
            }
        }