From 89b02ba4447b6bae0f019a9599c3b51cd2fae72f Mon Sep 17 00:00:00 2001 From: Measurity Date: Sat, 3 Jun 2023 00:00:08 +0200 Subject: [PATCH 1/4] Added common usage extension methods for IL pattern matching --- ...eResource_SpawnResourceFromPrefab_Patch.cs | 14 +++-------- .../Dynamic/DevConsole_Update_Patch.cs | 12 +-------- .../ItemsContainer_DestroyItem_Patch.cs | 15 ++++------- .../Dynamic/SpawnOnKill_OnKill_Patch.cs | 19 +++++--------- ...ager_SetVaryingMaterialProperties_Patch.cs | 25 ++++++++----------- NitroxPatcher/PatternMatching/ILExtensions.cs | 24 ++++++++++++++++++ 6 files changed, 50 insertions(+), 59 deletions(-) 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..079a999fd8 100644 --- a/NitroxPatcher/PatternMatching/ILExtensions.cs +++ b/NitroxPatcher/PatternMatching/ILExtensions.cs @@ -32,4 +32,28 @@ public static IEnumerable Transform(this 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; + }); + } + + 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; + }); + } } From 2bb1f98e1b428f1fa732fc4a94d743aae93c480d Mon Sep 17 00:00:00 2001 From: Measurity Date: Sat, 3 Jun 2023 00:44:21 +0200 Subject: [PATCH 2/4] Added comments to new IL pattern extension methods --- NitroxPatcher/PatternMatching/ILExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/NitroxPatcher/PatternMatching/ILExtensions.cs b/NitroxPatcher/PatternMatching/ILExtensions.cs index 079a999fd8..cedda95fed 100644 --- a/NitroxPatcher/PatternMatching/ILExtensions.cs +++ b/NitroxPatcher/PatternMatching/ILExtensions.cs @@ -33,6 +33,10 @@ 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, _) => @@ -45,6 +49,10 @@ public static IEnumerable InsertAfterMarker(this IEnumerable + /// 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) => From d0f7b08e2419a0912f0e6a5bcb6c2cc58d421f7e Mon Sep 17 00:00:00 2001 From: Measurity Date: Sat, 3 Jun 2023 00:44:52 +0200 Subject: [PATCH 3/4] Changed DevConsole_Update_PatchTest to also test for IL opcode/operand changes --- Nitrox.Test/Patcher/PatchTestHelper.cs | 11 +++++++++++ .../Patches/Dynamic/DevConsole_Update_PatchTest.cs | 8 ++++---- 2 files changed, 15 insertions(+), 4 deletions(-) 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"); } } From 300628e9c63b78e586fb16a39d165d4d6b69eac5 Mon Sep 17 00:00:00 2001 From: Measurity Date: Sat, 3 Jun 2023 01:07:41 +0200 Subject: [PATCH 4/4] Improved uSkyManager_SetVaryingMaterialProperties_PatchTest Ran the transpiler to confirm the IL has changed afterwards. --- ...uSkyManager_SetVaryingMaterialProperties_PatchTest.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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); } }