using System; using System.Linq; using System.Reflection; using Mono.Cecil; using Mono.Cecil.Cil; namespace NeighPatcher { [AttributeUsage(AttributeTargets.Method)] internal class NeighPatchAttribute : Attribute { public string[] ArgTypes; public string AssemblyName; public string MethodName; public string TypeName; public NeighPatchAttribute(string assemblyName, string typeName, string methodName, params string[] argTypes) { AssemblyName = assemblyName; TypeName = typeName; MethodName = methodName; ArgTypes = argTypes; } } internal static class NeighPatcher { public static void PatchAll(AssemblyDefinition targetAssembly, string patchTypeName) { var hookAd = AssemblyDefinition.ReadAssembly(Assembly.GetExecutingAssembly().Location); var patchType = hookAd.MainModule.GetType(patchTypeName); Console.WriteLine($"[NeighPatcher] Patching {targetAssembly.Name.Name} with {patchType.FullName}"); foreach (var methodDefinition in patchType.Methods.Where(m => m.HasCustomAttributes)) { var patchAttributes = methodDefinition .CustomAttributes .Where(c => c.HasConstructorArguments && c.Constructor.DeclaringType.Name == nameof(NeighPatchAttribute)) .ToList(); if (patchAttributes.Count == 0) continue; Console.WriteLine( $"[NeighPatcher] Applying {methodDefinition.FullName} with {patchAttributes.Count} patch attributes"); var patchAttrs = patchAttributes.Select(p => new NeighPatchAttribute( p.ConstructorArguments[0].Value as string, p.ConstructorArguments[1].Value as string, p.ConstructorArguments[2].Value as string, ((CustomAttributeArgument[])p.ConstructorArguments[3].Value) .Select(c => c.Value as string).ToArray())).ToList(); foreach (var neighPatchAttribute in patchAttrs) { Console.WriteLine( $"[NeighPatcher] Patching [{neighPatchAttribute.AssemblyName}]{neighPatchAttribute.TypeName}::{neighPatchAttribute.MethodName} with {neighPatchAttribute.ArgTypes.Length} arg types"); if (targetAssembly.Name.Name != neighPatchAttribute.AssemblyName) continue; var t = targetAssembly.MainModule.GetType(neighPatchAttribute.TypeName); Console.WriteLine($"[NeighPatcher] Found type {t}"); var method = t?.Methods.FirstOrDefault(m => m.Name == neighPatchAttribute.MethodName && (neighPatchAttribute.ArgTypes.Length == 0 || neighPatchAttribute.ArgTypes.Length != 0 && neighPatchAttribute.ArgTypes.Length == m.Parameters.Count && m.Parameters.Select(p => p.ParameterType.FullName) .SequenceEqual(neighPatchAttribute.ArgTypes))); Console.WriteLine($"[NeighPatcher] Found method {method}"); if (method == null) continue; var il = method.Body.GetILProcessor(); var ins = method.Body.Instructions.First(); foreach (var mParam in methodDefinition.Parameters) if (mParam.Name == "__instance") il.InsertBefore(ins, il.Create(OpCodes.Ldarg_0)); else if (mParam.Name.StartsWith("___")) { string realName = mParam.Name.Substring(3); var targetField = t.Fields.FirstOrDefault(ff => ff.Name == realName); if (targetField == null) throw new Exception($"Field {realName} not found from type {t.FullName}!"); if (!method.IsStatic) { il.InsertBefore(ins, il.Create(OpCodes.Ldarg_0)); il.InsertBefore(ins, il.Create(OpCodes.Ldflda, targetField)); } else il.InsertBefore(ins, il.Create(OpCodes.Ldsflda, targetField)); } else { var pIndex = method.Parameters.FirstOrDefault(p => p.Name == mParam.Name); if (pIndex == null) throw new Exception( $"Parameter with name {mParam.Name} does not exist in {method.FullName}"); il.InsertBefore(ins, il.Create(OpCodes.Ldarg, pIndex)); } il.InsertBefore( ins, il.Create(OpCodes.Call, targetAssembly.MainModule.ImportReference(methodDefinition))); } } } } }