using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using HarmonyLib; using NitroxClient.GameLogic; using NitroxClient.MonoBehaviours; using NitroxModel.Core; using NitroxModel.Helper; using NitroxPatcher.PatternMatching; using UnityEngine; namespace NitroxPatcher; internal static class TranspilerHelper { private static readonly MethodInfo serviceLocator = typeof(NitroxServiceLocator) .GetMethod(nameof(NitroxServiceLocator.LocateService), BindingFlags.Static | BindingFlags.Public, null, Array.Empty(), null); private static readonly MethodInfo DELTA_TIME_INSERTED_METHOD = Reflect.Method(() => GetDeltaTime()); private static readonly MethodInfo DELTA_TIME_MATCHING_FIELD = Reflect.Property(() => Time.deltaTime).GetGetMethod(); public static CodeInstruction LocateService() { return new CodeInstruction(OpCodes.Call, serviceLocator.MakeGenericMethod(typeof(T))); } private static IEnumerable GetMatchingLocalVariables(MethodBase method) { return method.GetMethodBody()?.LocalVariables.Where(v => v.LocalType == typeof(T)) ?? Array.Empty(); } /// /// Outputs an If (Multiplayer.Active) check /// /// Spot to jump to if Multiplayer.Active is false /// The ILGenerator /// public static IEnumerable IsMultiplayer(Label jmpLabel, ILGenerator generator) { yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Property(() => Multiplayer.Active).GetMethod); yield return new CodeInstruction(OpCodes.Brfalse, jmpLabel); // If false jump to the end of the code block } /// /// Outputs an If (!Multiplayer.Active) check /// /// Spot to jump to if Multiplayer.Active is true /// The ILGenerator /// public static IEnumerable IsNotMultiplayer(Label jmpLabel, ILGenerator generator) { yield return new CodeInstruction(OpCodes.Callvirt, Reflect.Property(() => Multiplayer.Active).GetMethod); yield return new CodeInstruction(OpCodes.Brtrue, jmpLabel); // If true jump to the end of the code block } /// /// Returns the one and only local variable of type . Throws if there is not exactly one local variable of that type. /// /// public static int GetLocalVariableIndex(this MethodBase method) { return GetMatchingLocalVariables(method).Single().LocalIndex; } /// /// Returns the index of the 'th local variable of type . /// /// public static int GetLocalVariableIndex(this MethodBase method, int i) { return GetMatchingLocalVariables(method).ElementAt(i).LocalIndex; } /// /// Returns the shortest possible Ldloc instruction for the given local variable index . /// private static CodeInstruction Ldloc(int i) { switch (i) { case 0: return new CodeInstruction(OpCodes.Ldloc_0); case 1: return new CodeInstruction(OpCodes.Ldloc_1); case 2: return new CodeInstruction(OpCodes.Ldloc_2); case 3: return new CodeInstruction(OpCodes.Ldloc_3); default: if (i <= 0xFF) { return new CodeInstruction(OpCodes.Ldloc_S, (byte)i); } else { return new CodeInstruction(OpCodes.Ldloc, (ushort)i); } } } /// /// Returns an instruction that loads the one and only local variable of type in onto the evaluation stack. /// /// The type to locate /// The method in which to locate the local variable public static CodeInstruction Ldloc(this MethodBase method) { return Ldloc(method.GetLocalVariableIndex()); } /// /// Loads the 'th occurence of a local variable of type in . /// /// The type to locate /// The method in which to locate the local variable /// Index of the local variable to load, in a list of only variables of type public static CodeInstruction Ldloc(this MethodBase method, int i) { if (method == null) { return new CodeInstruction(OpCodes.Nop); } return Ldloc(method.GetLocalVariableIndex(i)); } // ReSharper disable once UnusedMember.Global public static CodeMatcher LogInstructions(this CodeMatcher matcher) { StringBuilder sb = new(); sb.AppendLine(); sb.Append("IL-Instructions Valid: "); sb.AppendLine(matcher.IsValid.ToString()); sb.AppendLine(matcher.InstructionEnumeration().ToPrettyString()); Log.Info(sb.ToString()); return matcher; } /// /// Replaces first use of by . /// Moves to the said instruction. /// public static CodeMatcher ReplaceDeltaTime(this CodeMatcher codeMatcher) { return codeMatcher.MatchStartForward(new CodeMatch(OpCodes.Call, DELTA_TIME_MATCHING_FIELD)) .SetOperandAndAdvance(DELTA_TIME_INSERTED_METHOD); } /// /// Wrapper for dependency resolving and variable querying /// private static float GetDeltaTime() { return NitroxServiceLocator.Cache.Value.DeltaTime; } }