0
0
mirror of https://github.com/sp-tarkov/modules.git synced 2025-02-13 09:50:43 -05:00
modules/project/SPT.SinglePlayer/Patches/ScavMode/LoadOfflineRaidScreenPatch.cs

160 lines
7.6 KiB
C#
Raw Normal View History

2024-05-21 19:10:17 +01:00
using SPT.Reflection.Patching;
using SPT.Reflection.Utils;
2023-03-03 18:52:31 +00:00
using EFT;
using EFT.Bots;
using EFT.UI.Matchmaker;
using EFT.UI.Screens;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using OfflineRaidAction = System.Action;
// DON'T FORGET TO UPDATE REFERENCES IN CONSTRUCTOR
// AND IN THE LoadOfflineRaidScreenForScavs METHOD AS WELL
2024-05-21 19:10:17 +01:00
namespace SPT.SinglePlayer.Patches.ScavMode
2023-03-03 18:52:31 +00:00
{
public class LoadOfflineRaidScreenPatch : ModulePatch
{
private static readonly MethodInfo _onReadyScreenMethod;
private static readonly FieldInfo _isLocalField;
private static readonly FieldInfo _menuControllerField;
static LoadOfflineRaidScreenPatch()
{
_ = nameof(MainMenuController.InventoryController);
_ = nameof(TimeAndWeatherSettings.IsRandomWeather);
_ = nameof(BotControllerSettings.IsScavWars);
_ = nameof(WavesSettings.IsBosses);
2024-02-27 18:57:49 +00:00
_ = MatchmakerPlayerControllerClass.MAX_SCAV_COUNT; // UPDATE REFS TO THIS CLASS BELOW !!!
2023-03-03 18:52:31 +00:00
// `MatchmakerInsuranceScreen` OnShowNextScreen
2024-10-31 21:27:32 +00:00
_onReadyScreenMethod = AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_46));
_isLocalField = AccessTools.Field(typeof(MainMenuController), "bool_0");
2023-03-03 18:52:31 +00:00
_menuControllerField = typeof(TarkovApplication).GetFields(PatchConstants.PrivateFlags).FirstOrDefault(x => x.FieldType == typeof(MainMenuController));
if (_menuControllerField == null)
{
Logger.LogError($"LoadOfflineRaidScreenPatch() menuControllerField is null and could not be found in {nameof(TarkovApplication)} class");
}
}
protected override MethodBase GetTargetMethod()
{
// `MatchMakerSelectionLocationScreen` OnShowNextScreen
2024-07-04 21:34:31 +01:00
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_73));
2023-03-03 18:52:31 +00:00
}
[PatchTranspiler]
public static IEnumerable<CodeInstruction> PatchTranspiler(ILGenerator generator, IEnumerable<CodeInstruction> instructions)
2023-03-03 18:52:31 +00:00
{
/* The original msil looks something like this:
* 0 0000 ldarg.0
* 1 0001 call instance void MainMenuController::method_69()
* 2 0006 ldarg.0
* 3 0007 call instance void MainMenuController::method_41()
* 4 000C ldarg.0
* 5 000D call instance bool MainMenuController::method_46()
* 6 0012 brtrue.s 8 (0015) ldarg.0
* 7 0014 ret
* 8 0015 ldarg.0
* 9 0016 ldfld class EFT.RaidSettings MainMenuController::raidSettings_0
* 10 001B callvirt instance bool EFT.RaidSettings::get_IsPmc()
* 11 0020 brfalse.s 15 (0029) ldarg.0
* 12 0022 ldarg.0
* 13 0023 call instance void MainMenuController::method_42()
* 14 0028 ret
* 15 0029 ldarg.0
* 16 002A call instance void MainMenuController::method_44()
* 17 002F ret
*
* The goal is to replace the call to method_44 with our own LoadOfflineRaidScreenForScav function.
* method_44 expects one argument which is the implicit "this" pointer.
* The ldarg.0 instruction loads "this" onto the stack and the function call will consume it.
* But because our own LoadOfflineRaidScreenForScav method is static
* it won't consume a "this" pointer from the stack, so we have to remove the ldarg.0 instruction.
* But the brfalse instruction at 0020 jumps to the ldarg.0, so we can not simply delete it.
* Instead, we first need to transfer the jump label from the ldarg.0 instruction to our new
* call instruction and only then we remove it.
*/
2023-03-03 18:52:31 +00:00
var codes = new List<CodeInstruction>(instructions);
var onReadyScreenMethodOperand = AccessTools.Method(typeof(MainMenuController), _onReadyScreenMethod.Name);
2023-03-03 18:52:31 +00:00
var callCodeIndex = codes.FindLastIndex(code => code.opcode == OpCodes.Call
&& (MethodInfo)code.operand == onReadyScreenMethodOperand);
2023-03-03 18:52:31 +00:00
if (callCodeIndex == -1)
2023-03-03 18:52:31 +00:00
{
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Could not find {nameof(_onReadyScreenMethod)} reference code.");
}
var loadThisIndex = callCodeIndex - 1;
if (codes[loadThisIndex].opcode != OpCodes.Ldarg_0)
2023-03-03 18:52:31 +00:00
{
throw new Exception($"{nameof(LoadOfflineRaidScreenPatch)} failed: Expected ldarg.0 before call instruction but found {codes[loadThisIndex]}");
2023-03-03 18:52:31 +00:00
}
// Overwrite the call instruction with the call to LoadOfflineRaidScreenForScav, preserving the label for the 0020 brfalse jump
codes[callCodeIndex] = new CodeInstruction(OpCodes.Call,
AccessTools.Method(typeof(LoadOfflineRaidScreenPatch), nameof(LoadOfflineRaidScreenForScav))) {
labels = codes[loadThisIndex].labels
};
2023-03-03 18:52:31 +00:00
// Remove the ldarg.0 instruction which we no longer need because LoadOfflineRaidScreenForScav is static
codes.RemoveAt(loadThisIndex);
2023-03-03 18:52:31 +00:00
return codes.AsEnumerable();
}
private static void LoadOfflineRaidScreenForScav()
{
var profile = PatchConstants.BackEndSession.Profile;
var menuController = (object)GetMenuController();
// Get fields from MainMenuController.cs
2023-03-03 18:52:31 +00:00
var raidSettings = Traverse.Create(menuController).Field("raidSettings_0").GetValue<RaidSettings>();
2024-07-17 15:45:37 +01:00
var offlineRaidSettings = Traverse.Create(menuController).Field("raidSettings_1").GetValue<RaidSettings>();
2024-02-27 19:53:29 +00:00
// Find the private field of type `MatchmakerPlayerControllerClass`
var matchmakerPlayersController = menuController.GetType()
.GetFields(AccessTools.all)
.Single(field => field.FieldType == typeof(MatchmakerPlayerControllerClass))
.GetValue(menuController) as MatchmakerPlayerControllerClass;
2024-07-17 15:45:37 +01:00
var gclass = new MatchmakerOfflineRaidScreen.CreateRaidSettingsForProfileClass(profile?.Info, ref raidSettings, ref offlineRaidSettings, matchmakerPlayersController, ESessionMode.Pve);
2023-03-03 18:52:31 +00:00
gclass.OnShowNextScreen += LoadOfflineRaidNextScreen;
// `MatchmakerOfflineRaidScreen` OnShowReadyScreen
2024-07-04 21:34:31 +01:00
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, nameof(MainMenuController.method_77));
2023-03-03 18:52:31 +00:00
gclass.ShowScreen(EScreenState.Queued);
}
private static void LoadOfflineRaidNextScreen()
{
var menuController = GetMenuController();
var raidSettings = Traverse.Create(menuController).Field("raidSettings_0").GetValue<RaidSettings>();
if (raidSettings.SelectedLocation.Id == "laboratory")
{
raidSettings.WavesSettings.IsBosses = true;
}
// Set offline raid values
2023-03-03 18:52:31 +00:00
_isLocalField.SetValue(menuController, raidSettings.Local);
// Load ready screen method
2023-03-03 18:52:31 +00:00
_onReadyScreenMethod.Invoke(menuController, null);
}
private static MainMenuController GetMenuController()
{
return _menuControllerField.GetValue(ClientAppUtils.GetMainApp()) as MainMenuController;
}
}
}