示例#1
0
        public static void Install()
        {
            if (_applied)
            {
                return;
            }

            try
            {
                var refreshDet = new Detour(AccessTools.Method(AccessTools.Inner(typeof(ILHook), "Context"), "Refresh"),
                                            AccessTools.Method(typeof(StackTraceFixes), nameof(OnILChainRefresh)));
                _origRefresh = refreshDet.GenerateTrampoline <Action <object> >();

                var getMethodDet = new Detour(AccessTools.Method(typeof(StackFrame), nameof(StackFrame.GetMethod)),
                                              AccessTools.Method(typeof(StackTraceFixes), nameof(GetMethodFix)));
                _origGetMethod = getMethodDet.GenerateTrampoline <Func <StackFrame, MethodBase> >();

                var nat = new NativeDetour(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)),
                                           AccessTools.Method(typeof(StackTraceFixes), nameof(GetAssemblyFix)));
                _realGetAss = nat.GenerateTrampoline <Func <Assembly> >();
            }
            catch (Exception e)
            {
                Logger.LogText(Logger.LogChannel.Error, $"Failed to apply stack trace fix: ({e.GetType().FullName}) {e.Message}");
            }
            _applied = true;
        }
        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
            }
        }
        private void Awake()
        {
            var detour = new NativeDetour(typeof(Resources).GetMethod(nameof(Resources.UnloadUnusedAssets)),
                                          typeof(ResourceUnloadOptimizations).GetMethod(nameof(RunUnloadUnusedAssets)));

            detour.Apply();
            originalUnload = detour.GenerateTrampoline <Func <AsyncOperation> >();
        }
示例#4
0
        protected override void Apply(IntPtr from)
        {
            var hookPtr =
                Marshal.GetFunctionPointerForDelegate(new PrintFDelegate(OnUnityLog));
            var det = new NativeDetour(from, hookPtr, new NativeDetourConfig {
                ManualApply = true
            });

            original = det.GenerateTrampoline <PrintFDelegate>();
            det.Apply();
        }
示例#5
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"));
            }
        }
        private static void InstallHooks()
        {
            var target      = AccessTools.Method(typeof(Resources), nameof(Resources.UnloadUnusedAssets));
            var replacement = AccessTools.Method(typeof(Hooks), nameof(Hooks.UnloadUnusedAssetsHook));

            var detour = new NativeDetour(target, replacement);

            detour.Apply();

            _originalUnload = detour.GenerateTrampoline <Func <AsyncOperation> >();

            HarmonyWrapper.PatchAll(typeof(Hooks));
        }
        internal static Func <T, TResult> PatchGetter <T, TResult>(string propertyToPatch, Func <T, TResult> replacementGetter)
        {
            BindingFlags baseFlags  = BindingFlags.Public | BindingFlags.NonPublic;
            MethodInfo   origGetter = typeof(TextAsset).GetProperty(propertyToPatch, baseFlags | BindingFlags.Instance)?.GetGetMethod();

            if (origGetter is null)
            {
                throw new ArgumentException($"Unable to patch { propertyToPatch }. Property not found.");
            }

            NativeDetour detour = new NativeDetour(origGetter, replacementGetter.Method);

            detour.Apply();
            Logger.Log(LogLevel.Debug, $"patched: {origGetter.ReturnType} {nameof(TextAsset)}.{origGetter.Name}()");
            return(detour.GenerateTrampoline <Func <T, TResult> >());
        }
