public void CanGetInstructionsWithNoILGenerator() { var method = typeof(Class12).GetMethod(nameof(Class12.FizzBuzz)); var instrsNoGen = MethodBodyReader.GetInstructions(generator: null, method); var dynamicMethod = DynamicTools.CreateDynamicMethod(method, "_Patch"); var instrsHasGen = MethodBodyReader.GetInstructions(dynamicMethod.GetILGenerator(), method); Assert.AreEqual(instrsNoGen.Count, instrsHasGen.Count); for (var i = 0; i < instrsNoGen.Count; i++) { var instrNoGen = instrsNoGen[i]; var instrHasGen = instrsHasGen[i]; Assert.AreEqual(instrNoGen.offset, instrHasGen.offset, "offset @ {0} ({1})", i, instrNoGen); Assert.AreEqual(instrNoGen.opcode, instrHasGen.opcode, "opcode @ {0} ({1})", i, instrNoGen); AssertAreEqual(instrNoGen.operand, instrHasGen.operand, "operand", i, instrNoGen); CollectionAssert.AreEqual(instrNoGen.labels, instrHasGen.labels, "labels @ {0}", i); CollectionAssert.AreEqual(instrNoGen.blocks, instrHasGen.blocks, "blocks @ {0}", i); AssertAreEqual(instrNoGen.operand, instrHasGen.operand, "argument", i, instrNoGen); // The only difference between w/o gen and w/ gen is this: var operandType = instrNoGen.opcode.OperandType; if ((operandType == OperandType.ShortInlineVar || operandType == OperandType.InlineVar) && !(instrNoGen.argument is null)) { Assert.AreEqual(typeof(LocalVariableInfo), instrNoGen.argument.GetType(), "w/o generator argument type @ {0} ({1})", i, instrNoGen); Assert.AreEqual(typeof(LocalBuilder), instrHasGen.argument.GetType(), "w/ generator argument type @ {0} ({1})", i, instrNoGen); } } }
/// <summary> /// Checks if the given method has a IL instruction that SETS a persistent field /// </summary> private static bool MethodSetsPersistentField(MethodBase partModuleMethod) { var method = DynamicTools.CreateDynamicMethod(partModuleMethod, "read"); var instructions = MethodBodyReader.GetInstructions(method.GetILGenerator(), partModuleMethod); //OpCodes.Stfld is the opcode for SETTING the value of a field foreach (var instruction in instructions.Where(i => i.opcode == OpCodes.Stfld)) { if (!(instruction.operand is FieldInfo operand)) { continue; } if (FieldIsIgnored(operand)) { continue; } var attributes = operand.GetCustomAttributes(typeof(KSPField), false).Cast <KSPField>().ToArray(); if (attributes.Any() && attributes.First().isPersistant) { return(true); } } return(false); }
static HaulToBlueprintUnderRock() { //AccessTools.Inner HarmonyMethod transpiler = new HarmonyMethod(typeof(DeliverUnderRock), nameof(DeliverUnderRock.Transpiler)); HarmonyInstance harmony = HarmonyInstance.Create("Uuugggg.rimworld.Replace_Stuff.main"); MethodInfo CanConstructInfo = AccessTools.Method(typeof(GenConstruct), "CanConstruct"); //Find the compiler-created method in Toils_Haul that calls CanConstruct List <Type> nestedTypes = new List <Type>(typeof(Toils_Haul).GetNestedTypes(BindingFlags.NonPublic)); while (!nestedTypes.NullOrEmpty()) { Type type = nestedTypes.Pop(); nestedTypes.AddRange(type.GetNestedTypes(BindingFlags.NonPublic)); foreach (MethodInfo method in type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)) { if (method.DeclaringType != type) { continue; } DynamicMethod dm = DynamicTools.CreateDynamicMethod(method, "-unused"); if (Harmony.ILCopying.MethodBodyReader.GetInstructions(dm.GetILGenerator(), method). Any(ilcode => ilcode.operand == CanConstructInfo)) { harmony.Patch(method, null, null, transpiler); } } } }
//WorkGiver_Grower has one PotentialWorkCellsGlobal for both subclasses //public override IEnumerable<IntVec3> PotentialWorkCellsGlobal(Pawn pawn)Thing t, HashSet<Thing> nearbyNeeders, IConstructible constructible, Pawn pawn) static DoNotHarvest_Building() { HarmonyMethod transpiler = new HarmonyMethod(typeof(DoNotHarvest_Building), nameof(DoNotHarvest_Building.Transpiler)); HarmonyInstance harmony = HarmonyInstance.Create("Uuugggg.rimworld.TD_Enhancement_Pack.main"); MethodInfo IsForbiddenInfo = AccessTools.Method(typeof(ForbidUtility), "IsForbidden", new Type[] { typeof(Thing), typeof(Pawn) }); Func <MethodInfo, bool> check = delegate(MethodInfo method) { DynamicMethod dm = DynamicTools.CreateDynamicMethod(method, "-unused"); return(Harmony.ILCopying.MethodBodyReader.GetInstructions(dm.GetILGenerator(), method). Any(ilcode => ilcode.operand == IsForbiddenInfo)); }; harmony.PatchGeneratedMethod(typeof(WorkGiver_Grower), check, transpiler: transpiler); }
static HaulToBlueprintUnderRock() { HarmonyMethod transpiler = new HarmonyMethod(typeof(DeliverUnderRock), nameof(DeliverUnderRock.Transpiler)); HarmonyInstance harmony = HarmonyInstance.Create("Uuugggg.rimworld.Replace_Stuff.main"); MethodInfo CanConstructInfo = AccessTools.Method(typeof(GenConstruct), "CanConstruct"); Predicate <MethodInfo> check = delegate(MethodInfo method) { DynamicMethod dm = DynamicTools.CreateDynamicMethod(method, "-unused"); return(Harmony.ILCopying.MethodBodyReader.GetInstructions(dm.GetILGenerator(), method). Any(ilcode => ilcode.operand == CanConstructInfo)); }; harmony.PatchGeneratedMethod(typeof(Toils_Haul), check, transpiler: transpiler); }
static PriorityCareJobFail() { HarmonyMethod transpiler = new HarmonyMethod(typeof(PriorityCareJobFail), nameof(Transpiler)); HarmonyInstance harmony = HarmonyInstance.Create("uuugggg.rimworld.SmartMedicine.main"); MethodInfo AllowsMedicineInfo = AccessTools.Method(typeof(MedicalCareUtility), "AllowsMedicine"); Predicate <MethodInfo> check = delegate(MethodInfo method) { DynamicMethod dm = DynamicTools.CreateDynamicMethod(method, "-unused"); return(Harmony.ILCopying.MethodBodyReader.GetInstructions(dm.GetILGenerator(), method) .Any(ilcode => ilcode.operand == AllowsMedicineInfo)); }; harmony.PatchGeneratedMethod(typeof(JobDriver_TendPatient), check, transpiler: transpiler); }
/// <summary> /// Checks if the given method has a IL instruction that SETS (therefore, it changes the value) a customized field /// </summary> private static IEnumerable <FieldInfo> GetCustomizedFieldsChangedByMethod(MethodBase partModuleMethod, ModuleDefinition definition) { var listOfFields = new HashSet <FieldInfo>(); var method = DynamicTools.CreateDynamicMethod(partModuleMethod, "read"); var instructions = MethodBodyReader.GetInstructions(method.GetILGenerator(), partModuleMethod); //OpCodes.Stfld is the opcode for SETTING the value of a field foreach (var instruction in instructions.Where(i => i.opcode == OpCodes.Stfld)) { if (!(instruction.operand is FieldInfo operand)) { continue; } if (definition.Fields.Any(f => f.FieldName == operand.Name)) { listOfFields.Add(operand); } } return(listOfFields); }
public static void DoHotSwap() { foreach (var kv in AssemblyFiles) { var asm = kv.Key; var module = asm.GetModules()[0]; using (var dnModule = ModuleDefMD.Load(kv.Value.FullName)) { foreach (var dnType in dnModule.GetTypes()) { if (!dnType.HasCustomAttributes || !dnType.CustomAttributes.Select(a => a.AttributeType.Name).Any(n => n == "HotSwappable" || n == "HotSwappableAttribute") ) { continue; } var type = Type.GetType(dnType.AssemblyQualifiedName); var flags = BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; foreach (var method in type.GetMethods(flags)) { if (method.GetMethodBody() == null) { continue; } byte[] code = method.GetMethodBody().GetILAsByteArray(); var dnMethod = dnType.Methods.FirstOrDefault(m => MethodsSame(method, m)); var methodBody = dnMethod.Body; byte[] newCode = SerializeInstructions(methodBody); if (code.SequenceEqual(newCode)) { continue; } Log.Message("Patching " + method.FullDescription()); var replacement = DynamicTools.CreateDynamicMethod(method, $"_HotSwap{count++}"); var ilGen = replacement.GetILGenerator(); foreach (var local in methodBody.Variables) { var localType = Type.GetType(local.Type.AssemblyQualifiedName); //Log.Message($"local {local.Type.AssemblyQualifiedName} / {localType}"); ilGen.DeclareLocal(localType); } int pos = 0; foreach (var inst in methodBody.Instructions) { switch (inst.OpCode.OperandType) { case dnlib.DotNet.Emit.OperandType.InlineString: case dnlib.DotNet.Emit.OperandType.InlineType: case dnlib.DotNet.Emit.OperandType.InlineMethod: case dnlib.DotNet.Emit.OperandType.InlineField: case dnlib.DotNet.Emit.OperandType.InlineSig: case dnlib.DotNet.Emit.OperandType.InlineTok: pos += inst.OpCode.Size; object refe = TranslateRef(module, inst.Operand); if (refe == null) { Log.Message($"Null reference {inst.Operand} {inst.Operand.GetType()}"); } int token = replacement.AddRef(refe); newCode[pos++] = (byte)(token & 255); newCode[pos++] = (byte)(token >> 8 & 255); newCode[pos++] = (byte)(token >> 16 & 255); newCode[pos++] = (byte)(token >> 24 & 255); break; default: pos += inst.GetSize(); break; } } ilGen.code = newCode; ilGen.code_len = newCode.Length; ilGen.max_stack = methodBody.MaxStack; var exhandlers = methodBody.ExceptionHandlers.Distinct(new ExceptionHandlerComparer()).ToArray(); var ex_handlers = ilGen.ex_handlers = new ILExceptionInfo[exhandlers.Length]; for (int i = 0; i < exhandlers.Length; i++) { var ex = exhandlers[i]; int start = (int)ex.TryStart.Offset; int end = (int)ex.TryEnd.Offset; int len = end - start; var handlers = methodBody.ExceptionHandlers.Where(h => h.TryStart.Offset == start).ToArray(); ex_handlers[i].start = start; ex_handlers[i].len = len; ex_handlers[i].handlers = new ILExceptionBlock[handlers.Length]; for (int j = 0; j < handlers.Length; j++) { var exx = handlers[j]; int handlerStart = (int)exx.HandlerStart.Offset; int handlerEnd = (int)exx.HandlerEnd.Offset; int handlerLen = handlerEnd - handlerStart; Type catchType = null; int filterOffset = 0; if (exx.CatchType != null) { catchType = module.ResolveType(exx.CatchType.MDToken.ToInt32()); } else if (exx.FilterStart != null) { filterOffset = (int)exx.FilterStart.Offset; } var arr = ilGen.ex_handlers[i].handlers; arr[j].type = (int)ex.HandlerType; arr[j].start = handlerStart; arr[j].len = handlerLen; arr[j].extype = catchType; arr[j].filter_offset = filterOffset; } } Log.Message("Preparing method"); DynamicTools.PrepareDynamicMethod(replacement); Log.Message("Detouring"); dynMethods[method] = replacement; Memory.DetourMethod(method, replacement); Log.Message("Patch done"); } } } } }
/// <summary> /// Compiles a method into IL instructions. /// </summary> /// <param name="method">Method to compile.</param> /// <returns> /// List of ILInstructions. /// </returns> /// <remarks> /// Utilizes harmony library. /// </remarks> public static List <CodeInstruction> MethodToILInstructions(MethodBase method) { DynamicMethod dynamicMethod = DynamicTools.CreateDynamicMethod(method, "_ILParser"); if (dynamicMethod == null) { return(null); } // Get il generato ILGenerator il = dynamicMethod.GetILGenerator(); LocalBuilder[] existingVariables = DynamicTools.DeclareLocalVariables(method, il); Dictionary <string, LocalBuilder> privateVars = new Dictionary <string, LocalBuilder>(); MethodBodyReader reader = new MethodBodyReader(method, il); reader.DeclareVariables(existingVariables); reader.ReadInstructions(); List <ILInstruction> ilInstructions = (List <ILInstruction>)FIilInstructions.GetValue(reader); // Defines function start label il.DefineLabel(); // Define labels foreach (ILInstruction ilInstruction in ilInstructions) { switch (ilInstruction.opcode.OperandType) { case OperandType.InlineSwitch: { ILInstruction[] array = ilInstruction.operand as ILInstruction[]; if (array != null) { List <Label> labels = new List <Label>(); ILInstruction[] array2 = array; foreach (ILInstruction iLInstruction2 in array2) { Label item = il.DefineLabel(); iLInstruction2.labels.Add(item); labels.Add(item); } ilInstruction.argument = labels.ToArray(); } break; } case OperandType.InlineBrTarget: case OperandType.ShortInlineBrTarget: { ILInstruction iLInstruction = ilInstruction.operand as ILInstruction; if (iLInstruction != null) { Label label2 = il.DefineLabel(); iLInstruction.labels.Add(label2); ilInstruction.argument = label2; } break; } } } // Transpile code CodeTranspiler codeTranspiler = new CodeTranspiler(ilInstructions); List <CodeInstruction> result = codeTranspiler.GetResult(il, method); // Replace debug commands with normal foreach (CodeInstruction codeInstruction in result) { OpCode opCode = codeInstruction.opcode; codeInstruction.opcode = ReplaceShortJumps(opCode); } return(result); }