using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("DolfeLive")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("An extension to Harmony aiming to create a Mixin like patching system via attributes")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0+c6aab3d77d505f30400936717b4a07117854452c")] [assembly: AssemblyProduct("Harmony.PatchExtensions")] [assembly: AssemblyTitle("Harmony.PatchExtensions")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/UltraModding/Harmony.PatchExtensions/tree/master")] [assembly: AssemblyVersion("1.2.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace HarmonyLib.PatchExtensions { internal static class ConflictResolver { public static void DetectPatchConflicts(Dictionary> patches, HashSet toRemove) { foreach (KeyValuePair> item in patches.Where>>((KeyValuePair> group) => group.Value.Count > 1)) { LogConflict(item.Key, item.Value.Select((QueuedPatch p) => p.HarmonyMethod.method)); switch (MixinLoader.ConflictResolutionMethod) { case MixinLoader.ConflictResolver.SkipConflicts: toRemove.Add(item.Key); break; case MixinLoader.ConflictResolver.Error: toRemove.Add(item.Key); throw new InvalidOperationException($"Conflict detected: {item.Value.Count} patches target {item.Key.Name}"); } } } public static void DetectTranspilerConflicts(Dictionary> transpilers, HashSet toRemove) { foreach (KeyValuePair> item in transpilers.Where>>((KeyValuePair> g) => g.Value.Count > 1)) { LogConflict(item.Key, item.Value.Select((TranspilerConfig t) => t.PatchMethod)); switch (MixinLoader.ConflictResolutionMethod) { case MixinLoader.ConflictResolver.SkipConflicts: toRemove.Add(item.Key); break; case MixinLoader.ConflictResolver.Error: toRemove.Add(item.Key); throw new InvalidOperationException($"Conflict detected: {item.Value.Count} transpiler patches target {item.Key.Name}"); } } } private static void LogConflict(MethodBase targetMethod, IEnumerable patchMethods) { Logger.LogWarning("Multiple Mixins queued for " + targetMethod.DeclaringType?.FullName + "." + targetMethod.Name); foreach (MethodInfo patchMethod in patchMethods) { string value = string.Join(", ", from p in patchMethod.GetParameters() select p.ParameterType.Name + " " + p.Name + (p.HasDefaultValue ? $" = {p.DefaultValue}" : "")); Logger.LogWarning($" - {patchMethod.DeclaringType?.FullName}.{patchMethod.Name}({value})"); } } } public static class Logger { public static void Log(string log) { Console.WriteLine("[HarmonyLib.PatchExtensions | Log] " + log); } public static void LogWarning(string log) { Console.WriteLine("[HarmonyLib.PatchExtensions | Warning] " + log); } public static void LogError(string log) { Console.WriteLine("[HarmonyLib.PatchExtensions | Error] " + log); } } internal static class MixinApplier { public static void ApplyPatches(Dictionary> _queuedPatches, Harmony harmony, ModuleBuilder _moduleBuilder) { //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown foreach (KeyValuePair> _queuedPatch in _queuedPatches) { MethodInfo key = _queuedPatch.Key; foreach (QueuedPatch item in _queuedPatch.Value) { switch (item.Type) { case AT.HEAD: if (item.Overwriting && item.PatchMethod.ReturnType != typeof(bool) && item.PatchMethod.ReturnType != typeof(void)) { if (item.PatchMethod.ReturnType != key.ReturnType) { Logger.LogError($"Patch {item.PatchMethod.Name} returns {item.PatchMethod.Name}, but target returns {key.ReturnType.Name}. They must match."); return; } Logger.Log($"Using wrapper as the method returns: {item.PatchMethod.ReturnType}"); MethodInfo methodInfo = BoolLessPrefix(key, item.PatchMethod, _moduleBuilder); if (methodInfo == null) { Logger.LogError("Failed to create wrapper for " + item.PatchMethod.Name); break; } Logger.Log("Applied HEAD (prefix) with wrapper on " + key.Name + " using " + item.HarmonyMethod.methodName); harmony.Patch((MethodBase)key, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { harmony.Patch((MethodBase)key, item.HarmonyMethod, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.Log("Applied HEAD (prefix) on " + key.Name + " using " + item.HarmonyMethod.methodName); } break; case AT.POSTFIX: harmony.Patch((MethodBase)key, (HarmonyMethod)null, item.HarmonyMethod, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Logger.Log("Applied RETURN (postfix) on " + key.Name + " using " + item.HarmonyMethod.methodName); break; default: throw new NotImplementedException($"Have not implemented: {item.Type}"); } } } } private static MethodInfo? BoolLessPrefix(MethodInfo targetMethod, MethodInfo userPatchMethod, ModuleBuilder _moduleBuilder) { string name = $"MixinWrapper_{userPatchMethod.Name}_{Guid.NewGuid():N}"; TypeBuilder typeBuilder = _moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Abstract | TypeAttributes.Sealed); ParameterInfo[] parameters = userPatchMethod.GetParameters(); Type returnType = targetMethod.ReturnType; List list = parameters.Select((ParameterInfo p) => p.ParameterType).ToList(); list.Add(returnType.MakeByRefType()); MethodBuilder methodBuilder = typeBuilder.DefineMethod("Wrapper_" + userPatchMethod.Name, MethodAttributes.Public | MethodAttributes.Static, typeof(bool), list.ToArray()); for (int num = 0; num < parameters.Length; num++) { methodBuilder.DefineParameter(num + 1, ParameterAttributes.None, parameters[num].Name); } methodBuilder.DefineParameter(parameters.Length + 1, ParameterAttributes.Out, "__result"); ILGenerator iLGenerator = methodBuilder.GetILGenerator(); LoadArg(iLGenerator, parameters.Length); for (int num2 = 0; num2 < parameters.Length; num2++) { LoadArg(iLGenerator, num2); } iLGenerator.Emit(OpCodes.Call, userPatchMethod); iLGenerator.Emit(OpCodes.Stobj, returnType); iLGenerator.Emit(OpCodes.Ldc_I4_0); iLGenerator.Emit(OpCodes.Ret); Type type = CreateType(typeBuilder); return type.GetMethod("Wrapper_" + userPatchMethod.Name); } private static Type CreateType(TypeBuilder typeBuilder) { return typeBuilder.CreateTypeInfo().AsType(); } private static void LoadArg(ILGenerator il, int index) { switch (index) { case 0: il.Emit(OpCodes.Ldarg_0); break; case 1: il.Emit(OpCodes.Ldarg_1); break; case 2: il.Emit(OpCodes.Ldarg_2); break; case 3: il.Emit(OpCodes.Ldarg_3); break; default: il.Emit(OpCodes.Ldarg, index); break; } } } public static class MixinLoader { public enum ConflictResolver { Warn, Error, SkipConflicts } public static ConflictResolver ConflictResolutionMethod; private static ModuleBuilder _moduleBuilder; private static Dictionary> _queuedTranspilers; private static Dictionary> _queuedPatches; public static Version LatestBreakingVersion { get; } static MixinLoader() { LatestBreakingVersion = new Version(1, 2, 0); ConflictResolutionMethod = ConflictResolver.Warn; _queuedTranspilers = new Dictionary>(); _queuedPatches = new Dictionary>(); AssemblyName name = new AssemblyName("DolfeMixinDynamicAssembly"); AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); _moduleBuilder = assemblyBuilder.DefineDynamicModule("MixinWrappers"); } public static void ApplyPatches(Harmony harmony, Assembly assembly) { WarnOutOfDate(); ApplyPatches(harmony, assembly, Array.Empty()); } public static void ApplyPatches(Harmony harmony, Assembly assembly, params Type[] patchTypes) { WarnOutOfDate(); HashSet allowedTypes = ((patchTypes.Length == 0) ? null : new HashSet(patchTypes)); ApplyPatches(harmony, assembly, allowedTypes); } private static void WarnOutOfDate() { Assembly assembly = new StackTrace().GetFrame(2)?.GetMethod()?.Module.Assembly ?? null; if (!(assembly == null)) { AssemblyName assemblyName = assembly.GetReferencedAssemblies().FirstOrDefault((AssemblyName a) => a.Name == Assembly.GetExecutingAssembly().GetName().Name); if (assemblyName != null && assemblyName.Version < LatestBreakingVersion) { Logger.LogError($"{assembly.FullName} is using an outdated version of Harmony.PatchExtensions ({assemblyName.Version}), a breaking update has been introduced since then ({LatestBreakingVersion})"); } } } private static void ApplyPatches(Harmony harmony, Assembly assembly, HashSet? allowedTypes) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Expected O, but got Unknown _queuedTranspilers.Clear(); _queuedPatches.Clear(); Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (allowedTypes != null && !allowedTypes.Contains(type)) { continue; } MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { IEnumerable customAttributes = methodInfo.GetCustomAttributes(); foreach (PatchAttribute item2 in customAttributes) { if (item2.TargetMethod == null) { Logger.LogWarning("You must set TargetMethod in " + methodInfo.Name + " for the Patch to work"); continue; } HarmonyMethod harmonyMethod = new HarmonyMethod(methodInfo); QueuedPatch item = new QueuedPatch(harmonyMethod, item2.At, item2.Overwriting, methodInfo); if (item2.At == AT.HEAD) { if (!_queuedPatches.ContainsKey(item2.TargetMethod)) { _queuedPatches[item2.TargetMethod] = new List(); } _queuedPatches[item2.TargetMethod].Add(item); Logger.Log("Queueing HEAD on " + item2.TargetMethod.Name); } else if (item2.At == AT.POSTFIX) { if (!_queuedPatches.ContainsKey(item2.TargetMethod)) { _queuedPatches[item2.TargetMethod] = new List(); } _queuedPatches[item2.TargetMethod].Add(item); Logger.Log("Queueing POSTFIX on " + item2.TargetMethod.Name); } else { if (item2.At != AT.INVOKE && item2.At != AT.REDIRECT && item2.At != AT.AFTER && item2.At != AT.RETURN) { continue; } if (string.IsNullOrEmpty(item2.TargetMember)) { Logger.LogWarning($"You must set 'target' in {methodInfo.Name} when using AT.{item2.At}"); } else { if (!_queuedTranspilers.ContainsKey(item2.TargetMethod)) { _queuedTranspilers[item2.TargetMethod] = new List(); } _queuedTranspilers[item2.TargetMethod].Add(new TranspilerConfig(item2.At, item2.TargetMember, methodInfo, item2.Occurrence, item2.StartIndex)); } } } } } HashSet hashSet = new HashSet(); HarmonyLib.PatchExtensions.ConflictResolver.DetectPatchConflicts(_queuedPatches, hashSet); foreach (MethodInfo item3 in hashSet) { _queuedPatches.Remove(item3); } HashSet hashSet2 = new HashSet(); HarmonyLib.PatchExtensions.ConflictResolver.DetectTranspilerConflicts(_queuedTranspilers, hashSet2); foreach (MethodBase item4 in hashSet2) { _queuedTranspilers.Remove(item4); } MixinApplier.ApplyPatches(_queuedPatches, harmony, _moduleBuilder); HarmonyMethod val = new HarmonyMethod(typeof(MixinLoader), "TranspilerPiler", (Type[])null); foreach (MethodBase key in _queuedTranspilers.Keys) { try { harmony.Patch(key, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); Logger.Log("Processed patch for " + key.Name); } catch (Exception ex) { Logger.LogError("Exception " + key.Name + ": " + ex.Message); } } } private static IEnumerable TranspilerPiler(IEnumerable instructions, MethodBase original, ILGenerator generator) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Expected O, but got Unknown //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Expected O, but got Unknown //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Expected O, but got Unknown //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_038e: Expected O, but got Unknown //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Expected O, but got Unknown //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_034d: Expected O, but got Unknown //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Expected O, but got Unknown //IL_03f8: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Expected O, but got Unknown //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Expected O, but got Unknown //IL_041f: Unknown result type (might be due to invalid IL or missing references) //IL_0425: Expected O, but got Unknown if (!_queuedTranspilers.TryGetValue(original, out List value)) { return instructions; } CodeMatcher val = new CodeMatcher(instructions, generator); foreach (TranspilerConfig config in value) { val.Start(); int num = 0; int num2 = 0; string requiredClass = ""; string requiredMethod = config.TargetMember; if (requiredMethod.Contains('.')) { string[] array = requiredMethod.Split('.'); requiredClass = array[0]; requiredMethod = array[1]; } else if (requiredMethod.Contains("::")) { string[] array2 = requiredMethod.Split(new string[1] { "::" }, StringSplitOptions.None); requiredClass = array2[0]; requiredMethod = array2[1]; } while (true) { val.MatchForward(false, (CodeMatch[])(object)new CodeMatch[1] { new CodeMatch((Func)delegate(CodeInstruction val2) { if (config.Type == AT.RETURN && val2.opcode == OpCodes.Ret) { return true; } if (config.Type == AT.RETURN) { return false; } bool flag2 = val2.opcode == OpCodes.Call || val2.opcode == OpCodes.Callvirt || val2.opcode == OpCodes.Newobj; bool flag3 = val2.opcode == OpCodes.Stfld || val2.opcode == OpCodes.Ldfld || val2.opcode == OpCodes.Ldsfld || val2.opcode == OpCodes.Stsfld || val2.opcode == OpCodes.Ldflda || val2.opcode == OpCodes.Ldsflda; if (!flag2 && !flag3) { return false; } string name; string text; if (flag2 && val2.operand is MethodInfo methodInfo3) { name = methodInfo3.Name; text = methodInfo3.DeclaringType?.Name; } else { if (!flag3 || !(val2.operand is FieldInfo fieldInfo)) { return false; } name = fieldInfo.Name; text = fieldInfo.DeclaringType?.Name; } if (name != requiredMethod) { return false; } return (string.IsNullOrEmpty(requiredClass) || !(text != requiredClass)) ? true : false; }, (string)null) }); if (val.IsInvalid) { break; } num++; if (config.StartIndex == 0 || num >= config.StartIndex) { num2++; if (config.Occurrence == 0 || num2 == config.Occurrence) { if (config.Type == AT.INVOKE) { val.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Call, (object)config.PatchMethod) }); } else if (config.Type == AT.REDIRECT) { CodeInstruction instruction = val.Instruction; if (!(instruction.operand is MethodBase originalMethod)) { Logger.LogWarning("REDIRECT target '" + config.TargetMember + "' is a field, not a method. Skipped."); val.Advance(1); continue; } if (!DontScrewUpStack(originalMethod, instruction.opcode, config.PatchMethod)) { Logger.LogWarning($"REDIRECT patch '{config.PatchMethod.Name}' doesn't match with '{config.TargetMember}'. Skipped."); val.Advance(1); continue; } val.SetInstruction(new CodeInstruction(OpCodes.Call, (object)config.PatchMethod)); } else if (config.Type == AT.AFTER) { CodeInstruction instruction2 = val.Instruction; bool flag = false; Type type = null; if (instruction2.operand is MethodInfo methodInfo) { flag = methodInfo.ReturnType != typeof(void); type = methodInfo.ReturnType; } val.Advance(1); if (flag && type != null) { LocalBuilder localBuilder = generator.DeclareLocal(type); val.Insert((CodeInstruction[])(object)new CodeInstruction[3] { new CodeInstruction(OpCodes.Stloc, (object)localBuilder), new CodeInstruction(OpCodes.Call, (object)config.PatchMethod), new CodeInstruction(OpCodes.Ldloc, (object)localBuilder) }); val.Advance(3); } else { val.InsertAndAdvance((CodeInstruction[])(object)new CodeInstruction[1] { new CodeInstruction(OpCodes.Call, (object)config.PatchMethod) }); } } else if (config.Type == AT.RETURN && original is MethodInfo methodInfo2 && methodInfo2.ReturnType != typeof(void)) { LocalBuilder localBuilder2 = generator.DeclareLocal(((MethodInfo)original).ReturnType); val.Insert((CodeInstruction[])(object)new CodeInstruction[3] { new CodeInstruction(OpCodes.Stloc, (object)localBuilder2), new CodeInstruction(OpCodes.Call, (object)config.PatchMethod), new CodeInstruction(OpCodes.Ldloc, (object)localBuilder2) }); val.Advance(3); } if (config.Occurrence != 0) { break; } } } val.Advance(1); } } return val.InstructionEnumeration(); } private static bool DontScrewUpStack(MethodBase originalMethod, OpCode opCode, MethodInfo patchMethod) { List list = (from p in originalMethod.GetParameters() select p.ParameterType).ToList(); if (opCode != OpCodes.Newobj && !originalMethod.IsStatic) { list.Insert(0, originalMethod.DeclaringType); } List list2 = (from p in patchMethod.GetParameters() select p.ParameterType).ToList(); if (list.Count != list2.Count) { return false; } for (int num = 0; num < list.Count; num++) { if (!list[num].IsAssignableFrom(list2[num])) { return false; } } Type type = ((originalMethod is MethodInfo methodInfo) ? methodInfo.ReturnType : originalMethod.DeclaringType); return patchMethod.ReturnType == type; } } public enum AT { HEAD, RETURN, POSTFIX, INVOKE, REDIRECT, AFTER, [Obsolete("Not yet implemented")] INSERT } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class PatchAttribute : Attribute { public MethodInfo TargetMethod { get; } public AT At { get; } public string TargetMember { get; } public bool Overwriting { get; } public uint Occurrence { get; } public uint StartIndex { get; } public PatchAttribute(Type type, string methodName, AT at, string target = null, uint occurrence = 0u, uint startIndex = 0u, bool overwriting = false) { TargetMethod = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (TargetMethod == null) { throw new ArgumentException($"Could not find method '{methodName}' in type '{type.FullName}'."); } At = at; TargetMember = target; Overwriting = overwriting; Occurrence = occurrence; StartIndex = startIndex; } } internal class QueuedPatch { public HarmonyMethod HarmonyMethod; public AT Type; public bool Overwriting; public MethodInfo PatchMethod; public QueuedPatch(HarmonyMethod harmonyMethod, AT type, bool overwriting, MethodInfo patchMethod) { HarmonyMethod = harmonyMethod; Type = type; Overwriting = overwriting; PatchMethod = patchMethod; } } internal class TranspilerConfig { public AT Type; public string TargetMember; public MethodInfo PatchMethod; public uint Occurrence; public uint StartIndex; public TranspilerConfig(AT type, string targetMember, MethodInfo patchMethod, uint occurrence, uint startIndex) { Type = type; TargetMember = targetMember; PatchMethod = patchMethod; Occurrence = occurrence; StartIndex = startIndex; } } }