示例#8
0
        public static void PatchArtificerFireboltCoefficient()
        {
            if (!BalanceMod.ArtificerM1CoeffBuffEnabled.Value)
            {
                return;
            }
            On.RoR2.Run.Start += (orig, self) =>
            {
                var damageCoeff = (float)AccessTools.Field(AccessTools.TypeByName("EntityStates.Mage.Weapon.FireBolt"), "damageCoefficient").GetValue(null);
                //Debug.Log($"Artificer M1 damage was {damageCoeff}, setting to 100%");
                AccessTools.Field(AccessTools.TypeByName("EntityStates.Mage.Weapon.FireBolt"), "damageCoefficient").SetValue(null, 1.0f);
                orig(self);
            };
            // Threw an exception with HookGen hook, Detour, and Hook. Only NativeDetour worked
            // This is fixed in future versions of MonoMod and will be updated soon to use a Hook
            IDetour h = new NativeDetour(typeof(ProjectileManager).GetMethod("InitializeProjectile", BindingFlags.Static | BindingFlags.NonPublic).GetNativeStart(),
                                         typeof(Hooks).GetMethod("ProjectileManager_InitializeProjectilePrefix", BindingFlags.Static | BindingFlags.Public));

            orig_ProjectileManager_InitializeProjectile = h.GenerateTrampoline <Action <ProjectileController, FireProjectileInfo> >();
            BalanceMod.Logger.LogInfo("Patched: Artificer M1 damageCoeff set to 1.0, procCoeff set to 1.0");
        }
示例#9
0
        public static void Install()
        {
            if (_applied)
            {
                return;
            }

            new Hook(AccessTools.Method(AccessTools.Inner(typeof(ILHook), "Context"), "Refresh"),
                     AccessTools.Method(typeof(StackTraceFixes), nameof(OnILChainRefresh))).Apply();

            var nat = new NativeDetour(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)),
                                       AccessTools.Method(typeof(StackTraceFixes), nameof(GetAssemblyFix)));

            nat.Apply();
            _realGetAss = nat.GenerateTrampoline <Func <Assembly> >();

            new Hook(AccessTools.Method(typeof(StackFrame), nameof(StackFrame.GetMethod)),
                     AccessTools.Method(typeof(StackTraceFixes), nameof(GetMethodFix))).Apply();

            _applied = true;
        }
        /// <inheritdoc />
        public override MethodBase DetourTo(MethodBase replacement)
        {
            nativeDetour?.Dispose();

            nativeDetour = new NativeDetour(Original, replacement, new NativeDetourConfig {
                ManualApply = true
            });

            lock (TrampolineCache)
            {
                if (currentOriginal >= 0)
                {
                    TrampolineCache.Remove(currentOriginal);
                }
                currentOriginal = newOriginal;
                TrampolineCache[currentOriginal] = CreateDelegate(trampolineDelegateType,
                                                                  nativeDetour.GenerateTrampoline(invokeTrampolineMethod));
            }

            nativeDetour.Apply();
            return(replacement);
        }
示例#11
0
        internal static void InitHooks()
        {
            ResourcesLoadDetour = new NativeDetour(
                typeof(Resources).GetMethod("Load", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(Type) }, null),
                typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoad), BindingFlags.Static | BindingFlags.NonPublic)
                );
            _origLoad = ResourcesLoadDetour.GenerateTrampoline <d_ResourcesLoad>();
            ResourcesLoadDetour.Apply();

            ResourcesLoadAsyncDetour = new NativeDetour(
                typeof(Resources).GetMethod("LoadAsyncInternal", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(Type) }, null),
                typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoadAsync), BindingFlags.Static | BindingFlags.NonPublic)
                );
            _origResourcesLoadAsync = ResourcesLoadAsyncDetour.GenerateTrampoline <d_ResourcesAsyncLoad>();
            ResourcesLoadAsyncDetour.Apply();

            ResourcesLoadAllDetour = new NativeDetour(
                typeof(Resources).GetMethod("LoadAll", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(Type) }, null),
                typeof(ResourcesAPI).GetMethod(nameof(OnResourcesLoadAll), BindingFlags.Static | BindingFlags.NonPublic)
                );
            _origLoadAll = ResourcesLoadAllDetour.GenerateTrampoline <d_ResourcesLoadAll>();
            ResourcesLoadAllDetour.Apply();
        }
