/// <summary>Changes the spouse.Contains check to use spouse.Equals instead.</summary> public static IEnumerable <CIL> spouseContainsEquals_Transpiler(IEnumerable <CIL> instructions, MethodBase original) { try { var codes = new List <CodeInstruction>(instructions); int patchCount = 0; for (int i = 0; i < codes.Count - 4; i++) { // This is the snippet of code we want to find and change: // OLD Game1.player.spouse.Contains(this.Name) or who.spouse.Contains(this.Name) (who is Farmer) // NEW Game1.player.spouse.Equals(this.Name) or who.spouse.Equals(this.Name) // It can be done with a single opcode call change from string.Contains to string.Equals if (//callvirt instance string StardewValley.Farmer::get_spouse() codes[i].opcode == OpCodes.Callvirt && (MethodInfo)codes[i].operand == typeof(Farmer).GetProperty("spouse").GetGetMethod() && //ldarg.0 codes[i + 1].opcode == OpCodes.Ldarg_0 && //call instance string StardewValley.Character::get_Name() codes[i + 2].opcode == OpCodes.Call && (MethodInfo)codes[i + 2].operand == typeof(Character).GetProperty("Name").GetGetMethod() && //callvirt instance bool[mscorlib] System.String::Contains(string) codes[i + 3].opcode == OpCodes.Callvirt && (MethodInfo)codes[i + 3].operand == typeof(string).GetMethod("Contains", new Type[] { typeof(string) })) { // Insert the new replacement instruction codes[i + 3] = new CIL(OpCodes.Callvirt, typeof(string).GetMethod("Equals", new Type[] { typeof(string) })); patchCount += 1; } } // Log results of the patch attempt if (patchCount == 0) { ModMonitor.LogOnce($"Couldn't find a code location to apply {nameof(spouseContainsEquals_Transpiler)} patch to {original.DeclaringType.Name}.{original.Name}\n" + $"This is probably fine. Maybe a game update or another harmony mod already patched it?", LogLevel.Info); } else //patched at least once { //do stuff ModMonitor.LogOnce($"Applied bugfix patch to {original.DeclaringType.Name}.{original.Name}: {nameof(spouseContainsEquals_Transpiler)}", LogLevel.Trace); if (patchCount > 1) { ModMonitor.LogOnce($"Found an unusual number of patch locations for {nameof(spouseContainsEquals_Transpiler)} in {original.DeclaringType.Name}.{original.Name}\n" + $"This might cause unexpected behaviour. Please share your SMAPI log with the creator of this mod.", LogLevel.Warn); } } return(codes.AsEnumerable()); } catch (Exception ex) { ModMonitor.Log($"Failed in {nameof(spouseContainsEquals_Transpiler)}:\n{ex}", LogLevel.Error); return(instructions); // use original code } }
/// <summary>Adds a hook that calls FixedHUDMessage_Hook inside the game's addItemToInventoryBool method.</summary> public static IEnumerable<CIL> addItemToInventoryBool_Transpiler(IEnumerable<CIL> instructions) { try { var codes = new List<CodeInstruction>(instructions); for (int i = 0; i < codes.Count - 9; i++) { // This is the line of code we want to find and change: // Game1.addHUDMessage(new HUDMessage(displayName, Math.Max(1, item.Stack), true, color, item)); // Game1.addHUDMessage(new HUDMessage(displayName, 1, true, color, item)); // We can most easily do this by replacing several instructions with nop if (//ldloc.3 codes[i].opcode == OpCodes.Ldloc_3 && //ldc.i4.1 // Replace with nop codes[i + 1].opcode == OpCodes.Ldc_I4_1 && //ldarg.1 // Replace with nop codes[i + 2].opcode == OpCodes.Ldarg_1 && //callvirt instance int32 StardewValley.Item::get_Stack() // Replace with nop codes[i + 3].opcode == OpCodes.Callvirt && (MethodInfo)codes[i + 3].operand == typeof(Item).GetMethod("get_Stack") && //call int32 [mscorlib]System.Math::Max(int32, int32) // Replace with Ldc_I4_1 codes[i + 4].opcode == OpCodes.Call && (MethodInfo)codes[i + 4].operand == typeof(Math).GetMethod("Max", types: new Type[] { typeof(int), typeof(int) }) && //ldc.i4.1 codes[i + 5].opcode == OpCodes.Ldc_I4_1 && //ldloc.2 codes[i + 6].opcode == OpCodes.Ldloc_2 && //ldarg.1 codes[i + 7].opcode == OpCodes.Ldarg_1 && //newobj instance void StardewValley.HUDMessage::.ctor(string, int32, bool, valuetype [Microsoft.Xna.Framework]Microsoft.Xna.Framework.Color, class StardewValley.Item) codes[i + 8].opcode == OpCodes.Newobj && (ConstructorInfo)codes[i + 8].operand == typeof(HUDMessage).GetConstructor( types: new Type[] { typeof(string), typeof(int), typeof(bool), typeof(Color), typeof(Item) }) && //call void StardewValley.Game1::addHUDMessage(class StardewValley.HUDMessage) codes[i + 9].opcode == OpCodes.Call && (MethodInfo)codes[i + 9].operand == typeof(Game1).GetMethod("addHUDMessage")) { // Compose the new instructions to use as replacements codes[i + 8] = new CIL(OpCodes.Call, Instance.Helper.Reflection.GetMethod( typeof(FarmerPatch), nameof(FixedHUDMessage_Hook)).MethodInfo); // Call my function that returns a fixed HUDMessage ModMonitor.LogOnce($"Added hook to Farmer.addItemToInventoryBool method: {nameof(addItemToInventoryBool_Transpiler)}", LogLevel.Trace); } } return codes.AsEnumerable(); } catch (Exception ex) { ModMonitor.Log($"Failed in {nameof(addItemToInventoryBool_Transpiler)}:\n{ex}", LogLevel.Error); return instructions; // use original code } }
public static void PrintInstruction(Harmony.CodeInstruction instr) { if (instr.labels.Count > 0) { var labels = instr.labels.ToArray(); StringBuilder builder = new StringBuilder(); builder.Append($"LABELS : : : > {labels[0].GetHashCode()}"); int index = 1; while (index < instr.labels.Count) { builder.Append($", {labels[index].GetHashCode()}"); index++; } Debug(builder.ToString()); } Debug($"INSTR : {instr.opcode,-10}\t : " + ((instr.operand != null && instr.operand.GetType() == typeof(Label)) ? $": : > {instr.operand.GetHashCode(),-100}" : $"{instr.operand,-100}")); }
public CodeInstruction(CodeInstruction instruction) { opcode = instruction.opcode; operand = instruction.operand; labels = instruction.labels.ToArray().ToList(); }
public CodeMatch(CodeInstruction instruction, string name = null) : this(instruction.opcode, instruction.operand, name) { }
public CodeMatcher SetInstructionAndAdvance(CodeInstruction instruction) { SetInstruction(instruction); Pos++; return(this); }
// edit operation ------------------------------------------------------- public CodeMatcher SetInstruction(CodeInstruction instruction) { codes[Pos] = instruction; return(this); }
public virtual List <CodeInstruction> Process(CodeInstruction instruction) { return(null); }