internal void DetourILDetourTarget(bool force = false) { ILProxyDetour?.Dispose(); ILProxyDetour = null; if (!force && ILList.Count == 0) { return; } try { ILProxyDetour = new NativeDetour(ILCopy, DMD.Generate()); } catch (Exception e) { StringBuilder builder = new StringBuilder(); if (DMD.Definition?.Body?.Instructions != null) { builder.AppendLine("IL hook failed for:"); foreach (Instruction i in DMD.Definition.Body.Instructions) { builder.AppendLine(i?.ToString() ?? "NULL!"); } } else { builder.AppendLine("IL hook failed, no instructions found"); } throw new InvalidProgramException(builder.ToString(), e); } }
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 } }
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; }
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> >(); }
internal static void InitHooks() { var detour = new NativeDetour( typeof(SurvivorCatalog).GetMethodCached("Init"), typeof(SurvivorAPI).GetMethodCached(nameof(Init))); detour.Apply(); }
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 static unsafe void Detour(void *from, void *to, bool store = true) { NativeDetour detour = new NativeDetour((IntPtr)from, (IntPtr)to); if (!store) { return; } Stack <NativeDetour> detours = _GetDetours((long)from); detours.Push(detour); }
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")); } }
public static unsafe bool Refresh(void *target) { Stack <NativeDetour> detours = _GetDetours((long)target); if (detours.Count == 0) { return(false); } NativeDetour detour = detours.Peek(); detour.Apply(); return(true); }
public static unsafe void Undetour(void *target, int level = -1) { Stack <NativeDetour> detours = _GetDetours((long)target); if (detours.Count == 0) { return; } NativeDetour detour = detours.Pop(); detour.Undo(); detour.Free(); }
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)); }
private void TestNativeDetoursUnix(dt_rand d_not_rand, IntPtr ptr_not_rand) { DidNothing = true; libc_rand(); Assert.True(DidNothing); DidNothing = true; Assert.Equal(-1, d_not_rand()); Assert.False(DidNothing); using (new NativeDetour( typeof(NativeDetourTest).GetMethod("libc_rand"), typeof(NativeDetourTest).GetMethod("not_rand") )) { DidNothing = true; Assert.Equal(-1, libc_rand()); Assert.False(DidNothing); } DidNothing = true; libc_rand(); Assert.True(DidNothing); /* dl's dlopen doesn't follow ld, meaning that we need to... * - use libc.so.6 on Linux and hope that everyone is using glibc. * - use /usr/lib/libc.dylib on macOS because macOS is macOS. * If libc cannot be dlopened, skip the native -> managed detour test. * - ade */ if (!(PlatformHelper.Is(Platform.Linux) && DynDll.TryOpenLibrary("libc.so.6", out IntPtr libc)) && !(PlatformHelper.Is(Platform.MacOS) && DynDll.TryOpenLibrary("/usr/lib/libc.dylib", out libc)) && !DynDll.TryOpenLibrary($"libc.{PlatformHelper.LibrarySuffix}", out libc)) { return; } NativeDetour d = new NativeDetour( libc.GetFunction("rand"), ptr_not_rand ); DidNothing = true; Assert.Equal(-1, libc_rand()); Assert.False(DidNothing); d.Dispose(); DidNothing = true; libc_rand(); Assert.True(DidNothing); }
private void TestNativeDetoursWindows(dt_rand d_not_rand, IntPtr ptr_not_rand) { if (!DynDll.TryOpenLibrary($"msvcrt.{PlatformHelper.LibrarySuffix}", out IntPtr msvcrt)) { Console.WriteLine("NativeDetourTest skipped - msvcrt not found!"); return; } if (!msvcrt.TryGetFunction("rand", out IntPtr ptr_msvcrt_rand)) { Console.WriteLine("NativeDetourTest skipped - rand not found in msvcrt!"); return; } DidNothing = true; msvcrt_rand(); Assert.True(DidNothing); DidNothing = true; Assert.Equal(-1, d_not_rand()); Assert.False(DidNothing); using (new NativeDetour( typeof(NativeDetourTest).GetMethod("msvcrt_rand"), typeof(NativeDetourTest).GetMethod("not_rand") )) { DidNothing = true; Assert.Equal(-1, msvcrt_rand()); Assert.False(DidNothing); } DidNothing = true; msvcrt_rand(); Assert.True(DidNothing); NativeDetour d = new NativeDetour( ptr_msvcrt_rand, ptr_not_rand ); DidNothing = true; Assert.Equal(-1, msvcrt_rand()); Assert.False(DidNothing); d.Dispose(); DidNothing = true; msvcrt_rand(); Assert.True(DidNothing); }
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 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(); }
internal static void InitHooks() { var detour = new NativeDetour( typeof(SurvivorCatalog).GetMethodCached("Init"), typeof(SurvivorAPI).GetMethodCached(nameof(Init))); detour.Apply(); On.RoR2.SurvivorCatalog.GetSurvivorDef += (orig, index) => GetSurvivorDef(index); // TODO: THIS IS A HOTFIX, WHY IS THE FIRST CHARACTERBODY NULL On.RoR2.UI.LogBook.LogBookController.BuildCategories += orig => { typeof(BodyCatalog).SetFieldValue("bodyPrefabBodyComponents", typeof(BodyCatalog) .GetFieldValue <CharacterBody[]>("bodyPrefabBodyComponents") .Where(x => x != null).ToArray()); return(orig()); }; }
internal static void InitHooks() { var detour = new NativeDetour(typeof(SurvivorCatalog).GetMethod("Init", BindingFlags.NonPublic | BindingFlags.Static), typeof(SurvivorAPI).GetMethod(nameof(Init), BindingFlags.Public | BindingFlags.Static)); detour.Apply(); On.RoR2.SurvivorCatalog.GetSurvivorDef += (orig, survivorIndex) => { //orig is the original method and SurvivorIndex is the variable that is given to the original GetSurvivorDef if (survivorIndex < 0 || (int)survivorIndex > SurvivorDefinitions.Count) { return(null); } return(SurvivorDefinitions[(int)survivorIndex]); //by never doing orig(), the original method is never executed whenever it's called, effectively being replaced }; }
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; }
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"); }
/// <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); }
public static void Run() { AppDomain ad = AppDomain.CurrentDomain; ad.AssemblyResolve += AssemblyResolver; ad.TypeResolve += TypeResolver; CelesteAsm = Assembly.Load(CelesteName); Console.WriteLine($"EverestVanillaLoader loaded Celeste from: {CelesteAsm.Location}"); // The loaded Celeste.exe is in the orig subfolder. // Sadly Monocle assumes the entry executable to be in the same folder as the content dir. // Luckily we can hackfix that. // Sadly we can't set the value or even try-catch RuntimeHelpers.RunClassConstructor(CelesteAsm.GetType("Monocle.Engine").TypeHandle) // Throwing class constructors will just rerun until they don't throw. // Furthermore, we can't just set a magic field, at least not on mono. // In .NET, Assembly.GetEntryAssembly() -> AppDomainManager.EntryAssembly with a cached field // In mono, AppDomainManager.EntryAssembly -> Assembly.GetEntryAssembly(), internal call // Let's do the ugliest thing ever: native detour .NET using (NativeDetour pleaseEndMeAlreadyWhatDidIDoToDeserveThis = new NativeDetour( typeof(Assembly).GetMethod("GetEntryAssembly"), typeof(EverestVanillaLoader).GetMethod("GetEntryAssembly") )) { CelesteAsm .GetType("Monocle.Engine") .GetField("AssemblyDirectory", BindingFlags.NonPublic | BindingFlags.Static) .SetValue(null, Path.GetDirectoryName(EverestPath)); // Similar restrictions apply to XNA's TitleLocation.Path, which is reimplemented accurately by FNA. // Sadly, XNA uses GetEntryAssembly as well. // Luckily, some basic reflection exposed a (presumably cache) field that doesn't exist in FNA. typeof(TitleContainer).Assembly .GetType("Microsoft.Xna.Framework.TitleLocation") ?.GetField("_titleLocation", BindingFlags.NonPublic | BindingFlags.Static) ?.SetValue(null, Path.GetDirectoryName(EverestPath)); } }
private void TestNativeDetoursWindows(dt_rand d_not_rand, IntPtr ptr_not_rand) { DidNothing = true; msvcrt_rand(); Assert.True(DidNothing); DidNothing = true; Assert.Equal(-1, d_not_rand()); Assert.False(DidNothing); using (new NativeDetour( typeof(NativeDetourTest).GetMethod("msvcrt_rand"), typeof(NativeDetourTest).GetMethod("not_rand") )) { DidNothing = true; Assert.Equal(-1, msvcrt_rand()); Assert.False(DidNothing); } DidNothing = true; msvcrt_rand(); Assert.True(DidNothing); NativeDetour d = new NativeDetour( DynDll.OpenLibrary($"msvcrt.{PlatformHelper.LibrarySuffix}").GetFunction("rand"), ptr_not_rand ); DidNothing = true; Assert.Equal(-1, msvcrt_rand()); Assert.False(DidNothing); d.Dispose(); DidNothing = true; msvcrt_rand(); Assert.True(DidNothing); }
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 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); }
// 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));