Skip to content

Commit

Permalink
Added common usage extension methods for IL pattern matching (#2049)
Browse files Browse the repository at this point in the history
  • Loading branch information
Measurity authored Jun 2, 2023
2 parents b0bdcd6 + 300628e commit 1163b5c
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 66 deletions.
11 changes: 11 additions & 0 deletions Nitrox.Test/Patcher/PatchTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ public static void TestPattern(MethodInfo targetMethod, InstructionsPattern patt
shouldHappen.Should().BeTrue();
}

/// <summary>
/// Clones the instructions so that the returned instructions are not the same reference.
/// </summary>
/// <remarks>
/// Useful for testing code differences before and after a Harmony transpiler.
/// </remarks>
public static List<CodeInstruction> Clone(this IEnumerable<CodeInstruction> instructions)
{
return new List<CodeInstruction>(instructions.Select(il => new CodeInstruction(il)));
}

private static ReadOnlyCollection<CodeInstruction> GetInstructionsFromIL(IEnumerable<KeyValuePair<OpCode, object>> il)
{
List<CodeInstruction> result = new List<CodeInstruction>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
using System.Linq;
using FluentAssertions;
using HarmonyLib;
Expand All @@ -16,7 +15,8 @@ public class DevConsole_Update_PatchTest
public void Sanity()
{
ReadOnlyCollection<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(TARGET_METHOD);
IEnumerable<CodeInstruction> 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");
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<CodeInstruction> originalIl, out IEnumerable<CodeInstruction> transformedIl);
originalIl.Count().Should().Be(transformedIl.Count());
ReadOnlyCollection<CodeInstruction> originalIl = PatchTestHelper.GetInstructionsFromMethod(TARGET_METHOD);
CodeInstruction[] transformedIl = Transpiler(TARGET_METHOD, originalIl.Clone()).ToArray();
originalIl.Count.Should().Be(transformedIl.Length);
originalIl.Should().NotBeEquivalentTo(transformedIl);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,11 @@ public class BreakableResource_SpawnResourceFromPrefab_Patch : NitroxPatch, IDyn

public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
{
static IEnumerable<CodeInstruction> 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)
Expand Down
12 changes: 1 addition & 11 deletions NitroxPatcher/Patches/Dynamic/DevConsole_Update_Patch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,7 @@ internal class DevConsole_Update_Patch : NitroxPatch, IDynamicPatch

public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> 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)
Expand Down
15 changes: 5 additions & 10 deletions NitroxPatcher/Patches/Dynamic/ItemsContainer_DestroyItem_Patch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,12 @@ public class ItemsContainer_DestroyItem_Patch : NitroxPatch, IDynamicPatch

public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
{
static IEnumerable<CodeInstruction> 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)
Expand Down
19 changes: 6 additions & 13 deletions NitroxPatcher/Patches/Dynamic/SpawnOnKill_OnKill_Patch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,20 @@ public class SpawnOnKill_OnKill_Patch : NitroxPatch, IDynamicPatch

public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions)
{
static IEnumerable<CodeInstruction> 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)
{
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<IPacketSender>().Send(new EntityDestroyed(destroyedEntity.Id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// 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.
/// </summary>
public class uSkyManager_SetVaryingMaterialProperties_Patch : NitroxPatch, IDynamicPatch
{
public static readonly MethodInfo TARGET_METHOD = Reflect.Method((uSkyManager t) => t.SetVaryingMaterialProperties(default));

/// <summary>
/// This pattern detects the <see cref="UnityEngine.Time.time"/> property in the following line
/// and replaces the property call target to <see cref="CurrentTime"/>:
/// This pattern detects the <see cref="UnityEngine.Time.time" /> property in the following line
/// and replaces the property call target to <see cref="CurrentTime" />:
/// <code>Quaternion q = Quaternion.AngleAxis(cloudsRotateSpeed * Time.time, Vector3.up);</code>
/// </summary>
public static readonly InstructionsPattern ModifyInstructionPattern = new()
{
Ldarg_0,
Ldfld,
{ Reflect.Property(() => UnityEngine.Time.time).GetMethod, "Modify" },
{ Reflect.Property(() => Time.time).GetMethod, "Modify" },
Mul
};

/// <summary>
/// Intermediate time property to simplify the dependency resolving for the transpiler.
/// Intermediate time property to simplify the dependency resolving for the transpiler.
/// </summary>
private static double CurrentTime => Resolve<TimeManager>().CurrentTime;

/// <summary>
/// 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
/// </summary>
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) =>
instructions.Transform(ModifyInstructionPattern, (label, instruction) =>
{
if (label.Equals("Modify"))
{
instruction.operand = Reflect.Property(() => CurrentTime).GetMethod;
}
});
public static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<CodeInstruction> instructions) => instructions
.ChangeAtMarker(ModifyInstructionPattern, "Modify", instruction => instruction.operand = Reflect.Property(() => CurrentTime).GetMethod);

public override void Patch(Harmony harmony)
{
Expand Down
32 changes: 32 additions & 0 deletions NitroxPatcher/PatternMatching/ILExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,36 @@ public static IEnumerable<CodeInstruction> Transform(this IEnumerable<CodeInstru
return null;
});
}

/// <summary>
/// Inserts the new instructions on every occurence of the marker, as defined by the pattern.
/// </summary>
/// <returns>Code with the additions.</returns>
public static IEnumerable<CodeInstruction> InsertAfterMarker(this IEnumerable<CodeInstruction> instructions, InstructionsPattern pattern, string marker, CodeInstruction[] newInstructions)
{
return pattern.ApplyTransform(instructions, (m, _) =>
{
if (m.Equals(marker, StringComparison.Ordinal))
{
return newInstructions;
}
return null;
});
}

/// <summary>
/// Calls the <paramref name="instructionChange" /> action on each instruction matching the given marker, as defined by the
/// pattern.
/// </summary>
public static IEnumerable<CodeInstruction> ChangeAtMarker(this IEnumerable<CodeInstruction> instructions, InstructionsPattern pattern, string marker, Action<CodeInstruction> instructionChange)
{
return pattern.ApplyTransform(instructions, (m, instruction) =>
{
if (m.Equals(marker, StringComparison.Ordinal))
{
instructionChange(instruction);
}
return null;
});
}
}

0 comments on commit 1163b5c

Please sign in to comment.