示例#12
0
        public static void Install()
        {
            if (_applied)
            {
                return;
            }

            var refreshDet = new Detour(AccessTools.Method(AccessTools.Inner(typeof(ILHook), "Context"), "Refresh"),
                                        AccessTools.Method(typeof(StackTraceFixes), nameof(OnILChainRefresh)));

            _origRefresh = refreshDet.GenerateTrampoline <Action <object> >();

            var getMethodDet = new Detour(AccessTools.Method(typeof(StackFrame), nameof(StackFrame.GetMethod)),
                                          AccessTools.Method(typeof(StackTraceFixes), nameof(GetMethodFix)));

            _origGetMethod = getMethodDet.GenerateTrampoline <Func <StackFrame, MethodBase> >();

            var nat = new NativeDetour(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)),
                                       AccessTools.Method(typeof(StackTraceFixes), nameof(GetAssemblyFix)));

            _realGetAss = nat.GenerateTrampoline <Func <Assembly> >();

            _applied = true;
        }
示例#13
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.

#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
            }
        }
示例#14
0
        public override void Apply()
        {
            // The process to patch native methods is as follows:
            // 1. Create a managed proxy method that calls NativeDetour's trampoline (we need to cache it
            //    because we don't know the trampoline method when generating the DMD).
            // 2. Pass the proxy to the normal Harmony manipulator to apply prefixes, postfixes, transpilers, etc.
            // 3. NativeDetour the method to the managed proxy
            // 4. Cache the NativeDetour's trampoline (technically we wouldn't need to, this is just a workaround
            //    for MonoMod's API.

            if (IsRunningOnDotNetCore)
            {
                Logger.Log(Logger.LogChannel.Warn, () => $"Patch target {Original.GetID()} is marked as extern. " +
                           "Extern methods may not be patched because of inlining behaviour of coreclr (refer to https://github.com/dotnet/coreclr/pull/8263)." +
                           "If you need to patch externs, consider using pure NativeDetour instead.");
            }


            var prevDmd = _dmd;

            _nativeDetour?.Dispose();

            _dmd = GenerateManagedOriginal();
            var ctx = new ILContext(_dmd.Definition);

            HarmonyManipulator.Manipulate(Original, Original.GetPatchInfo(), ctx);

            var target = _dmd.Generate();

            _nativeDetour = new NativeDetour(Original, target, new NativeDetourConfig
            {
                ManualApply = true
            });

            lock (TrampolineCache)
            {
                if (prevDmd != null)
                {
                    TrampolineCache.Remove(prevDmd.GetHashCode());
                }

                TrampolineCache[_dmd.GetHashCode()] = CreateDelegate(_trampolineDelegateType, _nativeDetour.GenerateTrampoline(_invokeTrampolineMethod));
            }

            _nativeDetour.Apply();
        }
示例#15
0
 // NativeDetour.GenerateTrampoline restricts T : class
 private static T _GenerateTrampoline <T>(this NativeDetour detour)
 => (T)(object)((DynamicMethod)detour.GenerateTrampoline(typeof(T).GetMethod("Invoke"))).CreateDelegate(typeof(T));
