Skip to content

Commit

Permalink
Updated and fixed ReplaceMethod (#499)
Browse files Browse the repository at this point in the history
- The opcode of replaced method is based on the new method, rather than the one it replaces
- Added `extraInstructionsAfter` argument to insert instructions after the targeted code instruction
- The `extraInstructions` argument was renamed to `extraInstructionsBefore`
- Made the method replacement (and the argument for it) optional
  - The method can now be used to insert extra instructions before/after the targeted method without replacing it
- An exception will be thrown if the `IEnumerable<CodeInstruction>` is null
  - It feels like the best course of action, as not doing anything will cause an exception later on and returning anything (or nothing) could have an unexpected consequences
- An error will be displayed if the method was provided a null target method
- An error will be displayed if the method was not provided a replacement method or a function to insert extra instructions
- Updated the compat for "A RimWorld of Magic" to support the new method signature
  • Loading branch information
SokyranTheDragon authored Dec 3, 2024
1 parent 6eb6d18 commit 60f95c6
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 12 deletions.
40 changes: 33 additions & 7 deletions Source/PatchingUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -993,17 +993,27 @@ private static bool StopSecondHostCall()
/// <param name="from">Method or constructor to search for.</param>
/// <param name="to">Method or constructor to replace with.</param>
/// <param name="baseMethod">Method or constructor that is being patched, used for logging to provide information on which patched method had issues.</param>
/// <param name="extraInstructions">Extra instructions to insert before the method or constructor is called.</param>
/// <param name="extraInstructionsBefore">Extra instructions to insert before the method or constructor is called.</param>
/// <param name="extraInstructionsAfter">Extra instructions to insert after the method or constructor is called.</param>
/// <param name="expectedReplacements">The expected number of times the method should be replaced. Use -1 to to disable, or use -2 to expect unspecified amount (but more than 1 replacement).</param>
/// <param name="targetText">The text that should appear before replacing a method. A first occurence of the method after this text will be replaced.</param>
/// <param name="excludedText">The text that excludes the next method from being patched. Will prevent skip patching the next time the method was going to be patched.</param>
/// <returns>Modified enumeration of <see cref="T:HarmonyLib.CodeInstruction"/></returns>
public static IEnumerable<CodeInstruction> ReplaceMethod(this IEnumerable<CodeInstruction> instr, MethodBase from, MethodBase to, MethodBase baseMethod = null, Func<CodeInstruction, IEnumerable<CodeInstruction>> extraInstructions = null, int expectedReplacements = -1, string targetText = null, string excludedText = null)
public static IEnumerable<CodeInstruction> ReplaceMethod(this IEnumerable<CodeInstruction> instr, MethodBase from, MethodBase to = null, MethodBase baseMethod = null, Func<CodeInstruction, IEnumerable<CodeInstruction>> extraInstructionsBefore = null, Func<CodeInstruction, IEnumerable<CodeInstruction>> extraInstructionsAfter = null, int expectedReplacements = -1, string targetText = null, string excludedText = null)
{
if (instr == null)
throw new ArgumentNullException(nameof(instr));
if (from == null)
Log.Error($"Call to {nameof(ReplaceMethod)} is meaningless as the target method is null for method {MethodName()}");
// Check if all meaningful arguments are null and provide a proper warning if they are.
if (to == null && extraInstructionsBefore == null && extraInstructionsAfter == null)
Log.Error($"Call to {nameof(ReplaceMethod)} is meaningless as no useful arguments were provided for method {MethodName()}");

// Check for text only if expected text isn't null
var isCorrectText = targetText == null;
var skipNextCall = false;
var replacedCount = 0;
var insertInstructionsAfter = false;

foreach (var ci in instr)
{
Expand All @@ -1029,24 +1039,40 @@ public static IEnumerable<CodeInstruction> ReplaceMethod(this IEnumerable<CodeIn
}
else
{
// Replace method with our own
ci.opcode = from.IsConstructor ? OpCodes.Newobj : OpCodes.Call;
ci.operand = to;
if (to != null)
{
// Replace method with our own
ci.opcode = to.IsConstructor ? OpCodes.Newobj : OpCodes.Call;
ci.operand = to;
}

if (extraInstructions != null)
if (extraInstructionsBefore != null)
{
foreach (var extraInstr in extraInstructions(ci))
foreach (var extraInstr in extraInstructionsBefore(ci))
yield return extraInstr;
}

replacedCount++;
// Check for text only if expected text isn't null
isCorrectText = targetText == null;
// If extraInstructionsAfter isn't null,
// make sure they are actually inserted.
insertInstructionsAfter = true;
}
}
}

yield return ci;

if (insertInstructionsAfter && extraInstructionsAfter != null)
{
insertInstructionsAfter = false;

foreach (var extraInstr in extraInstructionsAfter(ci))
{
yield return extraInstr;
}
}
}

string MethodName()
Expand Down
10 changes: 5 additions & 5 deletions Source_Referenced/ARimWorldOfMagic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,7 @@ IEnumerable<CodeInstruction> ExtraInstructions(CodeInstruction _) =>
new CodeInstruction(OpCodes.Ldloc_0),
];

return instr.ReplaceMethod(target, replacement, baseMethod, ExtraInstructions, 1, "+");
return instr.ReplaceMethod(target, replacement, baseMethod, ExtraInstructions, null, 1, "+");
}

[MpCompatTranspiler(typeof(MagicCardUtility), nameof(MagicCardUtility.DrawLevelBar))]
Expand Down Expand Up @@ -859,7 +859,7 @@ private static IEnumerable<CodeInstruction> UniversalReplaceGlobalLevelUpPlusBut
// Shouldn't happen
else throw new Exception($"Trying to apply transpiler ({nameof(UniversalReplaceLevelUpPlusButton)}) for an unsupported type ({baseMethod.DeclaringType.FullDescription()}).");

return instr.ReplaceMethod(target, replacement, baseMethod, extraInstructions, expected, "+");
return instr.ReplaceMethod(target, replacement, baseMethod, extraInstructions, null, expected, "+");
}

#endregion
Expand Down Expand Up @@ -1123,9 +1123,9 @@ IEnumerable<CodeInstruction> ExtraInstructions(CodeInstruction _) =>
];

// Replace the "TM_Learn" button to learn a power
var replacedLearnButton = instr.ReplaceMethod(targetTextButton, textButtonReplacement, baseMethod, ExtraInstructions, 1, "TM_Learn", "TM_MCU_PointsToLearn");
var replacedLearnButton = instr.ReplaceMethod(targetTextButton, textButtonReplacement, baseMethod, ExtraInstructions, null, 1, "TM_Learn", "TM_MCU_PointsToLearn");
// Replace the image button to level-up a power
return replacedLearnButton.ReplaceMethod(targetImageButton, imageButtonReplacement, baseMethod, ExtraInstructions, 1);
return replacedLearnButton.ReplaceMethod(targetImageButton, imageButtonReplacement, baseMethod, ExtraInstructions, null, 1);
}

#endregion
Expand Down Expand Up @@ -1370,7 +1370,7 @@ IEnumerable<CodeInstruction> ExtraInstructions(CodeInstruction _) =>
];

// The "Apply" text isn't translated in the mod...
return instr.ReplaceMethod(target, replacement, baseMethod, ExtraInstructions, 1, "Apply");
return instr.ReplaceMethod(target, replacement, baseMethod, ExtraInstructions, null, 1, "Apply");
}

#endregion
Expand Down

0 comments on commit 60f95c6

Please sign in to comment.