From 9a3e1de1ba13bf666c8afce80b4c6b8a4b9d5160 Mon Sep 17 00:00:00 2001
From: DrakiaXYZ <565558+TheDgtl@users.noreply.github.com>
Date: Tue, 31 Dec 2024 16:25:45 -0800
Subject: [PATCH] Transpiler changes to quest loading method to fix quests
failing when they shouldn't
---
.../Progression/QuestLoadStatusChangePatch.cs | 75 +++++++++++++++++++
.../SPT.SinglePlayer/SPTSingleplayerPlugin.cs | 3 +
2 files changed, 78 insertions(+)
create mode 100644 project/SPT.SinglePlayer/Patches/Progression/QuestLoadStatusChangePatch.cs
diff --git a/project/SPT.SinglePlayer/Patches/Progression/QuestLoadStatusChangePatch.cs b/project/SPT.SinglePlayer/Patches/Progression/QuestLoadStatusChangePatch.cs
new file mode 100644
index 0000000..9084185
--- /dev/null
+++ b/project/SPT.SinglePlayer/Patches/Progression/QuestLoadStatusChangePatch.cs
@@ -0,0 +1,75 @@
+using HarmonyLib;
+using SPT.Reflection.Patching;
+using SPT.Reflection.Utils;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+
+namespace SPT.SinglePlayer.Patches.Progression
+{
+ ///
+ /// Transpiler used to resolve BSG checking quest conditions prior to quest condition handlers being connected.
+ ///
+ /// In Tarkov 0.15.5, BSG refactored quest initialization, and added a call to `CheckForStatusChange` during the
+ /// init process. This happened prior to the quest condition handlers being setup, so quests would end up failing
+ /// even if you didn't meet the fail criteria in some cases. The most obvious case is the "Getting Acquainted" quest
+ /// failing on raid start/relog after accepting it
+ ///
+ /// Their fix (And the below fix) is to remove the call to `CheckForStatusChange` from the quest loading code
+ ///
+ /// This patch is only necessary for SPT targetting client version 0.15.5, the bug is fixed in 0.16
+ ///
+ internal class QuestLoadStatusChangePatch : ModulePatch
+ {
+ protected override MethodBase GetTargetMethod()
+ {
+ var methodName = "SetQuestStatusData";
+ var flags = BindingFlags.Public | BindingFlags.Instance;
+
+ var desiredType = PatchConstants.EftTypes.SingleCustom(x => x.GetMethod(methodName, flags) != null);
+ var desiredMethod = AccessTools.FirstMethod(desiredType, IsTargetMethod);
+
+ Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
+ Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
+
+ return desiredMethod;
+ }
+
+ private bool IsTargetMethod(MethodInfo method)
+ {
+ ParameterInfo[] parameters = method.GetParameters();
+
+ return method.Name.StartsWith("method_")
+ && method.ReturnType == typeof(void)
+ && parameters.Length == 1
+ && parameters[0].ParameterType == typeof(RawQuestClass);
+ }
+
+ [PatchTranspiler]
+ public static IEnumerable Transpile(IEnumerable instructions)
+ {
+ var codeList = new List(instructions);
+ for (var i = 0; i < codeList.Count; i++)
+ {
+ // We're looking for a `Callvirt` opcode, so skip anything else
+ if (codeList[i].opcode != OpCodes.Callvirt) continue;
+
+ // We want to find a call to `CheckForStatusChange`, so skip anything else
+ var stringOperand = codeList[i].operand.ToString();
+ if (stringOperand == null || !stringOperand.Contains("CheckForStatusChange")) continue;
+
+ // We've found our call, NOP it out. We need to NOP out the current code, and previous 3 to fully remove it
+ Logger.LogDebug($"QuestLoadStatusChangePatch Code: |{codeList[i]?.opcode}| |{codeList[i].operand}|");
+ codeList[i].opcode = OpCodes.Nop;
+ codeList[i - 1].opcode = OpCodes.Nop;
+ codeList[i - 2].opcode = OpCodes.Nop;
+ codeList[i - 3].opcode = OpCodes.Nop;
+
+ // There should only be one, so we can break out early
+ break;
+ }
+
+ return codeList;
+ }
+ }
+}
diff --git a/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs b/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs
index 8e49783..47c4d16 100644
--- a/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs
+++ b/project/SPT.SinglePlayer/SPTSingleplayerPlugin.cs
@@ -66,6 +66,9 @@ namespace SPT.SinglePlayer
new FirearmControllerShowIncompatibleNotificationClass().Enable();
new FixKeyAlreadyExistsErrorOnAchievementPatch().Enable();
+ // 3.10.x specific, should be removed for 4.0.x
+ new QuestLoadStatusChangePatch().Enable();
+
}
catch (Exception ex)
{