Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save anonymous/4358405 to your computer and use it in GitHub Desktop.
Save anonymous/4358405 to your computer and use it in GitHub Desktop.
Replace event add_/remove_ methods with versions that do not use Interlocked.CompareExchange<T>. Instead they use Delegate.Combine/Remove in a way that lacks thread safety. Needed for AOT compilation on Unity3D
// CAUTION: This can irreversibly change your DLLs.
// Reference: Mono.Cecil (DLL version 0.9.4.0, from package Cecil 2.10.9)
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Linq;
using System.IO;
using System.Collections.Generic;
namespace Rewriter
{
class MainClass
{
public static int Verbosity = 1;
public static void Main(string[] args)
{
foreach(var fileName in
Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll")
.Concat(Directory.GetFiles(Directory.GetCurrentDirectory(), "*.exe")))
{
try
{
string outFileName = fileName + "-orig-";
while(File.Exists(outFileName))
{
outFileName += "-";
}
ReplaceCE(fileName, outFileName);
}
catch(Exception ex)
{
Console.WriteLine("Exception processing dll " + fileName + ": " + ex);
}
}
}
public static void ReplaceCE(string fileName, string outFileName, bool keepOriginal = true, bool swap = true)
{
int replaced = 0;
int skipped = 0;
if(!File.Exists(fileName)) throw new ArgumentException("File not found: " + fileName);
if(Verbosity >= 4) Console.WriteLine("Rewriting add/remove methods for " + fileName + " --> " + outFileName);
if(Verbosity >= 9) Console.WriteLine(typeof (Delegate).Module.FullyQualifiedName);
AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(fileName);
ModuleDefinition module = assembly.MainModule;
TypeReference delegateTypeDef = module.Import(typeof(Delegate));
MethodReference combineR = delegateTypeDef.Module.Import(
typeof(Delegate).GetMethod("Combine",
new Type[] { typeof(Delegate), typeof(Delegate) }));
MethodReference removeR = delegateTypeDef.Module.Import(
typeof(Delegate).GetMethod("Remove",
new Type[] { typeof(Delegate), typeof(Delegate) }));
foreach(TypeDefinition type in module.Types)
{
if(type.IsInterface) continue;
foreach(var method in type.Methods)
{
try
{
string fieldName;
MethodReference methodR;
#region Get required info or continue
if(method.Name.StartsWith("add_"))
{
fieldName = method.Name.Substring("add_".Length);
methodR = combineR;
}
else if(method.Name.StartsWith("remove_"))
{
fieldName = method.Name.Substring("remove_".Length);
methodR = removeR;
}
else
{
continue;
}
#endregion
var newI = new List<Instruction>();
var processor = method.Body.GetILProcessor();
bool foundCompExch = false;
foreach(var existingInstruction in processor.Body.Instructions)
{
if(existingInstruction.OpCode == OpCodes.Call)
{
var meth = existingInstruction.Operand as MethodReference;
if(meth!=null)
{
if(meth.Name == "CompareExchange")
{
if(Verbosity > 7) Console.WriteLine(" Found CompareExchange");
foundCompExch = true;
break;
}
}
}
}
if(!foundCompExch)
{
if(Verbosity >= 3)
{
Console.WriteLine(" . ignoring body with no CompareExchange: " + type.Name + "." + method.Name);
}
skipped++;
continue;
}
FieldReference field = type.Fields.Where(f => f.Name == fieldName).FirstOrDefault();
if(field == null) throw new Exception("Could not find field: " + fieldName + " in type " + type.FullName);
// IL_0000: ldarg.0
newI.Add(processor.Create(OpCodes.Ldarg_0));
// IL_0001: ldarg.0
newI.Add(processor.Create(OpCodes.Ldarg_0));
// IL_0002: ldfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler
newI.Add(processor.Create(OpCodes.Ldfld, field));
// IL_0007: ldarg.1
newI.Add(processor.Create(OpCodes.Ldarg_1));
// IL_0008: call class [mscorlib]System.Delegate class [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
newI.Add(processor.Create(OpCodes.Call, methodR));
// IL_000d: castclass MyNamespace.ActionBool
newI.Add(processor.Create(OpCodes.Castclass, field.FieldType));
// IL_0012: stfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler
newI.Add(processor.Create(OpCodes.Stfld, field));
// IL_0017: ret
newI.Add(processor.Create(OpCodes.Ret));
#region Replace the instructions
replaced++;
processor.Body.Instructions.Clear();
foreach(var i in newI)
{
processor.Body.Instructions.Add(i);
}
if(Verbosity >= 3)
{
Console.WriteLine(" - replaced method: " + type.Name + "." + method.Name);
}
#endregion
}
catch(Exception ex)
{
Console.WriteLine("Exception for method: " + type.FullName + "." + method.Name + ": " + ex);
}
}
}
if(Verbosity >= 1) Console.WriteLine("Replaced " + replaced + " event methods (" + skipped + " skipped) in " + Path.GetFileName(fileName));
if(swap)
{
File.Move(fileName, outFileName);
assembly.Write(fileName);
if(!keepOriginal) File.Delete(outFileName);
}
else
{
assembly.Write(outFileName);
if(!keepOriginal) File.Delete(fileName);
}
}
// Used monodis to get the desired add_/remove_ methods from a simpler C# implementation:
// // method line 2581
// .method private hidebysig specialname
// instance default void add_MyHandlerZYX (class MyNamespace.ActionBool 'value') cil managed
// {
// // Method begins at RVA 0x20000
// // Code size 24 (0x18)
// .maxstack 8
// IL_0000: ldarg.0
// IL_0001: ldarg.0
// IL_0002: ldfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler
// IL_0007: ldarg.1
// IL_0008: call class [mscorlib]System.Delegate class [mscorlib]System.Delegate::Combine(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
// IL_000d: castclass MyNamespace.ActionBool
// IL_0012: stfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler
// IL_0017: ret
// } // end of method EnableableBase::add_MyHandlerZYX
// // method line 2846
// .method private hidebysig specialname
// instance default void remove_MyHandlerZYX (class MyNamespace.ActionBool 'value') cil managed
// {
// // Method begins at RVA 0x226d4
// // Code size 24 (0x18)
// .maxstack 8
// IL_0000: ldarg.0
// IL_0001: ldarg.0
// IL_0002: ldfld class Namespace.ActionBool MyNamespace.EnableableBase::myHandler
// IL_0007: ldarg.1
// IL_0008: call class [mscorlib]System.Delegate class [mscorlib]System.Delegate::Remove(class [mscorlib]System.Delegate, class [mscorlib]System.Delegate)
// IL_000d: castclass MyNamespace.ActionBool
// IL_0012: stfld class MyNamespace.ActionBool MyNamespace.EnableableBase::myHandler
// IL_0017: ret
// } // end of method EnableableBase::remove_MyHandlerZYX
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment