Skip to content

Instantly share code, notes, and snippets.

@ByronMayne
Last active April 30, 2020 13:35
Show Gist options
  • Save ByronMayne/ec107d5a5f7196b132c561dccec38a71 to your computer and use it in GitHub Desktop.
Save ByronMayne/ec107d5a5f7196b132c561dccec38a71 to your computer and use it in GitHub Desktop.
This code is used to inject a callback into Unity's internal build system. They should have this but if you really want to you can just make your own. This only needs Mono.Cecil #Magic
public class Injector
{
[MenuItem("Hacks/Inject Build Callback")]
public static void BuildCallback()
{
Assembly.BeginEditingAssembly(AssemblyTypes.UnityEditor, editAlreadyModifed:true);
{
TypeDefinition buildPiplelineType = Assembly.GetType<BuildPipeline>();
MethodDefinition buildInternalMethod = buildPiplelineType.GetMethod("BuildPlayerInternal");
TypeReference MultiCastDelegateTypeRef = Assembly.Import<System.MulticastDelegate>();
TypeDefinition MultiCastDelegateTypeDef = new TypeDefinition("", "OnBuildStartedDelegate", TypeAttributes.Public, MultiCastDelegateTypeRef);
MethodDefinition ConstructorMethodDef = new MethodDefinition(".ctor", MethodAttributes.Public |
MethodAttributes.CompilerControlled |
MethodAttributes.RTSpecialName |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
Assembly.Import(typeof(void)));
// Constructor
ConstructorMethodDef.IsRuntime = true;
ConstructorMethodDef.HasThis = true;
ConstructorMethodDef.IsHideBySig = true;
ConstructorMethodDef.IsPublic = true;
ConstructorMethodDef.Parameters.Add(new ParameterDefinition("levels", ParameterAttributes.None, Assembly.Import<string[]>()));
ConstructorMethodDef.Parameters.Add(new ParameterDefinition("buildLocation", ParameterAttributes.None, Assembly.Import<string>()));
ConstructorMethodDef.Parameters.Add(new ParameterDefinition("buildTarget", ParameterAttributes.None, Assembly.Import<BuildTarget>()));
ConstructorMethodDef.Parameters.Add(new ParameterDefinition("buildOptions", ParameterAttributes.None, Assembly.Import<BuildOptions>()));
MultiCastDelegateTypeDef.Methods.Add(ConstructorMethodDef);
// Invoking Method
MethodDefinition BeginInvokeMethodDef = new MethodDefinition("BeginInvoke", MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.NewSlot |
MethodAttributes.Virtual,
Assembly.Import<System.AsyncCallback>());
BeginInvokeMethodDef.IsRuntime = true;
BeginInvokeMethodDef.HasThis = true;
BeginInvokeMethodDef.IsHideBySig = true;
BeginInvokeMethodDef.IsRuntimeSpecialName = true;
BeginInvokeMethodDef.IsSpecialName = true;
BeginInvokeMethodDef.IsPublic = true;
BeginInvokeMethodDef.IsVirtual = true;
BeginInvokeMethodDef.Parameters.Add(new ParameterDefinition("buildLocation", ParameterAttributes.None, Assembly.Import<string>()));
BeginInvokeMethodDef.Parameters.Add(new ParameterDefinition("buildTarget", ParameterAttributes.None, Assembly.Import<BuildTarget>()));
BeginInvokeMethodDef.Parameters.Add(new ParameterDefinition("buildOptions", ParameterAttributes.None, Assembly.Import<BuildOptions>()));
BeginInvokeMethodDef.Parameters.Add(new ParameterDefinition("callback", ParameterAttributes.None, Assembly.Import<System.IAsyncResult>()));
BeginInvokeMethodDef.Parameters.Add(new ParameterDefinition("object", ParameterAttributes.None, Assembly.Import<object>()));
MultiCastDelegateTypeDef.Methods.Add(BeginInvokeMethodDef);
// End Invoke
MethodDefinition EndInvokeMethodDef = new MethodDefinition("EndInvoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, Assembly.Import(typeof(void)));
EndInvokeMethodDef.Parameters.Add(new ParameterDefinition("result", ParameterAttributes.None, Assembly.Import<System.IAsyncResult>()));
EndInvokeMethodDef.IsRuntime = true;
EndInvokeMethodDef.HasThis = true;
EndInvokeMethodDef.IsHideBySig = true;
EndInvokeMethodDef.IsRuntimeSpecialName = true;
EndInvokeMethodDef.IsSpecialName = true;
EndInvokeMethodDef.IsPublic = true;
MultiCastDelegateTypeDef.Methods.Add(EndInvokeMethodDef);
// Invoke
MethodDefinition InvokeMethodDef = new MethodDefinition("Invoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, Assembly.Import(typeof(void)));
InvokeMethodDef.Parameters.Add(new ParameterDefinition("levels", ParameterAttributes.None, Assembly.Import<string[]>()));
InvokeMethodDef.Parameters.Add(new ParameterDefinition("buildLocation", ParameterAttributes.None, Assembly.Import<string>()));
InvokeMethodDef.Parameters.Add(new ParameterDefinition("buildTarget", ParameterAttributes.None, Assembly.Import<BuildTarget>()));
InvokeMethodDef.Parameters.Add(new ParameterDefinition("buildOptions", ParameterAttributes.None, Assembly.Import<BuildOptions>()));
InvokeMethodDef.IsRuntime = true;
InvokeMethodDef.HasThis = true;
InvokeMethodDef.IsHideBySig = true;
InvokeMethodDef.IsVirtual = true;
InvokeMethodDef.IsPublic = true;
MultiCastDelegateTypeDef.Methods.Add(InvokeMethodDef);
// Add our new delegate type
buildPiplelineType.NestedTypes.Add(MultiCastDelegateTypeDef);
// Add a field for it
FieldDefinition delegateFieldDef = new FieldDefinition("onBuildStarted", FieldAttributes.Public | FieldAttributes.Static, MultiCastDelegateTypeDef.Resolve());
buildPiplelineType.Fields.Add(delegateFieldDef);
{
ILProcessor ilProcessor = buildInternalMethod.Body.GetILProcessor();
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Nop));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Callvirt, InvokeMethodDef));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Ldarg_3));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Ldarg_2));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Ldarg_1));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Ldarg_0));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Ldsfld, delegateFieldDef));
ilProcessor.InsertBefore(buildInternalMethod.Body.Instructions[0], ilProcessor.Create(OpCodes.Nop));
}
// Add invoke to BuildPlayerWindow
TypeDefinition buildPlayerType = Assembly.GetType("UnityEditor", "BuildPlayerWindow");
MethodDefinition buildDefaultMethodDef = buildPlayerType.GetMethod("BuildPlayerWithDefaultSettings", 3);
{
UnityEngine.Debug.Log(buildDefaultMethodDef.Parameters[2].Name);
ILProcessor ilProcessor = buildDefaultMethodDef.Body.GetILProcessor();
int index = buildDefaultMethodDef.Body.Instructions.Count - 13;
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Nop));
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Callvirt, InvokeMethodDef));
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Ldloc, 7)); // 7 Build Options
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Ldloc_1)); // Build Target
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Ldloc, 5)); // 5 text
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Ldloc, 14)); // 14 Levels
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Ldsfld, delegateFieldDef));
ilProcessor.InsertBefore(buildDefaultMethodDef.Body.Instructions[index], ilProcessor.Create(OpCodes.Nop));
}
}
Assembly.EndEditingAssembly(shouldSave: true);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment