diff --git a/Nitrox.Test/Patcher/PatchTestHelper.cs b/Nitrox.Test/Patcher/PatchTestHelper.cs
index df3f920999..2923eb8f3c 100644
--- a/Nitrox.Test/Patcher/PatchTestHelper.cs
+++ b/Nitrox.Test/Patcher/PatchTestHelper.cs
@@ -63,6 +63,17 @@ public static void TestPattern(MethodInfo targetMethod, InstructionsPattern patt
shouldHappen.Should().BeTrue();
}
+ ///
+ /// Clones the instructions so that the returned instructions are not the same reference.
+ ///
+ ///
+ /// Useful for testing code differences before and after a Harmony transpiler.
+ ///
+ public static List Clone(this IEnumerable instructions)
+ {
+ return new List(instructions.Select(il => new CodeInstruction(il)));
+ }
+
private static ReadOnlyCollection GetInstructionsFromIL(IEnumerable> il)
{
List result = new List();
diff --git a/Nitrox.Test/Patcher/Patches/Dynamic/DevConsole_Update_PatchTest.cs b/Nitrox.Test/Patcher/Patches/Dynamic/DevConsole_Update_PatchTest.cs
index d5fdc76825..03a1625aa1 100644
--- a/Nitrox.Test/Patcher/Patches/Dynamic/DevConsole_Update_PatchTest.cs
+++ b/Nitrox.Test/Patcher/Patches/Dynamic/DevConsole_Update_PatchTest.cs
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
+using System.Collections.ObjectModel;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
@@ -16,7 +15,8 @@ public class DevConsole_Update_PatchTest
public void Sanity()
{
ReadOnlyCollection originalIl = PatchTestHelper.GetInstructionsFromMethod(TARGET_METHOD);
- IEnumerable transformedIl = Transpiler(TARGET_METHOD, originalIl);
- originalIl.Count.Should().Be(transformedIl.Count());
+ CodeInstruction[] transformedIl = Transpiler(TARGET_METHOD, originalIl.Clone()).ToArray();
+ originalIl.Count.Should().Be(transformedIl.Length, "the modified code shouldn't have a difference in size");
+ transformedIl.Should().NotBeEquivalentTo(originalIl, "the patch should have changed the IL");
}
}
diff --git a/Nitrox.Test/Patcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_PatchTest.cs b/Nitrox.Test/Patcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_PatchTest.cs
index af45b03e38..f078f8f85e 100644
--- a/Nitrox.Test/Patcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_PatchTest.cs
+++ b/Nitrox.Test/Patcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_PatchTest.cs
@@ -1,9 +1,10 @@
-using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NitroxTest.Patcher;
+using static NitroxPatcher.Patches.Dynamic.uSkyManager_SetVaryingMaterialProperties_Patch;
namespace NitroxPatcher.Patches.Dynamic;
@@ -13,7 +14,9 @@ public class uSkyManager_SetVaryingMaterialProperties_PatchTest
[TestMethod]
public void Sanity()
{
- PatchTestHelper.TestPattern(uSkyManager_SetVaryingMaterialProperties_Patch.TARGET_METHOD, uSkyManager_SetVaryingMaterialProperties_Patch.ModifyInstructionPattern, out IEnumerable originalIl, out IEnumerable transformedIl);
- originalIl.Count().Should().Be(transformedIl.Count());
+ ReadOnlyCollection originalIl = PatchTestHelper.GetInstructionsFromMethod(TARGET_METHOD);
+ CodeInstruction[] transformedIl = Transpiler(TARGET_METHOD, originalIl.Clone()).ToArray();
+ originalIl.Count.Should().Be(transformedIl.Length);
+ originalIl.Should().NotBeEquivalentTo(transformedIl);
}
}
diff --git a/NitroxPatcher/Patches/Dynamic/BreakableResource_SpawnResourceFromPrefab_Patch.cs b/NitroxPatcher/Patches/Dynamic/BreakableResource_SpawnResourceFromPrefab_Patch.cs
index 9287614adb..8ae276824f 100644
--- a/NitroxPatcher/Patches/Dynamic/BreakableResource_SpawnResourceFromPrefab_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/BreakableResource_SpawnResourceFromPrefab_Patch.cs
@@ -26,17 +26,11 @@ public class BreakableResource_SpawnResourceFromPrefab_Patch : NitroxPatch, IDyn
public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions)
{
- static IEnumerable InsertCallback(string label, CodeInstruction _)
+ return instructions.InsertAfterMarker(SpawnResFromPrefPattern, "DropItemInstance", new CodeInstruction[]
{
- switch(label)
- {
- case "DropItemInstance":
- yield return new(Ldloc_1);
- yield return new(Call, Reflect.Method(() => Callback(default)));
- break;
- }
- }
- return instructions.Transform(SpawnResFromPrefPattern, InsertCallback);
+ new(Ldloc_1),
+ new(Call, Reflect.Method(() => Callback(default)))
+ });
}
private static void Callback(GameObject __instance)
diff --git a/NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs b/NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs
index 2949c5338b..2231f8b65f 100644
--- a/NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs
@@ -29,17 +29,7 @@ internal class DevConsole_Update_Patch : NitroxPatch, IDynamicPatch
public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions)
{
- static void PreventDevConsoleTrue(string label, CodeInstruction instruction)
- {
- switch (label)
- {
- case "ConsoleEnableFlag":
- instruction.opcode = Ldc_I4_0;
- break;
- }
- }
-
- return instructions.Transform(devConsoleSetStateTruePattern, PreventDevConsoleTrue);
+ return instructions.ChangeAtMarker(devConsoleSetStateTruePattern, "ConsoleEnableFlag", i => i.opcode = Ldc_I4_0);
}
public override void Patch(Harmony harmony)
diff --git a/NitroxPatcher/Patches/Dynamic/ItemsContainer_DestroyItem_Patch.cs b/NitroxPatcher/Patches/Dynamic/ItemsContainer_DestroyItem_Patch.cs
index 673f826001..b1b9bddb13 100644
--- a/NitroxPatcher/Patches/Dynamic/ItemsContainer_DestroyItem_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/ItemsContainer_DestroyItem_Patch.cs
@@ -30,17 +30,12 @@ public class ItemsContainer_DestroyItem_Patch : NitroxPatch, IDynamicPatch
public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions)
{
- static IEnumerable InsertNotifyServerCall(string label, CodeInstruction instruction)
+ // After the call to RemoveItem (and storing the return value) we want to call our callback method
+ return instructions.InsertAfterMarker(removeItemPattern, "NotifyServer", new CodeInstruction[]
{
- // After the call to RemoveItem (and storing the return value) we want to call our callback method
- if (label.Equals("NotifyServer"))
- {
- yield return new(Ldloc_0);
- yield return new(Call, Reflect.Method(() => Callback(default)));
- }
- }
-
- return instructions.Transform(removeItemPattern, InsertNotifyServerCall);
+ new(Ldloc_0),
+ new(Call, Reflect.Method(() => Callback(default)))
+ });
}
private static void Callback(Pickupable pickupable)
diff --git a/NitroxPatcher/Patches/Dynamic/SpawnOnKill_OnKill_Patch.cs b/NitroxPatcher/Patches/Dynamic/SpawnOnKill_OnKill_Patch.cs
index ac2c391912..276841a181 100644
--- a/NitroxPatcher/Patches/Dynamic/SpawnOnKill_OnKill_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/SpawnOnKill_OnKill_Patch.cs
@@ -29,19 +29,12 @@ public class SpawnOnKill_OnKill_Patch : NitroxPatch, IDynamicPatch
public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions)
{
- static IEnumerable InsertCallbackCall(string label, CodeInstruction _)
+ return instructions.InsertAfterMarker(spawnInstanceOnKillPattern, "DropOnKillInstance", new CodeInstruction[]
{
- switch (label)
- {
- case "DropOnKillInstance":
- yield return new(Ldarg_0);
- yield return new(Ldloc_0);
- yield return new(Call, Reflect.Method(() => Callback(default, default)));
- break;
- }
- }
-
- return instructions.Transform(spawnInstanceOnKillPattern, InsertCallbackCall);
+ new(Ldarg_0),
+ new(Ldloc_0),
+ new(Call, Reflect.Method(() => Callback(default, default)))
+ });
}
private static void Callback(SpawnOnKill spawnOnKill, GameObject spawningItem)
@@ -49,7 +42,7 @@ private static void Callback(SpawnOnKill spawnOnKill, GameObject spawningItem)
if (!NitroxEntity.TryGetEntityFrom(spawnOnKill.gameObject, out NitroxEntity destroyedEntity))
{
Log.Warn($"[{nameof(SpawnOnKill_OnKill_Patch)}] Could not find {nameof(NitroxEntity)} for breakable entity {spawnOnKill.gameObject.GetFullHierarchyPath()}.");
- }
+ }
else
{
Resolve().Send(new EntityDestroyed(destroyedEntity.Id));
diff --git a/NitroxPatcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_Patch.cs b/NitroxPatcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_Patch.cs
index dbd9f55d3b..5bd34829f7 100644
--- a/NitroxPatcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_Patch.cs
+++ b/NitroxPatcher/Patches/Dynamic/uSkyManager_SetVaryingMaterialProperties_Patch.cs
@@ -4,47 +4,42 @@
using NitroxClient.GameLogic;
using NitroxModel.Helper;
using NitroxPatcher.PatternMatching;
+using UnityEngine;
using static System.Reflection.Emit.OpCodes;
namespace NitroxPatcher.Patches.Dynamic;
///
-/// Prevent uSkyManager from "freezing" the clouds when FreezeTime is active (game paused).
-/// Also sets skybox's rotation depending on the real server time.
+/// Prevent uSkyManager from "freezing" the clouds when FreezeTime is active (game paused).
+/// Also sets skybox's rotation depending on the real server time.
///
public class uSkyManager_SetVaryingMaterialProperties_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((uSkyManager t) => t.SetVaryingMaterialProperties(default));
///
- /// This pattern detects the property in the following line
- /// and replaces the property call target to :
+ /// This pattern detects the property in the following line
+ /// and replaces the property call target to :
/// Quaternion q = Quaternion.AngleAxis(cloudsRotateSpeed * Time.time, Vector3.up);
///
public static readonly InstructionsPattern ModifyInstructionPattern = new()
{
Ldarg_0,
Ldfld,
- { Reflect.Property(() => UnityEngine.Time.time).GetMethod, "Modify" },
+ { Reflect.Property(() => Time.time).GetMethod, "Modify" },
Mul
};
///
- /// Intermediate time property to simplify the dependency resolving for the transpiler.
+ /// Intermediate time property to simplify the dependency resolving for the transpiler.
///
private static double CurrentTime => Resolve().CurrentTime;
///
- /// Replaces Time.time call to Time.realtimeSinceStartup so that it doesn't take Time.timeScale into account
+ /// Replaces Time.time call to Time.realtimeSinceStartup so that it doesn't take Time.timeScale into account
///
- public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions) =>
- instructions.Transform(ModifyInstructionPattern, (label, instruction) =>
- {
- if (label.Equals("Modify"))
- {
- instruction.operand = Reflect.Property(() => CurrentTime).GetMethod;
- }
- });
+ public static IEnumerable Transpiler(MethodBase original, IEnumerable instructions) => instructions
+ .ChangeAtMarker(ModifyInstructionPattern, "Modify", instruction => instruction.operand = Reflect.Property(() => CurrentTime).GetMethod);
public override void Patch(Harmony harmony)
{
diff --git a/NitroxPatcher/PatternMatching/ILExtensions.cs b/NitroxPatcher/PatternMatching/ILExtensions.cs
index 5901152c86..cedda95fed 100644
--- a/NitroxPatcher/PatternMatching/ILExtensions.cs
+++ b/NitroxPatcher/PatternMatching/ILExtensions.cs
@@ -32,4 +32,36 @@ public static IEnumerable Transform(this IEnumerable
+ /// Inserts the new instructions on every occurence of the marker, as defined by the pattern.
+ ///
+ /// Code with the additions.
+ public static IEnumerable InsertAfterMarker(this IEnumerable instructions, InstructionsPattern pattern, string marker, CodeInstruction[] newInstructions)
+ {
+ return pattern.ApplyTransform(instructions, (m, _) =>
+ {
+ if (m.Equals(marker, StringComparison.Ordinal))
+ {
+ return newInstructions;
+ }
+ return null;
+ });
+ }
+
+ ///
+ /// Calls the action on each instruction matching the given marker, as defined by the
+ /// pattern.
+ ///
+ public static IEnumerable ChangeAtMarker(this IEnumerable instructions, InstructionsPattern pattern, string marker, Action instructionChange)
+ {
+ return pattern.ApplyTransform(instructions, (m, instruction) =>
+ {
+ if (m.Equals(marker, StringComparison.Ordinal))
+ {
+ instructionChange(instruction);
+ }
+ return null;
+ });
+ }
}