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) {