static IEnumerable <CodeInstruction> Transpiler_ShowCreateCharacter(IEnumerable <CodeInstruction> instructions) { var Sequence = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Call, CreateCharacter_BuildLibraryManagement) }); bool patched = false; foreach (var instruction in instructions) { if (!patched && Sequence.IsMatchComplete(instruction)) { instruction.operand = QudUX_BuildLibraryScreen_Show; patched = true; } yield return(instruction); } if (patched) { PatchHelpers.LogPatchResult("CreateCharacter", "Patched successfully." /* Revamps the Build Library text UI. */); } else { PatchHelpers.LogPatchResult("CreateCharacter", "Failed. This patch may not be compatible with the current game version. " + "The revamped Build Library text UI will not be used."); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { var Sequence = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldstr, "Starting game!") }); bool patched = false; foreach (var instruction in instructions) { if (!patched && Sequence.IsMatchComplete(instruction)) { yield return(new CodeInstruction(OpCodes.Call, Events_EmbarkEvent)); yield return(new CodeInstruction(OpCodes.Call, Events_OnLoadAlwaysEvent)); patched = true; } yield return(instruction); } if (patched) { PatchHelpers.LogPatchResult("XRLCore.NewGame", "Patched successfully." /* Enables an event framework that other QudUX features rely on. */); } else { PatchHelpers.LogPatchResult("XRLCore.NewGame", "Failed. This patch may not be compatible with the current game version. " + "Custom tiles chosen during character creation won't be properly applied at game start, " + "and certain other event-based QudUX features might not work as expected."); } }
static bool Prepare() { if (!Options.UI.UseQudUXCookMenus) { PatchHelpers.LogPatchResult("Campfire", "Skipped. The \"Use revamped cooking menus\" option is disabled."); return(false); } return(true); }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { //QudHistoryFactory.NameRuinsSite takes Stat.Random(0, 80) and then if the value is not less than //60, it returns "some forgotten ruins" as the name of a ruins site. To patch this function, we //will find the "Stat.Random(0, 80)" code, and replace it with "Stat.Random(0, 59)" which will //ensure that "some forgotten ruins" is never returned from the function. //Specifically, we need to find and overwrite the following sequence of IL instructions // IL_0321: ldc.i4.0 // IL_0322: ldc.i4.s 80 // IL_0324: call int32 XRL.Rules.Stat::Random(int32, int32) var ins = new List <CodeInstruction>(instructions); //don't apply patch if option is toggled off if (!Options.Exploration.RenameRuins) { PatchHelpers.LogPatchResult("QudHistoryFactory", "Skipped. The \"Force all ruins to have unique names\" option is disabled."); return(ins.AsEnumerable()); } for (var i = 0; i < ins.Count; i++) { if (ins[i].opcode == OpCodes.Ldc_I4_0 && (i + 2) < ins.Count) { if (ins[i + 1].opcode == OpCodes.Ldc_I4_S && Convert.ToInt32(ins[i + 1].operand) == 80) { if (ins[i + 2].opcode == OpCodes.Call) { MethodInfo callMethodInfo = (MethodInfo)(ins[i + 2].operand); if (callMethodInfo.MetadataToken == Stat_Random.MetadataToken) { TranspilePatchApplied = true; PatchHelpers.LogPatchResult("QudHistoryFactory", "Patched successfully." /* Removes \"some forgotten ruins\" as a naming option and ensures all ruins have unique names. */); //We have found our target triplet of IL instructions ins[i + 1].operand = 59; //make the modification return(ins.AsEnumerable()); //return the modified instructions } } } } } PatchHelpers.LogPatchResult("QudHistoryFactory", "Failed. This patch may not be compatible with the current game version. Ruins may not all have unique names, and \"some forgotten ruins\" may still be used."); return(ins.AsEnumerable()); }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { bool patchComplete = false; var ilcodes = new List <CodeInstruction>(instructions); for (int i = 0; i < ilcodes.Count; i++) { if (PatchHelpers.InstructionsAreEqual(ilcodes[i], TargetInstruction)) { if (ilcodes[i - 2].opcode == OpCodes.Br) { object jumpTarget = ilcodes[i - 2].operand; List <CodeInstruction> optionSwitch = PatchHelpers.ILBlocks.IfOptionYesJumpTo(Options.Exploration.OptionStrings.DisableMagnets, jumpTarget); //clone and then null out this instruction, preserving the jump label here CodeInstruction shiftedInstruction = ilcodes[i - 1].Clone(); ilcodes[i - 1].opcode = OpCodes.Nop; ilcodes[i - 1].operand = null; //insert our option checking code next after the Nop/label ilcodes.InsertRange(i, optionSwitch); //reinsert the cloned instruction we copied earlier (now without a label) ilcodes.Insert(i + optionSwitch.Count, shiftedInstruction); patchComplete = true; break; } } } if (patchComplete) { PatchHelpers.LogPatchResult("MagneticPulse", "Patched successfully." /* Enables option to prevent pulsed field magnets from ripping items from your inventory. */); } else { PatchHelpers.LogPatchResult("MagneticPulse", "Failed. This patch may not be compatible with the current game version. " + "The option to prevent pulsed field magnets from ripping items from your inventory won't work."); } return(ilcodes.AsEnumerable()); }
static bool Prepare(MethodBase original) { if (original == null) { return(true); } if (original.Name == "ShowCreateCharacter" && !Options.UI.UseQudUXBuildLibrary) { PatchHelpers.LogPatchResult("CreateCharacter", "Skipped. The \"Use revamped build library\" option is disabled."); return(false); } if (original.Name == "GenerateCharacter" && !Options.UI.UseSpriteMenu) { PatchHelpers.LogPatchResult("GenerateCharacter", "Skipped. Adds a menu for choosing your character sprite during character creation."); return(false); } return(true); }
private static void ReportPatchStatus(bool success) { PatchStatuses.Add(success); if (PatchStatuses.Count >= 2) { int failCount = PatchStatuses.Where(s => s == false).Count(); if (failCount > 0) { PatchHelpers.LogPatchResult("GameObject.Move", $"Failed ({failCount}/2). This patch may not be compatible with the current game version. " + "Some particle text effects may not be shown when movement is prevented."); } else { PatchHelpers.LogPatchResult("GameObject.Move", "Patched successfully." /* Adds option to show particle text messages when movement is prevented for various reasons. */); } PatchStatuses.Clear(); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { var Sequence1 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldstr, " ]}}"), new PatchTargetInstruction(OpCodes.Callvirt, ScreenBuffer_Write, 2), new PatchTargetInstruction(OpCodes.Pop, 0) }); bool patched = false; foreach (var instruction in instructions) { yield return(instruction); if (!patched && Sequence1.IsMatchComplete(instruction)) { //draw conversation speaker's tile and temporarily suppress any Unity prefab animations beneath it yield return(new CodeInstruction(OpCodes.Ldsfld, ConversationUI__ScreenBuffer)); //_ScreenBuffer yield return(new CodeInstruction(OpCodes.Ldarg_1)); //Speaker yield return(new CodeInstruction(OpCodes.Call, ConversationUIExtender_DrawConversationSpeakerTile)); patched = true; } } if (patched) { PatchHelpers.LogPatchResult("ConversationUI", "Patched successfully." /* Adds the speaker's sprite to the title bar of conversation windows. */); } else { PatchHelpers.LogPatchResult("ConversationUI", "Failed. This patch may not be compatible with the current game version. " + "Sprites won't be added to the title bar of conversation windows."); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { var Sequence = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldstr, "The way is blocked by "), new PatchTargetInstruction(OpCodes.Call, IComponent_GameObject_AddPlayerMessage, 11) }); bool patched = false; foreach (var instruction in instructions) { yield return(instruction); if (!patched && Sequence.IsMatchComplete(instruction)) { yield return(new CodeInstruction(OpCodes.Ldarg_0)); yield return(new CodeInstruction(OpCodes.Callvirt, IPart_get_ParentObject)); yield return(new CodeInstruction(OpCodes.Call, ParticleTextMaker_EmitFromPlayerIfBarrierInDifferentZone)); patched = true; } } if (patched) { PatchHelpers.LogPatchResult("Physics.HandleEvent", "Patched successfully." /* Adds option to show particle text messages when movement to connected zone is prevented. */); } else { PatchHelpers.LogPatchResult("Physics.HandleEvent", "Failed. This patch may not be compatible with the current game version. " + "Some particle text effects may not be shown when movement is prevented."); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions, ILGenerator generator) { var Sequence1 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)62), new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)24, 0), new PatchTargetInstruction(OpCodes.Callvirt, 0), //ScreenBuffer.Goto new PatchTargetInstruction(OpCodes.Pop, 0), new PatchTargetInstruction(OpCodes.Ldsfld, 0), new PatchTargetInstruction(LoadHighScoreDeleteCommandString, 0), new PatchTargetInstruction(OpCodes.Ldc_I4_1, 0), new PatchTargetInstruction(OpCodes.Callvirt, ScreenBuffer_Write, 0), new PatchTargetInstruction(OpCodes.Pop, 0), }); var Sequence2 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldsfld), //continues immediately after the previous sequence new PatchTargetInstruction(OpCodes.Ldsfld, 0), new PatchTargetInstruction(OpCodes.Ldnull, 0), new PatchTargetInstruction(OpCodes.Ldc_I4_1, 0), new PatchTargetInstruction(OpCodes.Callvirt, TextConsole_DrawBuffer, 0) }); var Sequence3 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(IsStoreLocalInstruction), new PatchTargetInstruction(IsLoadLocalInstruction, 0), new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)98, 0) }); int seq = 1; bool patched = false; foreach (var instruction in instructions) { if (seq == 1) { if (Sequence1.IsMatchComplete(instruction)) { yield return(new CodeInstruction(OpCodes.Ldc_I4_S, 58)); yield return(new CodeInstruction(OpCodes.Ldc_I4_S, 23)); yield return(Sequence1.MatchedInstructions[2].Clone()); yield return(new CodeInstruction(OpCodes.Ldstr, "&Y[&WTab&y - Detailed Stats&Y]")); yield return(Sequence1.MatchedInstructions[6].Clone()); yield return(Sequence1.MatchedInstructions[7].Clone()); seq++; } } else if (seq == 2) { if (Sequence2.IsMatchComplete(instruction)) { seq++; } } else if (!patched && seq == 3) { if (Sequence3.IsMatchComplete(instruction)) { //here we are essentially adding: // if (keys == Keys.Tab) // { // EnhancedScoreboardExtender.ShowGameStatsScreen(); // Console.DrawBuffer(Buffer, null, bSkipIfOverlay: true); // } yield return(new CodeInstruction(OpCodes.Ldc_I4_S, 9)); //Keys.Tab Label newLabel = generator.DefineLabel(); yield return(new CodeInstruction(OpCodes.Bne_Un_S, newLabel)); yield return(new CodeInstruction(OpCodes.Call, EnhancedScoreboardExtender_ShowGameStatsScreen)); //redraw the buffer over the screen we made yield return(Sequence2.MatchedInstructions[0].Clone()); yield return(Sequence2.MatchedInstructions[1].Clone()); yield return(Sequence2.MatchedInstructions[2].Clone()); yield return(Sequence2.MatchedInstructions[3].Clone()); yield return(Sequence2.MatchedInstructions[4].Clone()); CodeInstruction markedLoadLocal = Sequence3.MatchedInstructions[1].Clone(); markedLoadLocal.labels.Add(newLabel); yield return(markedLoadLocal); patched = true; } } yield return(instruction); } if (patched) { PatchHelpers.LogPatchResult("Scores", "Patched successfully." /* Adds an option to open a detailed game stats menu from the High Scores console UI. */); } else { PatchHelpers.LogPatchResult("Scores", "Failed. This patch may not be compatible with the current game version. " + "The High Scores text UI won't have an option to open detailed stats."); } }
static IEnumerable <CodeInstruction> Transpiler_GenerateCharacter(IEnumerable <CodeInstruction> instructions, ILGenerator generator) { var Sequence1 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldstr, "StartingPet"), new PatchTargetInstruction(OpCodes.Callvirt, List_GameObjectBlueprint_get_Count, 1), new PatchTargetInstruction(OpCodes.Ldsfld, CreateCharacter__Console, 35), new PatchTargetInstruction(OpCodes.Ldarg_0, 0), new PatchTargetInstruction(OpCodes.Ldnull, 0), new PatchTargetInstruction(OpCodes.Ldc_I4_0, 0), new PatchTargetInstruction(OpCodes.Callvirt, TextConsole_DrawBuffer, 0) }); var Sequence2 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)84), new PatchTargetInstruction(OpCodes.Bne_Un_S, 0), new PatchTargetInstruction(OpCodes.Ldstr, "HEY! Try my Caves of Qud character build.\n", 0), new PatchTargetInstruction(IsLoadLocalInstruction, 5), new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)83) }); int seq = 1; bool patched = false; foreach (var instruction in instructions) { if (seq == 1) { if (Sequence1.IsMatchComplete(instruction)) { yield return(Sequence1.MatchedInstructions[3].Clone()); yield return(new CodeInstruction(OpCodes.Call, CreateCharacterExtender_WriteCharCreateSpriteOptionText)); seq++; } } else if (!patched && seq == 2) { if (Sequence2.IsMatchComplete(instruction)) { //here we are essentially adding: // if (keys == Keys.M) // { // CreateCharacterExtender.PickCharacterTile(); // _Console.DrawBuffer(SB); // } yield return(new CodeInstruction(OpCodes.Ldc_I4_S, 77)); //Keys.M Label newLabel = generator.DefineLabel(); yield return(new CodeInstruction(OpCodes.Bne_Un_S, newLabel)); yield return(new CodeInstruction(OpCodes.Ldsfld, CreateCharacter_Template)); yield return(new CodeInstruction(OpCodes.Call, CreateCharacterExtender_PickCharacterTile)); //redraw the buffer over the screen we made yield return(Sequence1.MatchedInstructions[2].Clone()); yield return(Sequence1.MatchedInstructions[3].Clone()); yield return(Sequence1.MatchedInstructions[4].Clone()); yield return(Sequence1.MatchedInstructions[5].Clone()); yield return(Sequence1.MatchedInstructions[6].Clone()); CodeInstruction markedLoadLocal = Sequence2.MatchedInstructions[3].Clone(); markedLoadLocal.labels.Add(newLabel); yield return(markedLoadLocal); patched = true; } } yield return(instruction); } if (patched) { PatchHelpers.LogPatchResult("GenerateCharacter", "Patched successfully." /* Adds a menu for choosing your character sprite during character creation. */); } else { PatchHelpers.LogPatchResult("GenerateCharacter", "Failed. This patch may not be compatible with the current game version. " + "The character sprite customization menu won't be available during character creation."); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { int idx = 0; int gap = 0; int patchSegment = 1; bool found = false; CodeInstruction instruction_LoadLocal_AbilityNodeArray = null; CodeInstruction instruction_LoadLocal_AbilityNodeIndex = null; CodeInstruction instruction_LoadLocal_TextBlock = null; bool patchComplete = false; foreach (var instruction in instructions) { if (found) { if (patchSegment == 1) { if (idx == 0 && instruction.opcode == OpCodes.Brfalse_S) { idx++; //do nothing, we're just confirming the expected instruction is here } else if (idx == 1 && PatchHelpers.IsLoadLocalInstruction(instruction)) { instruction_LoadLocal_AbilityNodeArray = instruction.Clone(); idx++; } else if (idx == 2 && PatchHelpers.IsLoadLocalInstruction(instruction)) { instruction_LoadLocal_AbilityNodeIndex = instruction.Clone(); found = false; idx = 0; patchSegment++; } } else if (!patchComplete) { if (idx == 0 && instruction.opcode == OpCodes.Stloc_S) { idx++; instruction_LoadLocal_TextBlock = new CodeInstruction(OpCodes.Ldloc_S, instruction.operand); } else if (idx == 1) { //inject our patch code //get the current AbilityNode item from the array and load it onto the stack yield return(instruction_LoadLocal_AbilityNodeArray); yield return(instruction_LoadLocal_AbilityNodeIndex); yield return(new CodeInstruction(OpCodes.Callvirt, List_AbilityNode_get_Item)); //load the TextBlock onto the stack before the game uses it to write to the screen yield return(instruction_LoadLocal_TextBlock); //call our custom function to update the text block yield return(new CodeInstruction(OpCodes.Call, AbilityManagerExtender_UpdateAbilityText)); PatchHelpers.LogPatchResult("AbilityManager", "Patched successfully." /* Improves activated ability descriptions and cooldown information on the Manage Abilities screen. */); patchComplete = true; } } } else if (patchSegment == 1) { if (PatchHelpers.InstructionsAreEqual(instruction, FirstSegmentTargetInstructions[idx])) { if (++idx == FirstSegmentTargetInstructions.Count()) { found = true; idx = 0; } gap = 0; } else { if (++gap > AllowedInstructionDistance) { idx = 0; } } } else if (patchSegment == 2) { if (PatchHelpers.InstructionsAreEqual(instruction, SecondSegmentTargetInstruction)) { found = true; } } yield return(instruction); } if (patchComplete == false) { PatchHelpers.LogPatchResult("AbilityManager", "Failed. This patch may not be compatible with the current game version. Improved activated ability descriptions and cooldown information won't be added to the Manage Abilities screen."); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { int patchSegment = 1; int patchSegment1_stloc_ct = 0; object ingredientList_LocalVarIndex = null; object ingredientBools_LocalVarIndex = null; object recipeList_LocalVarIndex = null; int idx = 0; int gap = 0; bool found = false; bool patchComplete = false; foreach (var instruction in instructions) { if (found) { if (patchSegment == 1) { if (instruction.opcode == OpCodes.Stloc_S) { patchSegment1_stloc_ct++; } if (patchSegment1_stloc_ct == 2) { //save the local variable index of the ingredient list ingredientList_LocalVarIndex = instruction.operand; patchSegment++; found = false; } } else if (patchSegment == 2) { if (instruction.opcode == OpCodes.Stloc_S) { ingredientBools_LocalVarIndex = instruction.operand; patchSegment++; found = false; } } else if (patchSegment == 3) { //ignore all the lines that push stuff onto the stack for Popup.ShowOptionList if (!PatchHelpers.InstructionsAreEqual(instruction, ThirdSegmentFinalInstruction)) { continue; } //replace the call to Popup.ShowOptionList with our custom ingredient selection menu yield return(new CodeInstruction(OpCodes.Ldloc_S, ingredientList_LocalVarIndex)); yield return(new CodeInstruction(OpCodes.Ldloc_S, ingredientBools_LocalVarIndex)); yield return(new CodeInstruction(OpCodes.Call, QudUX_IngredientSelectionScreen_Static_Show)); patchSegment++; found = false; continue; } else if (patchSegment == 4) { //unused } else if (patchSegment == 5) { if (recipeList_LocalVarIndex == null && instruction.opcode == OpCodes.Ldloc_S) { //grab the recipe list variable, we'll need it below recipeList_LocalVarIndex = instruction.operand; } else if (PatchHelpers.InstructionsAreEqual(instruction, FifthSegmentFinalInstruction)) { //replace the call to Popup.ShowOptionList with our custom recipe selection menu yield return(new CodeInstruction(OpCodes.Ldloc_S, recipeList_LocalVarIndex)); yield return(new CodeInstruction(OpCodes.Call, QudUX_RecipeSelectionScreen_Static_Show)); patchSegment++; found = false; } continue; } else if (!patchComplete) { if (PatchHelpers.InstructionsAreEqual(instruction, SixthSegmentFinalInstruction)) { patchComplete = true; PatchHelpers.LogPatchResult("Campfire", "Patched successfully." /* Adds completely new UI screens for ingredient- and recipe-based cooking. */); //allow this instruction to fall through, we want it and everything after it. } else { //null out various instructions (several of them are used as labels, so can't just ignore them) instruction.opcode = OpCodes.Nop; instruction.operand = null; } } } else if (patchSegment == 1) { if (PatchHelpers.InstructionsAreEqual(instruction, FirstSegmentTargetInstruction)) { found = true; idx = 0; } } else if (patchSegment == 2) { if (PatchHelpers.InstructionsAreEqual(instruction, SecondSegmentTargetInstructions[idx])) { if (++idx == SecondSegmentTargetInstructions.Count()) { found = true; idx = 0; } gap = 0; } else { if (++gap > AllowedInstructionDistance) { idx = 0; } } } else if (patchSegment == 3) { if (PatchHelpers.InstructionsAreEqual(instruction, ThirdSegmentTargetInstructions[idx])) { if (++idx == ThirdSegmentTargetInstructions.Count()) { found = true; instruction.opcode = OpCodes.Nop; //null out this instruction (can't remove it because it's used as a label) instruction.operand = null; idx = 0; } gap = 0; } else { if (++gap > AllowedInstructionDistance) { idx = 0; } } } else if (patchSegment == 4) { if (idx == 0) { if (PatchHelpers.InstructionsAreEqual(instruction, FourthSegmentTarget1_Instruction)) { idx++; } } else if (idx == 1) { if (instruction.opcode == FourthSegmentTarget2_OpCodeOnly) { idx++; } else { idx = 0; } } else if (idx == 2) { if (!PatchHelpers.InstructionsAreEqual(instruction, FourthSegmentTarget3_Instruction)) { if (++gap > FourthSegmentAllowedInstructionDistance) { idx = 0; gap = 0; } } else { instruction.opcode = OpCodes.Ldc_I4_1; //modify to set flag to true instead of false, so that recipes without ingredients on hand aren't hidden from the array patchSegment++; idx = 0; gap = 0; } } } else if (patchSegment == 5) { if (PatchHelpers.InstructionsAreEqual(instruction, FifthSegmentTargetInstruction)) { found = true; instruction.opcode = OpCodes.Nop; //null out this instruction (can't remove it because it's used as a label) instruction.operand = null; } } else if (patchSegment == 6) { if (PatchHelpers.InstructionsAreEqual(instruction, SixthSegmentTargetInstruction)) { found = true; instruction.opcode = OpCodes.Nop; instruction.operand = null; } } yield return(instruction); } if (patchComplete == false) { PatchHelpers.LogPatchResult("Campfire", "Failed. This patch may not be compatible with the current game version. " + "The game's default cooking UI pop-ups will be used instead of QudUX's revamped screens."); } }
static IEnumerable <CodeInstruction> Transpiler(IEnumerable <CodeInstruction> instructions) { var Sequence1 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldstr, "CmdWalk"), new PatchTargetInstruction(OpCodes.Ldstr, " | {{hotkey|", 8), new PatchTargetInstruction(OpCodes.Ldsfld, Look_Buffer, 30), new PatchTargetInstruction(OpCodes.Ldloc_S, 0), new PatchTargetInstruction(OpCodes.Ldloc_S, 1), new PatchTargetInstruction(OpCodes.Callvirt, ScreenBuffer_WriteAt, 1), new PatchTargetInstruction(OpCodes.Pop, 0), new PatchTargetInstruction(OpCodes.Ldloc_S, 0), new PatchTargetInstruction(OpCodes.Brfalse, 0), new PatchTargetInstruction(OpCodes.Ldloc_S, 0), new PatchTargetInstruction(OpCodes.Brfalse, 0) }); var Sequence2 = new PatchTargetInstructionSet(new List <PatchTargetInstruction> { new PatchTargetInstruction(OpCodes.Ldloc_S), new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)101, 0), new PatchTargetInstruction(OpCodes.Beq_S, 0), new PatchTargetInstruction(OpCodes.Ldloc_S, 0), new PatchTargetInstruction(OpCodes.Ldc_I4_S, (object)27, 0), new PatchTargetInstruction(OpCodes.Bne_Un_S, 0), new PatchTargetInstruction(OpCodes.Ldc_I4_1, 0), new PatchTargetInstruction(OpCodes.Stloc_1, 0), new PatchTargetInstruction(OpCodes.Ldloc_S, 0) }); int seq = 1; bool patched = false; foreach (var instruction in instructions) { if (seq == 1) { if (Sequence1.IsMatchComplete(instruction)) { yield return(instruction); yield return(Sequence1.MatchedInstructions[2].Clone()); //load ScreenBuffer yield return(Sequence1.MatchedInstructions[7].Clone()); //load target GameObject yield return(Sequence1.MatchedInstructions[4].Clone()); //load Look UI hotkey string yield return(new CodeInstruction(OpCodes.Call, LookExtender_AddMarkLegendaryOptionToLooker)); seq++; continue; } } else if (!patched) { if (Sequence2.IsMatchComplete(instruction)) { CodeInstruction replacementInstruction = instruction.Clone(); instruction.opcode = Sequence2.MatchedInstructions[0].opcode; instruction.operand = Sequence2.MatchedInstructions[0].operand; yield return(instruction); //load pressed key onto stack (with existing label) yield return(Sequence1.MatchedInstructions[7].Clone()); //load target GameObject yield return(new CodeInstruction(OpCodes.Ldloc_1)); //load flag on the stack yield return(new CodeInstruction(OpCodes.Call, LookExtender_CheckKeyPress)); //call our method, which puts a bool on stack yield return(new CodeInstruction(OpCodes.Stloc_1)); //store bool into flag yield return(replacementInstruction); //return original instruction (without label) patched = true; continue; } } yield return(instruction); } if (patched) { PatchHelpers.LogPatchResult("Look", "Patched successfully." /* Adds option to mark legendary creature locations in the journal from the Look UI. */); } else { PatchHelpers.LogPatchResult("Look", "Failed. This patch may not be compatible with the current game version. " + "The option to mark legendary creature locations in journal won't be available from the Look UI."); } }