示例#16
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),
示例#17
0
        public MGBALink()
        {
            if (Links.Count == 0)
            {
                // First link - setup.

                IntPtr libmgba    = DynamicDll.OpenLibrary("libmgba.dll");
                IntPtr libmgbasdl = DynamicDll.OpenLibrary("libmgba-sdl.dll");

                // Hook GBSIOInit to grab a reference to GBSIO.
                h_GBSIOInit = new NativeDetour(
                    libmgba.GetFunction("GBSIOInit"),
                    typeof(MGBALink).GetMethod("GBSIOInit", BindingFlags.NonPublic | BindingFlags.Static)
                    );
                orig_GBSIOInit = h_GBSIOInit.GenerateTrampoline <d_GBSIOInit>();

                // Hook GBSIODeinit to properly dispose everything.
                h_GBSIODeinit = new NativeDetour(
                    libmgba.GetFunction("GBSIODeinit"),
                    typeof(MGBALink).GetMethod("GBSIODeinit", BindingFlags.NonPublic | BindingFlags.Static)
                    );
                orig_GBSIODeinit = h_GBSIODeinit.GenerateTrampoline <d_GBSIODeinit>();

                // Hook mTimingInit (called via non-exported GBInit) to set up any timing-related stuff and the driver.
                h_mTimingInit = new NativeDetour(
                    libmgba.GetFunction("mTimingInit"),
                    typeof(MGBALink).GetMethod("mTimingInit", BindingFlags.NonPublic | BindingFlags.Static)
                    );
                orig_mTimingInit = h_mTimingInit.GenerateTrampoline <d_mTimingInit>();

                // Hook mCoreConfigLoadDefaults to change the configs before loading them.
                h_mCoreConfigLoadDefaults = new NativeDetour(
                    libmgba.GetFunction("mCoreConfigLoadDefaults"),
                    typeof(MGBALink).GetMethod("mCoreConfigLoadDefaults", BindingFlags.NonPublic | BindingFlags.Static)
                    );
                orig_mCoreConfigLoadDefaults = h_mCoreConfigLoadDefaults.GenerateTrampoline <d_mCoreConfigLoadDefaults>();

                // Hook mSDLAttachPlayer to hook the renderer's runloop.
                // This is required to fix any managed runtime <-> unmanaged state bugs.
                h_mSDLAttachPlayer = new NativeDetour(
                    libmgbasdl.GetFunction("mSDLAttachPlayer"),
                    typeof(MGBALink).GetMethod("mSDLAttachPlayer", BindingFlags.NonPublic | BindingFlags.Static)
                    );
                orig_mSDLAttachPlayer = h_mSDLAttachPlayer.GenerateTrampoline <d_mSDLAttachPlayer>();

                // Hook mSDLInitAudio to force our own sample rate.
                h_mSDLInitAudio = new NativeDetour(
                    libmgbasdl.GetFunction("mSDLInitAudio"),
                    typeof(MGBALink).GetMethod("mSDLInitAudio", BindingFlags.NonPublic | BindingFlags.Static)
                    );
                orig_mSDLInitAudio = h_mSDLInitAudio.GenerateTrampoline <d_mSDLInitAudio>();

                // Setup the custom GBSIODriver, responsible for syncing dequeues.
                Driver = (GBSIODriver *)Marshal.AllocHGlobal(Marshal.SizeOf(typeof(GBSIODriver)));
                // Slow but functional zeroing.
                for (int i = 0; i < sizeof(mTimingEvent); i++)
                {
                    Driver->p = (GBSIO *)IntPtr.Zero;
                }
                Driver->init    = PinnedPtr <GBSIODriver.d_init>(DriverInit);
                Driver->deinit  = PinnedPtr <GBSIODriver.d_deinit>(DriverDeinit);
                Driver->writeSB = PinnedPtr <GBSIODriver.d_writeSB>(DriverWriteSB);
                Driver->writeSC = PinnedPtr <GBSIODriver.d_writeSC>(DriverWriteSC);

                // Setup the queue check event.
                QueueCheckEvent = (mTimingEvent *)Marshal.AllocHGlobal(sizeof(mTimingEvent));
                // Slow but functional zeroing.
                for (int i = 0; i < sizeof(mTimingEvent); i++)
                {
                    *((byte *)((IntPtr)QueueCheckEvent + i)) = 0x00;
                }
                QueueCheckEvent->context  = (void *)IntPtr.Zero;
                QueueCheckEvent->name     = (byte *)Marshal.StringToHGlobalAnsi("MidiToGBG Queue Check");
                QueueCheckEvent->callback = PinnedPtr <mTimingEvent.d_callback>(QueueCheck);
                QueueCheckEvent->priority = 0x30;
            }

            Links.Add(this);
        }