Last active
July 31, 2024 23:07
-
-
Save gsuberland/6b20f735048a15a6bf4518331a420695 to your computer and use it in GitHub Desktop.
C# code to generate Harmony prefix and postfix stubs against a target type
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[HarmonyPatch] | |
class Target_Patch | |
{ | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test1")] | |
[HarmonyPrefix] | |
public static void Test1_Prefix(UserQuery.Target __instance) | |
{ | |
Log.Log("function Test1 is about to do a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test1")] | |
[HarmonyPostfix] | |
public static void Test1_Postfix(UserQuery.Target __instance) | |
{ | |
Log.Log("function Test1 did a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test2")] | |
[HarmonyPrefix] | |
public static void Test2_Prefix(UserQuery.Target __instance) | |
{ | |
Log.Log("function Test2 is about to do a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test2")] | |
[HarmonyPostfix] | |
public static System.String Test2_Postfix(UserQuery.Target __instance, System.String __result) | |
{ | |
Log.Log("function Test2 did a thing!"); return __result; | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test3")] | |
[HarmonyPrefix] | |
public static void Test3_Prefix(UserQuery.Target __instance, System.String foo) | |
{ | |
Log.Log("function Test3 is about to do a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test3")] | |
[HarmonyPostfix] | |
public static System.String Test3_Postfix(UserQuery.Target __instance, System.String __result, System.String foo) | |
{ | |
Log.Log("function Test3 did a thing!"); return __result; | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test4")] | |
[HarmonyPrefix] | |
public static void Test4_Prefix(UserQuery.Target __instance, System.Collections.Generic.List<System.String> list) | |
{ | |
Log.Log("function Test4 is about to do a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test4")] | |
[HarmonyPostfix] | |
public static System.String Test4_Postfix(UserQuery.Target __instance, System.String __result, System.Collections.Generic.List<System.String> list) | |
{ | |
Log.Log("function Test4 did a thing!"); return __result; | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test5")] | |
[HarmonyPrefix] | |
public static void Test5_Prefix(UserQuery.Target __instance, System.String[] array) | |
{ | |
Log.Log("function Test5 is about to do a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test5")] | |
[HarmonyPostfix] | |
public static System.Collections.Generic.List<System.String> Test5_Postfix(UserQuery.Target __instance, System.Collections.Generic.List<System.String> __result, System.String[] array) | |
{ | |
Log.Log("function Test5 did a thing!"); return __result; | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test6")] | |
[HarmonyPrefix] | |
public static void Test6_Prefix(UserQuery.Target __instance) | |
{ | |
Log.Log("function Test6 is about to do a thing!"); | |
} | |
[HarmonyPatch(typeof(UserQuery.Target), @"UserQuery.Target.Test6")] | |
[HarmonyPostfix] | |
public static System.Collections.Generic.Dictionary<System.String, System.Int32> Test6_Postfix(UserQuery.Target __instance, System.Collections.Generic.Dictionary<System.String, System.Int32> __result) | |
{ | |
Log.Log("function Test6 did a thing!"); return __result; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Target | |
{ | |
private void Test1() | |
{ | |
} | |
private string Test2() | |
{ | |
return ""; | |
} | |
private string Test3(string foo) | |
{ | |
return foo; | |
} | |
private string Test4(List<string> list) | |
{ | |
return list.Count.ToString(); | |
} | |
public List<string> Test5(string[] array) | |
{ | |
return array.ToList(); | |
} | |
public Dictionary<string,int> Test6() | |
{ | |
return new Dictionary<string,int>(); | |
} | |
} | |
void Main() | |
{ | |
Console.WriteLine(GenerateStubs<Target>()); | |
} | |
string GenerateStubs<T>() | |
{ | |
return GenerateStubs(typeof(T)); | |
} | |
string GenerateStubs(Type type) | |
{ | |
string prefixStubCode = "Log.Log(\"function $NAME$ is about to do a thing!\");"; | |
string postfixStubCode = "Log.Log(\"function $NAME$ did a thing!\"); return __result;"; | |
string postfixStubVoidCode = "Log.Log(\"function $NAME$ did a thing!\");"; | |
var instanceMethods = type.GetMethods( | |
BindingFlags.Public | | |
BindingFlags.NonPublic | | |
BindingFlags.Instance | | |
BindingFlags.DeclaredOnly); | |
string targetTypeName = GetCSharpTypeName(type); | |
StringBuilder sb = new StringBuilder(); | |
sb.AppendLine("[HarmonyPatch]"); | |
sb.AppendLine($"class {type.Name}_Patch"); | |
sb.AppendLine("{"); | |
foreach (var method in instanceMethods) | |
{ | |
// get the return type name as it would be written in C# | |
var returnTypeName = GetCSharpTypeName(method.ReturnType); | |
// normally we'd want to special-case special method names like get_Foo back to their property, but since this is for Harmony we actually don't want that | |
var methodName = method.Name; | |
// if the method has any args, tack them on to the pre/postfix method args | |
var methodArgs = string.Join(", ", method.GetParameters().Select(p => GetCSharpTypeName(p.ParameterType) + " " + p.Name)); | |
if (methodArgs.Length > 0) | |
{ | |
methodArgs = ", " + methodArgs; | |
} | |
// prefixes return void | |
sb.AppendLine($"\t[HarmonyPatch(typeof({targetTypeName}), @\"{targetTypeName}.{methodName}\")]"); | |
sb.AppendLine($"\t[HarmonyPrefix]"); | |
sb.AppendLine($"\tpublic static void {methodName}_Prefix({targetTypeName} __instance{methodArgs})"); | |
sb.AppendLine("\t{"); | |
sb.AppendLine("\t\t" + prefixStubCode.Replace("$NAME$", methodName)); | |
sb.AppendLine("\t}"); | |
sb.AppendLine(); | |
// postfixes may or may not return void | |
sb.AppendLine($"\t[HarmonyPatch(typeof({targetTypeName}), @\"{targetTypeName}.{methodName}\")]"); | |
sb.AppendLine($"\t[HarmonyPostfix]"); | |
if (returnTypeName == "void") | |
{ | |
sb.AppendLine($"\tpublic static {returnTypeName} {methodName}_Postfix({targetTypeName} __instance{methodArgs})"); | |
sb.AppendLine("\t{"); | |
sb.AppendLine("\t\t" + postfixStubVoidCode.Replace("$NAME$", methodName)); | |
} | |
else | |
{ | |
sb.AppendLine($"\tpublic static {returnTypeName} {methodName}_Postfix({targetTypeName} __instance, {returnTypeName} __result{methodArgs})"); | |
sb.AppendLine("\t{"); | |
sb.AppendLine("\t\t" + postfixStubCode.Replace("$NAME$", methodName)); | |
} | |
sb.AppendLine("\t}"); | |
sb.AppendLine(); | |
} | |
sb.AppendLine("}"); | |
sb.AppendLine(); | |
return sb.ToString(); | |
} | |
static string GetCSharpTypeName(Type t) | |
{ | |
if (t == null) | |
{ | |
throw new ArgumentNullException(nameof(t)); | |
} | |
if (t == typeof(void)) | |
{ | |
return @"void"; | |
} | |
if (t.IsArray) | |
{ | |
return GetCSharpTypeName(t.GetElementType()) + "[]"; | |
} | |
// if the full name is available, we want to use that rather than the simple class name | |
// we replace + with . to handle nested types | |
string fullTypeName = (t.FullName ?? t.Name).Replace('+', '.'); | |
if (t.IsGenericType) | |
{ | |
// .NET provides type names in a format like System.Collections.ObjectModel.ReadOnlyCollection`1[System.String] | |
// which is not how C# writes generics. we can grab the left side of the ` to get the original name, then the generic args. | |
string typeName = fullTypeName.Split('`').First(); | |
string typeArgs = string.Join(", ", t.GetGenericArguments().Select(GetCSharpTypeName)); | |
return $"{typeName}<{typeArgs}>"; | |
} | |
return fullTypeName; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment