Registration Code (Part 1): w%kQ6
Registration Code (Part 2): b<#$1[*(cw~
In order to register on this forum, you must use the codes above. Combine them into one code (copy paste).

C# Loader Hooking With Mono.Cecil

Programming topics that relate to the C# language.
Post Reply
User avatar
atom0s
Site Admin
Posts: 397
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

C# Loader Hooking With Mono.Cecil

Post by atom0s » Fri Jan 09, 2015 10:32 am

About This Tutorial
This tutorial will cover how to implement function alterations to a managed target using Mono.Cecil. As far as I know Mono.Cecil only works for recreating a binary before it is loaded. I am not sure if it will allow runtime reconstruction, nor have I tried to get it to so. So if you know if it can please do let me know. :) This tutorial will cover hooking onto an XNA application.

Legal Bullshit No One Reads
This is a tutorial for research purposes only. I am not responsible for what you do with this material. You agree that by reading this that you take full responsibility of your actions. I am not responsible for what you do with this information!

Tools Needed Getting Started
To start, we'll be working on attaching onto XNA's main functions for a normal game. Since Terraria is written in C# using XNA we can see that it inherits 'Game' in its main class.

If we use a .NET object browser we can see what Game contains. For the sake of this tutorial you can find the members here:
Game Members

So we are mainly interested in:
  • Initialize
  • LoadContent
  • Update
  • Draw
Like most hacks we will want pre/post hooks for each of these functions. This will allow us to handle things before and after the actual call occurs.

Preparing Our Project
We'll start by making a new XNA game, it is easier to do this since it will add all the XNA references for us even though we don't need any of the actual game code it makes. So start by opening VS2010 and going to:
File -> New -> Project -> C# -> XNA Game Studio 4.0 -> Windows Game (4.0)

Next, we'll remove the Content project because its useless to us (unless you plan to add custom content). So right-click the Content project and remove it. You will also need to remove the content reference in the main project's Content References if it wasn't removed automatically.

Next, we'll delete Game1.cs since we don't need it.

Create a new class, call it whatever you want. In this case I'm calling it Hooks.cs


Preparing Our Hooks Class
To start we will need four main class variables, one for the loaded assembly definition, one for the main module definition, one for the complete assembly, and one for the assemblies main class type. So we will add:
  1. private AssemblyDefinition m_vAsmDefinition = null;
  2. private ModuleDefinition m_vModDefinition = null;
  3. private Assembly m_vAssembly = null;
  4. private Type m_vMainType = null;
Next we will write an initialize function to load our target into our assembly definition and obtain its main module for the module definition:
  1. public bool Initialize()
  2. {
  3.     try
  4.     {
  5.         this.m_vAsmDefinition = AssemblyDefinition.ReadAssembly("Terraria.exe");
  6.         this.m_vModDefinition = this.m_vAsmDefinition.MainModule;
  7.         return true;
  8.     }
  9.     catch { return false; }
  10. }
Next we'll write the finalization function to finish up our modifications and prepare the new assembly for usage:
  1. public bool Finialize()
  2. {
  3.     try
  4.     {
  5.         // Validate we've loaded the main executable first..
  6.         if (this.m_vAsmDefinition == null)
  7.             return false;
  8.  
  9.         using (MemoryStream mStream = new MemoryStream())
  10.         {
  11.             // Write the edited data to the memory stream..
  12.             this.m_vAsmDefinition.Write(mStream);
  13.  
  14.             // Load the new assembly from the memory stream buffer..
  15.             this.m_vAssembly = Assembly.Load(mStream.GetBuffer());
  16.             return true;
  17.         }
  18.     }
  19.     catch { return false; }
  20. }
Next, we need to create a call to run the new assembly rather then using the one on the disk. Since we have the assembly written in memory we can load it in memory as well, so we'll write our Run function like this:
  1. public bool Run()
  2. {
  3.     try
  4.     {
  5.         if (this.m_vAssembly == null)
  6.             return false;
  7.  
  8.         // Get the main class type..
  9.         this.m_vMainType = this.m_vAssembly.GetType("Terraria.Main");
  10.  
  11.         // Create the constructor call..
  12.         var constructor = this.m_vMainType.GetConstructor(new Type[] { }).Invoke(null);
  13.  
  14.         // Obtain the main run method and invoke it..
  15.         var method = this.m_vMainType.GetMethod("Run", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
  16.         method.Invoke(constructor, null);
  17.  
  18.         return true;
  19.     }
  20.     catch { return false; }
  21. }
Now that we have our base done, we can setup the program to run this new instance of the assembly.
Inside of Program.cs, inside of Main() replace everything with:
  1. static void Main(string[] args)
  2. {
  3.     var hooks = new Hooks();
  4.     hooks.Initialize();
  5.     hooks.Finialize();
  6.     hooks.Run();
  7. }
Now you can test if the base hooking works by running your wrapper etc. to make sure its working as expected.

Hooking Onto Our Functions..
Next we want to hook onto the functions. Simple enough, Pre* hooks just get called at the start of the function, while Post* at the end.
(Please see the note below at the end of this tutorial, you may need to do more work in some cases for some hooks to work properly!)

We'll start with Initialize. Inside our Hooks class lets add two functions PreInitialize and PostInitialize following the same method definition from MSDN for Initialize. For now we'll out some console outputs to determine if they get called. (Be sure that these are marked public and static or you will get runtime errors!)
  1. public static void PreInitialize()
  2. {
  3.     System.Diagnostics.Debug.WriteLine("Custom PreInitialize was called!");
  4. }
  5. public static void PostInitialize()
  6. {
  7.     System.Diagnostics.Debug.WriteLine("Custom PostInitialize was called!");
  8. }
Our next step involves digging into Mono.Cecil's abilities. We'll make a new function to apply our hooks. So lets call that ApplyHooks for now. To start, we need to locate the method inside our main type. I wrote wrappers for making these parts easier. These are the wrappers I wrote for these next steps:
  1. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Object Reflection From New Terraria Objects
  4. //
  5. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  6.  
  7. /// <summary>
  8. /// Gets a method definition from the new Terraria executable.
  9. /// </summary>
  10. /// <param name="t"></param>
  11. /// <param name="methodName"></param>
  12. /// <returns></returns>
  13. public MethodDefinition GetMethodDefinition(TypeDefinition t, String methodName)
  14. {
  15.     return (from MethodDefinition m in t.Methods
  16.             where m.Name == methodName
  17.             select m).FirstOrDefault();
  18. }
  19.  
  20. /// <summary>
  21. /// Gets a field definition from the new Terraria executable.
  22. /// </summary>
  23. /// <param name="t"></param>
  24. /// <param name="fieldName"></param>
  25. /// <returns></returns>
  26. public FieldDefinition GetFieldDefinition(TypeDefinition t, String fieldName)
  27. {
  28.     return (from FieldDefinition f in t.Fields
  29.             where f.Name == fieldName
  30.             select f).FirstOrDefault();
  31. }
  32.  
  33. /// <summary>
  34. /// Gets a property definition from the new Terraria executable.
  35. /// </summary>
  36. /// <param name="t"></param>
  37. /// <param name="propName"></param>
  38. /// <returns></returns>
  39. public PropertyDefinition GetPropertyDefinition(TypeDefinition t, String propName)
  40. {
  41.     return (from PropertyDefinition p in t.Properties
  42.             where p.Name == propName
  43.             select p).FirstOrDefault();
  44. }
  45.  
  46. /// <summary>
  47. /// Gets a type definition from the new Terraria executable.
  48. /// </summary>
  49. /// <param name="typeName"></param>
  50. /// <returns></returns>
  51. public TypeDefinition GetTypeDefinition(String typeName)
  52. {
  53.     return (from TypeDefinition t in this.m_vModDefinition.Types
  54.             where t.Name == typeName
  55.             select t).FirstOrDefault();
  56. }
  57.  
  58. /// <summary>
  59. /// Gets a type from within the new Terraria executable.
  60. /// </summary>
  61. /// <param name="typeName"></param>
  62. /// <returns></returns>
  63. public Type GetType(String typeName)
  64. {
  65.     return (from Type t in this.m_vAssembly.GetTypes()
  66.             where t.Name == typeName
  67.             select t).FirstOrDefault();
  68. }
  69.  
  70. /// <summary>
  71. /// Gets a method from within Terraria.Main.
  72. /// </summary>
  73. /// <param name="methodName"></param>
  74. /// <returns></returns>
  75. public MethodInfo GetMethod(String methodName)
  76. {
  77.     return (from MethodInfo m in this.m_vMainType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
  78.             where m.Name == methodName
  79.             select m).FirstOrDefault();
  80. }
  81.  
  82. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  83. //
  84. // Object Reflection From New Terraria.Main Object
  85. //
  86. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  87.  
  88. /// <summary>
  89. /// Gets the value of a field within Terraria.Main.
  90. /// </summary>
  91. /// <typeparam name="T"></typeparam>
  92. /// <param name="fieldName"></param>
  93. /// <returns></returns>
  94. public T GetMainField<T>(String fieldName)
  95. {
  96.     var field = (from FieldInfo f in this.m_vMainType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
  97.                  where f.Name == fieldName
  98.                  select f).FirstOrDefault();
  99.  
  100.     if (field == null)
  101.         return default(T);
  102.  
  103.     return (T)field.GetValue(this.m_vMainType);
  104. }
  105.  
  106. /// <summary>
  107. /// Sets the value of a field within Terraria.Main.
  108. /// </summary>
  109. /// <typeparam name="T"></typeparam>
  110. /// <param name="fieldName"></param>
  111. /// <param name="objValue"></param>
  112. public void SetMainField<T>(String fieldName, T objValue)
  113. {
  114.     var field = (from FieldInfo f in this.m_vMainType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
  115.                  where f.Name == fieldName
  116.                  select f).FirstOrDefault();
  117.  
  118.     if (field == null)
  119.         return;
  120.  
  121.     field.SetValue(this.m_vMainType, objValue);
  122. }
So now we can easily locate the method for Initialize using:
  1. MethodDefinition initMethod = GetMethodDefinition(GetTypeDefinition("Main"), "Initialize");
Next we want to inject our 'PreInitialize' call to the start of this method. We can do that by using:
  1. MethodDefinition initMethod = GetMethodDefinition(GetTypeDefinition("Main"), "Initialize");
  2. var initProc = initMethod.Body.GetILProcessor();
  3. initProc.InsertBefore(initMethod.Body.Instructions[0], initProc.Create(OpCodes.Call,
  4.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PreInitialize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  5.     ));
We can run the test app again and see if it gets called, in the output window you should see:
Custom PreInitialize was called!

Great now we want to add our PostInitialize call to the end of the function. If we look at the all in a IL decoder (such as ILSpy) we can see the Initialize call ends with:
  1. IL_1e8f: ret
So we need to inject before this return:
  1. initProc.InsertBefore(initMethod.Body.Instructions[initMethod.Body.Instructions.Count - 2], initProc.Create(OpCodes.Call,
  2.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PostInitialize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  3.     ));
So now we have in total for our Initialize hooks:
  1. public void ApplyHooks()
  2. {
  3.     MethodDefinition initMethod = GetMethodDefinition(GetTypeDefinition("Main"), "Initialize");
  4.     var initProc = initMethod.Body.GetILProcessor();
  5.     initProc.InsertBefore(initMethod.Body.Instructions[0], initProc.Create(OpCodes.Call,
  6.         this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PreInitialize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  7.         ));
  8.     initProc.InsertBefore(initMethod.Body.Instructions[initMethod.Body.Instructions.Count - 2], initProc.Create(OpCodes.Call,
  9.         this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PostInitialize", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  10.         ));
  11. }
Next we'll do the same for LoadContent, add the Pre and Post methods:
  1. public static void PreLoadContent()
  2. {
  3.     System.Diagnostics.Debug.WriteLine("Custom PreLoadContent was called!");
  4. }
  5. public static void PostLoadContent()
  6. {
  7.     System.Diagnostics.Debug.WriteLine("Custom PostLoadContent was called!");
  8. }
And then for our hook it will be similar to Initializes hook:
  1. MethodDefinition contMethod = GetMethodDefinition(GetTypeDefinition("Main"), "LoadContent");
  2. var contProc = contMethod.Body.GetILProcessor();
  3. contProc.InsertBefore(contMethod.Body.Instructions[0], contProc.Create(OpCodes.Call,
  4.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PreLoadContent", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  5.     ));
  6. contProc.InsertBefore(contMethod.Body.Instructions[contMethod.Body.Instructions.Count - 2], contProc.Create(OpCodes.Call,
  7.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PostLoadContent", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  8.     ));
Hooking Onto Update and Draw
Next, we want to hook onto Update and Draw. The difference here is that we now have functions that take arguments. In this case a 'GameTime' object which holds the time of the current XNA game. Looking at some of the function in a diassembler we'll see that using the GameTime object will look like this:
  1. IL_1922: ldarg.0
  2. IL_1923: ldarg.1
  3. IL_1924: call instance void [Microsoft.Xna.Framework.Game]Microsoft.Xna.Framework.Game::Update(class [Microsoft.Xna.Framework.Game]Microsoft.Xna.Framework.GameTime)
  4. IL_1929: ret
We see that two arguments are being pushed to Update. That's because this is a class method so ldarg.0 is the base Game object, and ldarg.1 is the GameTime object. So let's add our handlers first for Update:
  1. public static void PreUpdate(GameTime gameTime)
  2. {
  3. }
  4. public static void PostUpdate(GameTime gameTime)
  5. {
  6. }
Next we'll do the similar stuff to get the function. But we will add an extra opcode to push the gameTime argument:
  1. MethodDefinition updateMethod = GetMethodDefinition(GetTypeDefinition("Main"), "Update");
  2. var updateProc = updateMethod.Body.GetILProcessor();
  3. var updateInst = updateMethod.Body.Instructions[0];
  4. updateProc.InsertBefore(updateInst, updateProc.Create(OpCodes.Ldarg_1)); // push gameTime
  5. updateProc.InsertBefore(updateInst, updateProc.Create(OpCodes.Call,
  6.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PreUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  7.     ));
Now, if you wish you could push Ldarg_0 as well to your function, but you will need to add the param for the Game object.

Next we want to inject our PostUpdate call. The above IL code I posted about the Update function is the end of the function, so we know it ends with a Ret. We want to push our code, again, before the return.
  1. updateProc.InsertBefore(updateMethod.Body.Instructions[updateMethod.Body.Instructions.Count - 1], updateProc.Create(OpCodes.Ldarg_1)); // push gameTime
  2. updateProc.InsertBefore(updateMethod.Body.Instructions[updateMethod.Body.Instructions.Count - 1], updateProc.Create(OpCodes.Call,
  3.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PostUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  4.     ));
For draw, we'll do the same thing. Add the two methods calls:
  1. public static void PreDraw(GameTime gameTime)
  2. {
  3. }
  4. public static void PostDraw(GameTime gameTime)
  5. {
  6. }
Then we'll add our hook code the same way as Update:
  1. MethodDefinition drawMethod = GetMethodDefinition(GetTypeDefinition("Main"), "Draw");
  2. var drawProc = drawMethod.Body.GetILProcessor();
  3. var drawInst = drawMethod.Body.Instructions[0];
  4. drawProc.InsertBefore(drawInst, drawProc.Create(OpCodes.Ldarg_1)); // push gameTime
  5. drawProc.InsertBefore(drawInst, drawProc.Create(OpCodes.Call,
  6.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PreDraw", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  7.     ));
  8.  
  9. drawProc.InsertBefore(drawMethod.Body.Instructions[drawMethod.Body.Instructions.Count - 1], drawProc.Create(OpCodes.Ldarg_1));
  10. drawProc.InsertBefore(drawMethod.Body.Instructions[drawMethod.Body.Instructions.Count - 1], drawProc.Create(OpCodes.Call,
  11.     this.m_vModDefinition.Import(typeof(Hooks).GetMethod("PostDraw", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
  12.     ));
There we have it, all of the main XNA calls are now hooked as we wanted. :)

Hooking Other Methods
So now that we have hooked all the XNA methods, lets hook something else. In this case I will be using the collectors edition method CheckBunny. This method determines if you have the Collectors Edition of the game by looking in your Registry for a specific key. The IL of this code looks like:
  1. .try
  2. {
  3.     IL_0000: ldsfld class [mscorlib]Microsoft.Win32.RegistryKey [mscorlib]Microsoft.Win32.Registry::CurrentUser
  4.     IL_0005: stloc.0
  5.     IL_0006: ldloc.0
  6.     IL_0007: ldstr "Software\\Terraria"
  7.     IL_000c: callvirt instance class [mscorlib]Microsoft.Win32.RegistryKey [mscorlib]Microsoft.Win32.RegistryKey::CreateSubKey(string)
  8.     IL_0011: stloc.0
  9.     IL_0012: ldloc.0
  10.     IL_0013: brfalse.s IL_0044
  11.  
  12.     IL_0015: ldloc.0
  13.     IL_0016: ldstr "Bunny"
  14.     IL_001b: callvirt instance object [mscorlib]Microsoft.Win32.RegistryKey::GetValue(string)
  15.     IL_0020: brfalse.s IL_0044
  16.  
  17.     IL_0022: ldloc.0
  18.     IL_0023: ldstr "Bunny"
  19.     IL_0028: callvirt instance object [mscorlib]Microsoft.Win32.RegistryKey::GetValue(string)
  20.     IL_002d: callvirt instance string [mscorlib]System.Object::ToString()
  21.     IL_0032: ldstr "1"
  22.     IL_0037: call bool [mscorlib]System.String::op_Equality(string, string)
  23.     IL_003c: brfalse.s IL_0044
  24.  
  25.     IL_003e: ldc.i4.1
  26.     IL_003f: stsfld bool Terraria.Main::cEd
  27.  
  28.     IL_0044: leave.s IL_004f
  29. } // end .try
  30. catch [mscorlib]System.Object
  31. {
  32.     IL_0046: pop
  33.     IL_0047: ldc.i4.0
  34.     IL_0048: stsfld bool Terraria.Main::cEd
  35.     IL_004d: leave.s IL_004f
  36. } // end handler
  37.  
  38. IL_004f: ret
However, instead of injecting anything, we'll just rewrite the entire method. We knw that we want to set the property cEd to true. Which can be done with:
  1. IL_003e: ldc.i4.1
  2. IL_003f: stsfld bool Terraria.Main::cEd
So we'll rewrite this method using:
  1. MethodDefinition bunnyMethod = GetMethodDefinition(GetTypeDefinition("Main"), "CheckBunny");
  2. FieldDefinition bunnyField = GetFieldDefinition(GetTypeDefinition("Main"), "cEd");
  3.  
  4. // Clear the old function instructions..
  5. bunnyMethod.Body.Instructions.Clear();
  6.  
  7. // Remove the exception handler..
  8. bunnyMethod.Body.ExceptionHandlers.Clear();
  9.  
  10. // Rewrite the function..
  11. var bunnyProc = bunnyMethod.Body.GetILProcessor();
  12. bunnyProc.Append(bunnyProc.Create(OpCodes.Nop)); // nop for alignment
  13. bunnyProc.Append(bunnyProc.Create(OpCodes.Ldc_I4_1)); // push true (1) onto the stack
  14. bunnyProc.Append(bunnyProc.Create(OpCodes.Stsfld, bunnyField)); // set field to the value on stack
  15. bunnyProc.Append(bunnyProc.Create(OpCodes.Ret)); // return
My Methods Aren't Getting Called In The Right Order!
With Terraria as an example you may notice that your XNA hooks may not be called in the right order. This is due to how Terraria is written. For example, Draw has multiple returns within it to handle drawing certain parts of the game. For instance, it will attempt to Draw the menu specifically with:
  1. IL_52dd: ldarg.0
  2. IL_52de: call instance void Terraria.Main::DrawMenu()
  3. IL_52e3: ret
Which will return before our PostDraw is called while we are at the main menu. There are a few ways to correct this. Without actually digging into the code here are some suggestions you can do to fix problems like:
  • Hook DrawMenu and call PostDraw at the end of it to ensure your PostDraw is called. (I recommend this method.)
  • Inject your PostDraw call after every Ret inside the function to ensure it gets called.
  • Disassemble the code and look to see where you should place each Post call to ensure that it will always be called. (This could become a tiresome process if its a large method.)
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
User avatar
atom0s
Site Admin
Posts: 397
Joined: Sun Jan 04, 2015 11:23 pm
Location: 127.0.0.1
Contact:

Re: C# Loader Hooking With Mono.Cecil

Post by atom0s » Fri Jan 09, 2015 10:34 am

Another fun thing you can do is use a custom attribute to automatically find methods in your application that will be applying detours/edits to the new assembly and execute them like this:
InjectionAttribute.cs
  1. // -----------------------------------------------------------------------
  2. // <copyright file="InjectionAttribute.cs" company="">
  3. // TODO: Update copyright text.
  4. // </copyright>
  5. // -----------------------------------------------------------------------
  6.  
  7. namespace toxyClient.Classes
  8. {
  9.     using System;
  10.     using System.Collections.Generic;
  11.     using System.Linq;
  12.     using System.Text;
  13.  
  14.     /// <summary>
  15.     /// InjectionAttribute Class Implementation
  16.     ///
  17.     /// Attribute used to mark a function as an injection function.
  18.     ///
  19.     /// The main program class will scan the application for this
  20.     /// attribute and automatically call the functions.
  21.     /// </summary>
  22.     public class InjectionAttribute : Attribute
  23.     {
  24.         /// <summary>
  25.         /// Default Constructor
  26.         /// </summary>
  27.         /// <param name="detourName"></param>
  28.         /// <param name="detourDesc"></param>
  29.         public InjectionAttribute(String detourName, String detourDesc = "")
  30.         {
  31.             this.Name = detourName;
  32.             this.Desc = detourDesc;
  33.         }
  34.  
  35.         /// <summary>
  36.         /// Gets or sets the name of this detour.
  37.         /// </summary>
  38.         public String Name { get; set; }
  39.  
  40.         /// <summary>
  41.         /// Gets or sets the desc of this detour.
  42.         /// </summary>
  43.         public String Desc { get; set; }
  44.     }
  45. }
Then in your main Program.cs code use this before you call Finalize:
  1. // Scan for detour attributes and apply their detour..
  2. (from Type t in Assembly.GetExecutingAssembly().GetTypes()
  3.  from MethodInfo m in t.GetMethods()
  4.  from InjectionAttribute d in m.GetCustomAttributes(typeof(InjectionAttribute), false)
  5.  select m).ToList().ForEach(m => m.Invoke(null, null));
Then any function that you want to apply detours/injection stuff with to the new assembly just mark with the new custom attribute like this:
  1. /// <summary>
  2. /// Applies the detours required by this class.
  3. /// </summary>
  4. [Injection("Some Name Here", "A short description about what this function is for.")]
  5. public static void DoInjection()
  6. {
  7. }
Derp~
Need a great web host? Check out: AnHonestHost.com


Donations can be made via Paypal:
https://www.paypal.me/atom0s
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest