Created
August 20, 2020 04:56
-
-
Save egocarib/d4ea351536a44e164625fefeae415fe5 to your computer and use it in GitHub Desktop.
Example Qud Transpiler
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Linq; | |
| using System.Reflection; | |
| using System.Reflection.Emit; | |
| using System.Collections.Generic; | |
| using HarmonyLib; | |
| using XRL.Core; | |
| using XRL.World.Parts; | |
| using UnityEngine; | |
| namespace Egocarib.Junkbunker | |
| { | |
| public static class Patch_Helpers | |
| { | |
| // Checks whether two CodeInstructions are equivalent. Supports only certain OpCodes. | |
| public static bool InstructionsAreEqual(CodeInstruction i1, CodeInstruction i2) | |
| { | |
| if (i1.opcode == i2.opcode) | |
| { | |
| if (i1.opcode == OpCodes.Ldstr) | |
| { | |
| return ((string)i1.operand) == ((string)i2.operand); | |
| } | |
| else if (i1.opcode == OpCodes.Call || i1.opcode == OpCodes.Callvirt || i1.opcode == OpCodes.Ldsfld || i1.opcode == OpCodes.Ldfld) | |
| { | |
| return ((MemberInfo)i1.operand).MetadataToken == ((MemberInfo)i2.operand).MetadataToken; | |
| } | |
| else if (i1.operand == i2.operand) | |
| { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| /* | |
| * The following patch bypasses the swim confirmation message that appears before entering a | |
| * swim-depth body of water, if the player currently has a flexiboard equipped. We just need to | |
| * insert two new IL instructions in the appropriate spot to perform that equip check and | |
| * suppress the message in GameObject.Move(). | |
| * | |
| */ | |
| [HarmonyPatch(typeof(XRL.World.GameObject))] | |
| public class Patch_XRL_World_GameObject | |
| { | |
| public static MethodInfo Method_HasEffect = typeof(XRL.World.GameObject).GetMethod("HasEffect", new Type[] { typeof(string) }); | |
| public static MethodInfo Method_HasBridge = typeof(XRL.World.Cell).GetMethod("HasBridge"); | |
| public static FieldInfo Field_XRLCore = typeof(XRLCore).GetField("Core"); | |
| public static FieldInfo Field_MoveConfirmDirection = typeof(XRLCore).GetField("MoveConfirmDirection"); | |
| public static MethodInfo Method_IsFlexiboardEquipped = typeof(Egcb_Flexiboard).GetMethod("PlayerHasFlexiboardEquipped"); | |
| public static MethodInfo Method_StringNotEqual = typeof(System.String).GetMethod("op_Inequality"); | |
| //instruction sequence we are looking for. We'll insert our patch after this. | |
| private readonly static List<CodeInstruction> TargetInstructions = new List<CodeInstruction>() | |
| { | |
| new CodeInstruction(OpCodes.Ldstr, "Swimming"), | |
| new CodeInstruction(OpCodes.Call, Method_HasEffect), | |
| new CodeInstruction(OpCodes.Callvirt, Method_HasBridge), | |
| new CodeInstruction(OpCodes.Ldsfld, Field_XRLCore), | |
| new CodeInstruction(OpCodes.Ldfld, Field_MoveConfirmDirection), | |
| new CodeInstruction(OpCodes.Ldarg_1, null), | |
| new CodeInstruction(OpCodes.Call, Method_StringNotEqual) | |
| }; | |
| //max allowed distance between individual instructions in the above sequence | |
| private static int AllowedInstructionDistance = 10; | |
| [HarmonyTranspiler] | |
| [HarmonyPatch("Move")] | |
| static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) | |
| { | |
| int idx = 0; | |
| int gap = 0; | |
| object jumpTarget = null; | |
| bool found = false; | |
| bool patchComplete = false; | |
| foreach (var instruction in instructions) | |
| { | |
| if (found) | |
| { | |
| if (!patchComplete) | |
| { | |
| if (jumpTarget == null) | |
| { | |
| if (instruction.opcode != OpCodes.Brfalse) | |
| { | |
| throw new Exception("Exception in Junkbunker Mod - Unexpected opcode found in GameObject.Move patch transpiler."); | |
| } | |
| jumpTarget = instruction.operand; | |
| } | |
| else | |
| { | |
| //insert our patch code | |
| yield return new CodeInstruction(OpCodes.Call, Method_IsFlexiboardEquipped); | |
| yield return new CodeInstruction(OpCodes.Brtrue, jumpTarget); | |
| patchComplete = true; | |
| } | |
| } | |
| } | |
| else if (Patch_Helpers.InstructionsAreEqual(instruction, TargetInstructions[idx])) | |
| { | |
| if (++idx == TargetInstructions.Count()) | |
| { | |
| found = true; | |
| } | |
| gap = 0; | |
| } | |
| else | |
| { | |
| if (++gap > AllowedInstructionDistance) | |
| { | |
| idx = 0; | |
| } | |
| } | |
| yield return instruction; | |
| } | |
| if (patchComplete == false) | |
| { | |
| XRLCore.Log("Junkbunker Mod: (Warning) Failed to patch swim behavior for the flexiboard item. If you " | |
| + "have the \"Confirm Swimming\" option turned on, the flexiboard may be somewhat annoying to use " | |
| + "in large bodies of water (You'll have to confirm your movement each tile)."); | |
| } | |
| } | |
| } | |
| /* | |
| * The following patch allows Egcb_ConstrainedLiquidVolume to be interpreted as a valid target for | |
| * "pour" events. The game by default only looks for objects with a "LiquidVolume" part to pour into. | |
| * | |
| */ | |
| [HarmonyPatch(typeof(XRL.World.Parts.LiquidVolume))] | |
| public class Patch_XRL_World_Parts_LiquidVolume | |
| { | |
| [HarmonyPatch("PerformFill")] | |
| static void Prefix() | |
| { | |
| //XRLCore.Log("LiquidVolume.PerformFill PREFIX called"); | |
| Patch_XRL_World_IPart.PerformFillActive = true; | |
| //Egcb_ConstrainedLiquidVolume.PretendToBeALiquidVolume(); | |
| } | |
| [HarmonyPatch("PerformFill")] | |
| static void Postfix() | |
| { | |
| //XRLCore.Log("LiquidVolume.PerformFill POSTFIX called"); | |
| //Egcb_ConstrainedLiquidVolume.StopPretendingToBeALiquidVolume(); | |
| Patch_XRL_World_IPart.PerformFillActive = false; | |
| } | |
| } | |
| [HarmonyPatch(typeof(XRL.World.IPart))] | |
| public class Patch_XRL_World_IPart | |
| { | |
| public static bool PerformFillActive = false; | |
| private static string NameOf_Egcb_ConstrainedLiquidVolume = typeof(Egcb_ConstrainedLiquidVolume).Name; | |
| private static string NameOf_LiquidVolume = typeof(LiquidVolume).Name; | |
| [HarmonyPatch("Name", MethodType.Getter)] | |
| static void Postfix(ref string __result) | |
| { | |
| if (PerformFillActive == true) | |
| { | |
| if (__result == NameOf_Egcb_ConstrainedLiquidVolume) | |
| { | |
| //XRLCore.Log("ADJUSTING Egcb_ConstrainedLiquidVolume type name to 'LiquidVolume'."); | |
| __result = NameOf_LiquidVolume; | |
| } | |
| } | |
| } | |
| } | |
| //[HarmonyPatch(typeof(XRL.World.GameObject))] | |
| //public class Patch_XRL_World_GameObject_2 | |
| //{ | |
| // [HarmonyPatch("IsAutogetLiquid")] | |
| // static void Postfix(XRL.World.GameObject __instance) | |
| // { | |
| // LiquidVolume liquidVolume = __instance.GetPart("LiquidVolume") as LiquidVolume; | |
| // if (liquidVolume != null && liquidVolume.IsOpenVolume() && liquidVolume.IsMixed()) | |
| // { | |
| // } | |
| // } | |
| //} | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment