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> >(); }
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(); }
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> >()); }
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"); }
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); }
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(); }
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; }
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 } }
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(); }
// 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));
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),
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); }