0
0
mirror of https://github.com/sp-tarkov/modules.git synced 2025-02-13 09:50:43 -05:00

Publicized assembly refactor (!58)

Depends on SPT-AKI/SPT-AssemblyTool#3

* Refactored Modules for better consistency and general readability, along with preparing the code for a publicized assembly
* Added `PublicDeclaredFlags` to `PatchConstants` to cover a set of commonly used flags to get methods post-publicizing
* Added a replacement to LINQ's `.Single()` - `.SingleCustom()` which has improved logging to help with debugging Module code. Replaced all `.Single()` usages where applicable
* Replaced most method info fetching with `AccessTools` for consistency and better readability, especially in places where methods were being retrieved by their name anyways

**NOTE:**
As a side effect of publicizing all properties, some property access code such as `Player.Position` will now show "ambiguous reference" errors during compile, due to there being multiple interfaces with the Property name being defined on the class. The way to get around this is to use a cast to an explicit interface
Example:
```cs
Singleton<GameWorld>.Instance.MainPlayer.Position
```
will now need to be
```cs
((IPlayer)Singleton<GameWorld>.Instance.MainPlayer).Position
```

Co-authored-by: Terkoiz <terkoiz@spt.dev>
Reviewed-on: SPT-AKI/Modules#58
Co-authored-by: Terkoiz <terkoiz@noreply.dev.sp-tarkov.com>
Co-committed-by: Terkoiz <terkoiz@noreply.dev.sp-tarkov.com>
This commit is contained in:
Terkoiz 2024-01-13 22:08:29 +00:00 committed by chomp
parent bbf55a7031
commit 337a0733ae
78 changed files with 330 additions and 596 deletions

View File

@ -1,9 +1,8 @@
using Aki.Core.Utils;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using HarmonyLib;
namespace Aki.Core.Patches
{
@ -11,11 +10,7 @@ namespace Aki.Core.Patches
{
protected override MethodBase GetTargetMethod()
{
var methodName = "RunValidation";
var flags = BindingFlags.Public | BindingFlags.Instance;
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
.GetMethod(methodName, flags);
return AccessTools.Method(typeof(BattleeyePatchClass), nameof(BattleeyePatchClass.RunValidation));
}
[PatchPrefix]

View File

@ -12,8 +12,8 @@ namespace Aki.Core.Patches
{
protected override MethodBase GetTargetMethod()
{
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
.GetMethods().Single(x => x.Name == "EnsureConsistency" && x.ReturnType == typeof(Task<ICheckResult>));
return PatchConstants.FilesCheckerTypes.SingleCustom(x => x.Name == "ConsistencyController")
.GetMethods().SingleCustom(x => x.Name == "EnsureConsistency" && x.ReturnType == typeof(Task<ICheckResult>));
}
[PatchPrefix]

View File

@ -12,8 +12,8 @@ namespace Aki.Core.Patches
{
protected override MethodBase GetTargetMethod()
{
return PatchConstants.FilesCheckerTypes.Single(x => x.Name == "ConsistencyController")
.GetMethods().Single(x => x.Name == "EnsureConsistencySingle" && x.ReturnType == typeof(Task<ICheckResult>));
return PatchConstants.FilesCheckerTypes.SingleCustom(x => x.Name == "ConsistencyController")
.GetMethods().SingleCustom(x => x.Name == "EnsureConsistencySingle" && x.ReturnType == typeof(Task<ICheckResult>));
}
[PatchPrefix]

View File

@ -1,30 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using Aki.Core.Models;
using System.Threading.Tasks;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using FilesChecker;
using HarmonyLib;
using System;
namespace Aki.Core.Patches
{
public class DataHandlerDebugPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return PatchConstants.EftTypes
.Single(t => t.Name == "DataHandler")
.GetMethod("method_5", BindingFlags.Instance | BindingFlags.NonPublic);
}
[PatchPostfix]
private static void PatchPrefix(ref string __result)
{
Console.WriteLine($"response json: ${__result}");
}
}
}

View File

@ -1,25 +1,23 @@
using System.Linq;
using System.Reflection;
using UnityEngine.Networking;
using System.Security.Cryptography.X509Certificates;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Aki.Core.Utils;
using HarmonyLib;
namespace Aki.Core.Patches
{
public class SslCertificatePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return PatchConstants.EftTypes.Single(x => x.BaseType == typeof(CertificateHandler))
.GetMethod("ValidateCertificate", PatchConstants.PrivateFlags);
{
return AccessTools.Method(typeof(SslCertPatchClass), nameof(SslCertPatchClass.ValidateCertificate), new[] { typeof(X509Certificate) });
}
[PatchPrefix]
private static bool PatchPrefix(ref bool __result)
{
__result = ValidationUtil.Validate();
return false; // Skip origial
return false; // Skip original
}
}
}

View File

@ -35,18 +35,17 @@ namespace Aki.Core.Patches
protected override MethodBase GetTargetMethod()
{
return PatchConstants.EftTypes.Single(t => t.GetMethods().Any(m => m.Name == "CreateFromLegacyParams"))
return PatchConstants.EftTypes.SingleCustom(t => t.GetMethods().Any(m => m.Name == "CreateFromLegacyParams"))
.GetMethod("CreateFromLegacyParams", BindingFlags.Static | BindingFlags.Public);
}
[PatchPrefix]
private static bool PatchPrefix(ref LegacyParamsStruct legacyParams)
{
//Console.WriteLine($"Original url {legacyParams.Url}");
legacyParams.Url = legacyParams.Url
.Replace("https://", "")
.Replace("http://", "");
//Console.WriteLine($"Edited url {legacyParams.Url}");
return true; // do original method after
}

View File

@ -1,7 +1,6 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using System;
using System.Linq;
using System.Reflection;
namespace Aki.Core.Patches
@ -10,15 +9,18 @@ namespace Aki.Core.Patches
{
protected override MethodBase GetTargetMethod()
{
var targetInterface = PatchConstants.EftTypes.Single(x => x == typeof(IConnectionHandler) && x.IsInterface);
var typeThatMatches = PatchConstants.EftTypes.Single(x => targetInterface.IsAssignableFrom(x) && x.IsAbstract && !x.IsInterface);
return typeThatMatches.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(x => x.ReturnType == typeof(Uri));
var targetInterface = PatchConstants.EftTypes.SingleCustom(x => x == typeof(IConnectionHandler) && x.IsInterface);
var typeThatMatches = PatchConstants.EftTypes.SingleCustom(x => targetInterface.IsAssignableFrom(x) && x.IsAbstract && !x.IsInterface);
return typeThatMatches.GetMethods(BindingFlags.Public | BindingFlags.Instance).SingleCustom(x => x.ReturnType == typeof(Uri));
}
// This is a pass through postfix and behaves a little differently than usual
// https://harmony.pardeike.net/articles/patching-postfix.html#pass-through-postfixes
[PatchPostfix]
private static Uri PatchPostfix(Uri __instance)
private static Uri PatchPostfix(Uri __result)
{
return new Uri(__instance.ToString().Replace("wss:", "ws:"));
return new Uri(__result.ToString().Replace("wss:", "ws:"));
}
}
}

View File

@ -3,7 +3,6 @@ using Aki.Common;
using Aki.Custom.Airdrops.Patches;
using Aki.Custom.Patches;
using Aki.Custom.Utils;
using Aki.SinglePlayer.Patches.ScavMode;
using BepInEx;
namespace Aki.Custom
@ -29,7 +28,7 @@ namespace Aki.Custom
// Fixed in live, no need for patch
//new RaidSettingsWindowPatch().Enable();
new OfflineRaidSettingsMenuPatch().Enable();
new SessionIdPatch().Enable();
// new SessionIdPatch().Enable();
new VersionLabelPatch().Enable();
new IsEnemyPatch().Enable();
new LocationLootCacheBustingPatch().Enable();

View File

@ -1,34 +0,0 @@
using Aki.Reflection.Patching;
using EFT;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Aki.Custom.Patches
{
/// <summary>
/// If a bot being added has an ID found in list_1, it means its trying to add itself to its enemy list
/// Dont add bot to enemy list if its in list_1 and skip the rest of the AddEnemy() function
/// </summary>
public class AddSelfAsEnemyPatch : ModulePatch
{
private static readonly string methodName = "AddEnemy";
protected override MethodBase GetTargetMethod()
{
return typeof(BotZoneGroupsDictionary).GetMethod(methodName);
}
[PatchPrefix]
private static bool PatchPrefix(BotZoneGroupsDictionary __instance, IPlayer person)
{
var botOwners = (List<BotOwner>)__instance.GetType().GetField("list_1", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance);
if (botOwners.Any(x => x.Id == person.Id))
{
return false;
}
return true;
}
}
}

View File

@ -1,38 +1,15 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
public class AddEnemyToAllGroupsInBotZonePatch : ModulePatch
{
private static Type _targetType;
private const string methodName = "AddEnemyToAllGroupsInBotZone";
public AddEnemyToAllGroupsInBotZonePatch()
{
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
}
private bool IsTargetType(Type type)
{
if (type.Name == nameof(BotsController) && type.GetMethod(methodName) != null)
{
return true;
}
return false;
}
protected override MethodBase GetTargetMethod()
{
Logger.LogDebug($"{this.GetType().Name} Type: {_targetType.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {methodName}");
return _targetType.GetMethod(methodName);
return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone));
}
/// <summary>

View File

@ -3,6 +3,7 @@ using System.Reflection;
using Aki.PrePatch;
using Aki.Reflection.Patching;
using EFT;
using HarmonyLib;
namespace Aki.Custom.Patches
{
@ -10,7 +11,7 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
return typeof(BotSettingsRepoClass).GetMethod("Init");
return AccessTools.Method(typeof(BotSettingsRepoClass), nameof(BotSettingsRepoClass.Init));
}
[PatchPrefix]

View File

@ -16,11 +16,11 @@ namespace Aki.Custom.Patches
{
var desiredType = PatchConstants.LocalGameType;
var desiredMethod = desiredType
.GetMethods(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.DeclaredOnly)
.SingleOrDefault(m => IsTargetMethod(m));
.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)
.SingleOrDefault(IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name ?? "NOT FOUND"}");
return desiredMethod;
}

View File

@ -3,7 +3,6 @@ using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using EFT.UI;
using System.Linq;
using System.Reflection;
namespace Aki.Custom.Patches
@ -15,7 +14,7 @@ namespace Aki.Custom.Patches
var methodName = "LoadDifficultyStringInternal";
var flags = BindingFlags.Public | BindingFlags.Static;
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
return PatchConstants.EftTypes.SingleCustom(x => x.GetMethod(methodName, flags) != null)
.GetMethod(methodName, flags);
}

View File

@ -1,36 +1,15 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
public class BotEnemyTargetPatch : ModulePatch
{
private static Type _targetType;
private static readonly string methodName = "AddEnemyToAllGroupsInBotZone";
public BotEnemyTargetPatch()
{
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
}
private bool IsTargetType(Type type)
{
if (type.Name == nameof(BotsController) && type.GetMethod(methodName) != null)
{
Logger.LogInfo($"{methodName}: {type.FullName}");
return true;
}
return false;
}
protected override MethodBase GetTargetMethod()
{
return _targetType.GetMethod(methodName);
return AccessTools.Method(typeof(BotsController), nameof(BotsController.AddEnemyToAllGroupsInBotZone));
}
/// <summary>

View File

@ -1,6 +1,7 @@
using Aki.Reflection.Patching;
using EFT;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
@ -9,11 +10,9 @@ namespace Aki.Custom.Patches
/// </summary>
internal class BotSelfEnemyPatch : ModulePatch
{
private static readonly string methodName = "PreActivate";
protected override MethodBase GetTargetMethod()
{
return typeof(BotOwner).GetMethod(methodName);
return AccessTools.Method(typeof(BotOwner), nameof(BotOwner.PreActivate));
}
[PatchPrefix]

View File

@ -1,38 +1,15 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
public class CheckAndAddEnemyPatch : ModulePatch
{
private static Type _targetType;
private readonly string _targetMethodName = "CheckAndAddEnemy";
/// <summary>
/// BotGroupClass.CheckAndAddEnemy()
/// </summary>
public CheckAndAddEnemyPatch()
{
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
}
private bool IsTargetType(Type type)
{
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
{
return true;
}
return false;
}
protected override MethodBase GetTargetMethod()
{
return _targetType.GetMethod(_targetMethodName);
return AccessTools.Method(typeof(BotsGroup), nameof(BotsGroup.CheckAndAddEnemy));
}
/// <summary>

View File

@ -1,7 +1,6 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Aki.Common.Http;
using System.Linq;
using System.Reflection;
namespace Aki.Custom.Patches
@ -13,7 +12,7 @@ namespace Aki.Custom.Patches
var methodName = "LoadCoreByString";
var flags = BindingFlags.Public | BindingFlags.Static;
return PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null)
return PatchConstants.EftTypes.SingleCustom(x => x.GetMethod(methodName, flags) != null)
.GetMethod(methodName, flags);
}

View File

@ -5,6 +5,7 @@ using Comfort.Common;
using System.Reflection;
using Aki.PrePatch;
using Aki.Custom.CustomAI;
using HarmonyLib;
namespace Aki.Custom.Patches
{
@ -15,7 +16,7 @@ namespace Aki.Custom.Patches
protected override MethodBase GetTargetMethod()
{
return typeof(StandartBotBrain).GetMethod("Activate", BindingFlags.Public | BindingFlags.Instance);
return AccessTools.Method(typeof(StandartBotBrain), nameof(StandartBotBrain.Activate));
}
/// <summary>

View File

@ -1,5 +1,4 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Diz.Jobs;
using Diz.Resources;
using JetBrains.Annotations;
@ -15,24 +14,17 @@ using System.Threading.Tasks;
using Aki.Custom.Models;
using Aki.Custom.Utils;
using DependencyGraph = DependencyGraph<IEasyBundle>;
using Aki.Reflection.Utils;
namespace Aki.Custom.Patches
{
public class EasyAssetsPatch : ModulePatch
{
private static readonly FieldInfo _manifestField;
private static readonly FieldInfo _bundlesField;
private static readonly PropertyInfo _systemProperty;
static EasyAssetsPatch()
{
var type = typeof(EasyAssets);
_manifestField = type.GetField(nameof(EasyAssets.Manifest));
_bundlesField = type.GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
// DependencyGraph<IEasyBundle>
_systemProperty = type.GetProperty("System");
_bundlesField = typeof(EasyAssets).GetField($"{EasyBundleHelper.Type.Name.ToLowerInvariant()}_0", PatchConstants.PrivateFlags);
}
public EasyAssetsPatch()
@ -45,7 +37,7 @@ namespace Aki.Custom.Patches
protected override MethodBase GetTargetMethod()
{
return typeof(EasyAssets).GetMethods(PatchConstants.PrivateFlags).Single(IsTargetMethod);
return typeof(EasyAssets).GetMethods(PatchConstants.PublicDeclaredFlags).SingleCustom(IsTargetMethod);
}
private static bool IsTargetMethod(MethodInfo mi)
@ -97,9 +89,9 @@ namespace Aki.Custom.Patches
await JobScheduler.Yield(EJobPriority.Immediate);
}
_manifestField.SetValue(instance, manifest);
instance.Manifest = manifest;
_bundlesField.SetValue(instance, bundles);
_systemProperty.SetValue(instance, new DependencyGraph(bundles, defaultKey, shouldExclude));
instance.System = new DependencyGraph(bundles, defaultKey, shouldExclude);
}
private static async Task<CompatibilityAssetBundleManifest> GetManifestBundle(string filepath)

View File

@ -1,4 +1,5 @@
using Aki.Reflection.Patching;
using System;
using Aki.Reflection.Patching;
using Diz.DependencyManager;
using UnityEngine.Build.Pipeline;
using System.IO;
@ -27,7 +28,7 @@ namespace Aki.Custom.Patches
private static void PatchPostfix(object __instance, string key, string rootPath, CompatibilityAssetBundleManifest manifest, IBundleLock bundleLock)
{
var path = rootPath + key;
var dependencyKeys = manifest.GetDirectDependencies(key) ?? new string[0];
var dependencyKeys = manifest.GetDirectDependencies(key) ?? Array.Empty<string>();
if (BundleManager.Bundles.TryGetValue(key, out BundleInfo bundle))
{

View File

@ -1,9 +1,8 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Comfort.Common;
using EFT;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
@ -14,8 +13,7 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
return PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType // BaseLocalGame
.GetMethod("Stop", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
}
// Look at BaseLocalGame<TPlayerOwner> and find a method named "Stop"

View File

@ -1,35 +1,16 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
public class IsEnemyPatch : ModulePatch
{
private static Type _targetType;
private readonly string _targetMethodName = "IsEnemy";
public IsEnemyPatch()
{
_targetType = PatchConstants.EftTypes.Single(IsTargetType);
}
private bool IsTargetType(Type type)
{
if (type.GetMethod("AddEnemy") != null && type.GetMethod("AddEnemyGroupIfAllowed") != null)
{
return true;
}
return false;
}
protected override MethodBase GetTargetMethod()
{
return _targetType.GetMethod(_targetMethodName);
return AccessTools.Method(typeof(BotsGroup), nameof(BotsGroup.IsEnemy));
}
/// <summary>

View File

@ -1,8 +1,7 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using System;
using System.Linq;
using System.Reflection;
using EFT;
namespace Aki.Custom.Patches
{
@ -13,8 +12,8 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "LocalGame").BaseType; // BaseLocalGame
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).Single(x => IsTargetMethod(x)); // method_6
var desiredType = typeof(BaseLocalGame<GamePlayerOwner>);
var desiredMethod = desiredType.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public).SingleCustom(IsTargetMethod); // method_6
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");

View File

@ -7,6 +7,7 @@ using EFT.UI.Matchmaker;
using System.Reflection;
using EFT;
using HarmonyLib;
using Aki.Reflection.Utils;
namespace Aki.Custom.Patches
{
@ -14,13 +15,8 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(MatchmakerOfflineRaidScreen);
var desiredMethod = desiredType.GetMethod(nameof(MatchmakerOfflineRaidScreen.Show));
Logger.LogDebug($"{GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.GetDeclaredMethods(typeof(MatchmakerOfflineRaidScreen))
.SingleCustom(m => m.Name == nameof(MatchmakerOfflineRaidScreen.Show) && m.GetParameters().Length == 1);
}
[PatchPrefix]

View File

@ -2,23 +2,15 @@
using Aki.Reflection.Patching;
using EFT.UI;
using EFT.UI.Matchmaker;
using HarmonyLib;
namespace Aki.Custom.Patches
{
public class OfflineRaidSettingsMenuPatch : ModulePatch
{
/// <summary>
/// RaidSettingsWindow.Show()
/// </summary>
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(RaidSettingsWindow);
var desiredMethod = desiredType.GetMethod(nameof(RaidSettingsWindow.Show));
Logger.LogDebug($"{GetType().Name} Type: {desiredType.Name}");
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(RaidSettingsWindow), nameof(RaidSettingsWindow.Show));
}
[PatchPostfix]

View File

@ -23,7 +23,7 @@ namespace Aki.Custom.Patches
protected override MethodBase GetTargetMethod()
{
return _targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic);
return _targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public);
}
/// <summary>

View File

@ -2,14 +2,16 @@
using Aki.Reflection.Patching;
using System.Reflection;
using EFT;
using Aki.Reflection.Utils;
using System.Linq;
using HarmonyLib;
namespace Aki.Custom.Patches
{
public class QTEPatch : ModulePatch
{
protected override MethodBase GetTargetMethod() => typeof(HideoutPlayerOwner).GetMethod(nameof(HideoutPlayerOwner.StopWorkout));
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(HideoutPlayerOwner), nameof(HideoutPlayerOwner.StopWorkout));
}
[PatchPostfix]
private static void PatchPostfix(HideoutPlayerOwner __instance)

View File

@ -1,9 +1,9 @@
using Aki.Common.Http;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.InventoryLogic;
using EFT.UI.Ragfair;
using System.Reflection;
using HarmonyLib;
using UnityEngine;
namespace Aki.Custom.Patches
@ -23,7 +23,7 @@ namespace Aki.Custom.Patches
protected override MethodBase GetTargetMethod()
{
return typeof(AddOfferWindow).GetMethod("method_1", PatchConstants.PrivateFlags);
return AccessTools.Method(typeof(AddOfferWindow), nameof(AddOfferWindow.method_1));
}
/// <summary>
@ -42,8 +42,7 @@ namespace Aki.Custom.Patches
tpl = ___item_0.TemplateId,
count = ___gclass3067_0.OfferItemCount,
fee = Mathf.CeilToInt((float)GClass2084.CalculateTaxPrice(___item_0, ___gclass3067_0.OfferItemCount, ___double_0, ___bool_0))
}
.ToJson());
}.ToJson());
}
}
}

View File

@ -5,6 +5,7 @@ using Aki.Custom.Models;
using EFT.UI;
using EFT.UI.Matchmaker;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
@ -14,15 +15,12 @@ namespace Aki.Custom.Patches
/// </summary>
public class RaidSettingsWindowPatch : ModulePatch
{
/// <summary>
/// Target method should have ~20 .UpdateValue() calls in it
/// </summary>
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(RaidSettingsWindow);
var desiredMethod = desiredType.GetMethod("method_8", BindingFlags.NonPublic | BindingFlags.Instance);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(RaidSettingsWindow), nameof(RaidSettingsWindow.method_8));
}
[PatchPrefix]

View File

@ -1,9 +1,9 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Comfort.Common;
using EFT;
using EFT.UI;
using System.Reflection;
using HarmonyLib;
namespace Aki.Custom.Patches
{
@ -11,13 +11,7 @@ namespace Aki.Custom.Patches
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(RankPanel);
var desiredMethod = desiredType.GetMethod("Show", PatchConstants.PublicFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(RankPanel), nameof(RankPanel.Show));
}
[PatchPrefix]

View File

@ -1,10 +1,11 @@
using Aki.Reflection.Patching;
using System.Linq;
using System.Reflection;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.UI.Matchmaker;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.ScavMode
namespace Aki.Custom.Patches
{
/// <summary>
/// Copy over scav-only quests from PMC profile to scav profile on pre-raid screen
@ -14,13 +15,8 @@ namespace Aki.SinglePlayer.Patches.ScavMode
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(MatchmakerOfflineRaidScreen);
var desiredMethod = desiredType.GetMethod(nameof(MatchmakerOfflineRaidScreen.Show));
Logger.LogDebug($"{GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.GetDeclaredMethods(typeof(MatchmakerOfflineRaidScreen))
.SingleCustom(m => m.Name == nameof(MatchmakerOfflineRaidScreen.Show) && m.GetParameters().Length == 1);
}
[PatchPostfix]

View File

@ -1,8 +1,9 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.UI;
using System.IO;
using System.Reflection;
using EFT;
using HarmonyLib;
using UnityEngine;
namespace Aki.Custom.Patches
@ -11,15 +12,10 @@ namespace Aki.Custom.Patches
{
private static PreloaderUI _preloader;
static SessionIdPatch()
{
_preloader = null;
}
protected override MethodBase GetTargetMethod()
{
return PatchConstants.LocalGameType.BaseType.GetMethod("method_5", PatchConstants.PrivateFlags);
}
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.method_5));
}
[PatchPostfix]
private static void PatchPostfix()

View File

@ -22,10 +22,11 @@ namespace Aki.Custom.Patches
Type localGameBaseType = PatchConstants.LocalGameType.BaseType;
// At this point, gameWorld.MainPlayer isn't set, so we need to use the LocalGame's `Location_0` property
_locationProperty = localGameBaseType.GetProperties(PatchConstants.PrivateFlags).Single(x => x.PropertyType == typeof(Location));
_locationProperty = localGameBaseType.GetProperties(PatchConstants.PublicDeclaredFlags)
.SingleCustom(x => x.PropertyType == typeof(Location));
// Find the TimeAndWeatherSettings handling method
var desiredMethod = localGameBaseType.GetMethods(PatchConstants.PrivateFlags).SingleOrDefault(m => IsTargetMethod(m));
var desiredMethod = localGameBaseType.GetMethods(PatchConstants.PublicDeclaredFlags).SingleOrDefault(IsTargetMethod);
Logger.LogDebug($"{GetType().Name} Type: {localGameBaseType?.Name}");
Logger.LogDebug($"{GetType().Name} Method: {desiredMethod?.Name}");
@ -52,6 +53,13 @@ namespace Aki.Custom.Patches
}
Location location = _locationProperty.GetValue(__instance) as Location;
if (location == null)
{
Logger.LogError($"[SetLocationId] Failed to get location data");
return;
}
gameWorld.LocationId = location.Id;
Logger.LogDebug($"[SetLocationId] Set locationId to: {location.Id}");

View File

@ -5,7 +5,6 @@ using Aki.Reflection.Utils;
using Aki.Custom.Models;
using EFT.UI;
using HarmonyLib;
using System.Linq;
using System.Reflection;
using Comfort.Common;
@ -17,18 +16,9 @@ namespace Aki.Custom.Patches
protected override MethodBase GetTargetMethod()
{
try
{
return PatchConstants.EftTypes
.Single(x => x.GetField("Taxonomy", BindingFlags.Public | BindingFlags.Instance) != null)
return PatchConstants.EftTypes
.SingleCustom(x => x.GetField("Taxonomy", BindingFlags.Public | BindingFlags.Instance) != null)
.GetMethod("Create", BindingFlags.Public | BindingFlags.Static);
}
catch (System.Exception e)
{
Logger.LogInfo($"VersionLabelPatch failed {e.Message} {e.StackTrace} {e.InnerException.StackTrace}");
throw;
}
}
[PatchPostfix]
@ -45,7 +35,7 @@ namespace Aki.Custom.Patches
Traverse.Create(Singleton<PreloaderUI>.Instance).Field("string_2").SetValue(_versionLabel);
var major = Traverse.Create(__result).Field("Major");
var existingValue = major.GetValue();
major.SetValue($"{existingValue} {_versionLabel}" );
major.SetValue($"{existingValue} {_versionLabel}");
}
}
}

View File

@ -11,7 +11,7 @@ namespace Aki.Custom.Utils
{
public class EasyBundleHelper
{
private const BindingFlags _NonPublicInstanceflags = BindingFlags.Instance | BindingFlags.NonPublic;
private const BindingFlags NonPublicInstanceFlags = BindingFlags.Instance | BindingFlags.NonPublic;
private static readonly FieldInfo _pathField;
private static readonly FieldInfo _keyWithoutExtensionField;
private static readonly FieldInfo _bundleLockField;
@ -27,24 +27,28 @@ namespace Aki.Custom.Utils
_ = nameof(IBundleLock.IsLocked);
_ = nameof(BindableState.Bind);
// Class can be found as a private array inside EasyAssets.cs, next to DependencyGraph<IEasyBundle>
Type = PatchConstants.EftTypes.Single(x => x.GetMethod("set_SameNameAsset", _NonPublicInstanceflags) != null);
Type = PatchConstants.EftTypes.SingleCustom(x => !x.IsInterface && x.GetProperty("SameNameAsset", PatchConstants.PublicDeclaredFlags) != null);
_pathField = Type.GetField("string_1", _NonPublicInstanceflags);
_keyWithoutExtensionField = Type.GetField("string_0", _NonPublicInstanceflags);
_bundleLockField = Type.GetFields(_NonPublicInstanceflags).FirstOrDefault(x => x.FieldType == typeof(IBundleLock));
_pathField = Type.GetField("string_1", NonPublicInstanceFlags);
_keyWithoutExtensionField = Type.GetField("string_0", NonPublicInstanceFlags);
_bundleLockField = Type.GetFields(NonPublicInstanceFlags).FirstOrDefault(x => x.FieldType == typeof(IBundleLock));
_dependencyKeysProperty = Type.GetProperty("DependencyKeys");
_keyProperty = Type.GetProperty("Key");
_loadStateProperty = Type.GetProperty("LoadState");
// Function with 0 params and returns task (usually method_0())
var possibleMethods = Type.GetMethods(_NonPublicInstanceflags).Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task));
if (possibleMethods.Count() > 1)
var possibleMethods = Type.GetMethods(PatchConstants.PublicDeclaredFlags).Where(x => x.GetParameters().Length == 0 && x.ReturnType == typeof(Task)).ToArray();
if (possibleMethods.Length > 1)
{
Console.WriteLine($"Unable to find desired method as there are multiple possible matches: {string.Join(",", possibleMethods.Select(x => x.Name))}");
throw new Exception($"Unable to find the Loading Coroutine method as there are multiple possible matches: {string.Join(",", possibleMethods.Select(x => x.Name))}");
}
_loadingCoroutineMethod = possibleMethods.SingleOrDefault();
if (possibleMethods.Length == 0)
{
throw new Exception("Unable to find the Loading Coroutine method as there are no matches");
}
_loadingCoroutineMethod = possibleMethods.Single();
}
public EasyBundleHelper(object easyBundle)

View File

@ -1,51 +1,47 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using TMPro;
using UnityEngine;
using System;
using System.Reflection;
using HarmonyLib;
namespace Aki.Debugging.Patches
{
public class CoordinatesPatch : ModulePatch
{
private static TextMeshProUGUI _alphaLabel;
private static PropertyInfo _playerProperty;
protected override MethodBase GetTargetMethod()
{
var localGameBaseType = PatchConstants.LocalGameType.BaseType;
_playerProperty = localGameBaseType.GetProperty("PlayerOwner", BindingFlags.Public | BindingFlags.Instance);
return localGameBaseType.GetMethod("Update", PatchConstants.PrivateFlags);
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Update));
}
[PatchPrefix]
private static void PatchPrefix(object __instance)
private static void PatchPrefix(BaseLocalGame<GamePlayerOwner> __instance)
{
if (Input.GetKeyDown(KeyCode.LeftControl))
if (!Input.GetKeyDown(KeyCode.LeftControl)) return;
if (_alphaLabel == null)
{
if (_alphaLabel == null)
{
_alphaLabel = GameObject.Find("AlphaLabel").GetComponent<TextMeshProUGUI>();
_alphaLabel.color = Color.green;
_alphaLabel.fontSize = 22;
_alphaLabel.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/ARIAL SDF");
}
var playerOwner = (GamePlayerOwner)_playerProperty.GetValue(__instance);
var aiming = LookingRaycast(playerOwner.Player);
if (_alphaLabel != null)
{
_alphaLabel.text = $"Looking at: [{aiming.x}, {aiming.y}, {aiming.z}]";
Logger.LogInfo(_alphaLabel.text);
}
var position = playerOwner.transform.position;
var rotation = playerOwner.transform.rotation.eulerAngles;
Logger.LogInfo($"Character position: [{position.x},{position.y},{position.z}] | Rotation: [{rotation.x},{rotation.y},{rotation.z}]");
_alphaLabel = GameObject.Find("AlphaLabel").GetComponent<TextMeshProUGUI>();
_alphaLabel.color = Color.green;
_alphaLabel.fontSize = 22;
_alphaLabel.font = Resources.Load<TMP_FontAsset>("Fonts & Materials/ARIAL SDF");
}
var playerOwner = __instance.PlayerOwner;
var aiming = LookingRaycast(playerOwner.Player);
if (_alphaLabel != null)
{
_alphaLabel.text = $"Looking at: [{aiming.x}, {aiming.y}, {aiming.z}]";
Logger.LogInfo(_alphaLabel.text);
}
var position = playerOwner.transform.position;
var rotation = playerOwner.transform.rotation.eulerAngles;
Logger.LogInfo($"Character position: [{position.x},{position.y},{position.z}] | Rotation: [{rotation.x},{rotation.y},{rotation.z}]");
}
public static Vector3 LookingRaycast(Player player)

View File

@ -0,0 +1,21 @@
using System;
using System.Reflection;
using Aki.Reflection.Patching;
using HarmonyLib;
namespace Aki.Debugging.Patches
{
public class DataHandlerDebugPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return AccessTools.Method(typeof(DataHandler), nameof(DataHandler.method_5));
}
[PatchPostfix]
private static void PatchPrefix(ref string __result)
{
Console.WriteLine($"response json: ${__result}");
}
}
}

View File

@ -1,9 +1,9 @@
using Aki.Reflection.Patching;
using System.Reflection;
using Aki.Reflection.Utils;
using BepInEx.Logging;
using EFT;
using EFT.UI;
using HarmonyLib;
using TMPro;
namespace Aki.Debugging.Patches
@ -12,7 +12,7 @@ namespace Aki.Debugging.Patches
{
protected override MethodBase GetTargetMethod()
{
return typeof(TraderCard).GetMethod("method_0", PatchConstants.PrivateFlags);
return AccessTools.Method(typeof(TraderCard), nameof(TraderCard.method_0));
}
[PatchPrefix]

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Comfort.Common;
@ -11,6 +12,7 @@ namespace Aki.Reflection.Utils
{
public static BindingFlags PrivateFlags { get; private set; }
public static BindingFlags PublicFlags { get; private set; }
public static BindingFlags PublicDeclaredFlags { get; private set; }
public static Type[] EftTypes { get; private set; }
public static Type[] FilesCheckerTypes { get; private set; }
public static Type LocalGameType { get; private set; }
@ -36,14 +38,48 @@ namespace Aki.Reflection.Utils
{
_ = nameof(ISession.GetPhpSessionId);
PrivateFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
PrivateFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly;
PublicFlags = BindingFlags.Public | BindingFlags.Instance;
PublicDeclaredFlags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
EftTypes = typeof(AbstractGame).Assembly.GetTypes();
FilesCheckerTypes = typeof(ICheckResult).Assembly.GetTypes();
LocalGameType = EftTypes.Single(x => x.Name == "LocalGame");
ExfilPointManagerType = EftTypes.Single(x => x.GetMethod("InitAllExfiltrationPoints") != null);
SessionInterfaceType = EftTypes.Single(x => x.GetMethods().Select(y => y.Name).Contains("GetPhpSessionId") && x.IsInterface);
BackendSessionInterfaceType = EftTypes.Single(x => x.GetMethods().Select(y => y.Name).Contains("ChangeProfileStatus") && x.IsInterface);
LocalGameType = EftTypes.SingleCustom(x => x.Name == "LocalGame");
ExfilPointManagerType = EftTypes.SingleCustom(x => x.GetMethod("InitAllExfiltrationPoints") != null);
SessionInterfaceType = EftTypes.SingleCustom(x => x.GetMethods().Select(y => y.Name).Contains("GetPhpSessionId") && x.IsInterface);
BackendSessionInterfaceType = EftTypes.SingleCustom(x => x.GetMethods().Select(y => y.Name).Contains("ChangeProfileStatus") && x.IsInterface);
}
/// <summary>
/// A custom LINQ .Single() implementation with improved logging for easier patch debugging
/// </summary>
/// <returns>A single member of the input sequence that matches the given search pattern</returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="InvalidOperationException"></exception>
public static T SingleCustom<T>(this IEnumerable<T> types, Func<T, bool> predicate) where T : MemberInfo
{
if (types == null)
{
throw new ArgumentNullException(nameof(types));
}
if (predicate == null)
{
throw new ArgumentNullException(nameof(predicate));
}
var matchingTypes = types.Where(predicate).ToArray();
if (matchingTypes.Length > 1)
{
throw new InvalidOperationException($"More than one member matches the specified search pattern: {string.Join(", ", matchingTypes.Select(t => t.Name))}");
}
if (matchingTypes.Length == 0)
{
throw new InvalidOperationException("No members match the specified search pattern");
}
return matchingTypes[0];
}
}
}

View File

@ -4,24 +4,25 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Healing
{
/// <summary>
/// HealthController used by post-raid heal screen and health listenen class are different, this patch
/// HealthController used by post-raid heal screen and health listener class are different, this patch
/// ensures effects (fracture/bleeding) on body parts stay in sync
/// </summary>
public class HealthControllerPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return typeof(HealthControllerClass).GetMethod("ApplyTreatment", BindingFlags.Public | BindingFlags.Instance);
return AccessTools.Method(typeof(HealthControllerClass), nameof(HealthControllerClass.ApplyTreatment));
}
[PatchPrefix]
private static void PatchPrefix(object healthObserver)
{
var property = healthObserver.GetType().GetProperty("Effects");
var property = AccessTools.Property(healthObserver.GetType(), "Effects");
if (property != null)
{
var effects = property.GetValue(healthObserver);

View File

@ -1,7 +1,7 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.HealthSystem;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Healing
{
@ -15,13 +15,7 @@ namespace Aki.SinglePlayer.Patches.Healing
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(MainMenuController);
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_1));
}
[PatchPostfix]

View File

@ -1,9 +1,9 @@
using System;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System.Reflection;
using System.Threading.Tasks;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Healing
{
@ -11,13 +11,7 @@ namespace Aki.SinglePlayer.Patches.Healing
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(Player);
var desiredMethod = desiredType.GetMethod("Init", PatchConstants.PrivateFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(Player), nameof(Player.Init));
}
[PatchPostfix]

View File

@ -17,10 +17,8 @@ namespace Aki.SinglePlayer.Patches.Healing
{
// Class1049.smethod_0 as of 18969
//internal static Class1049 smethod_0(GInterface29 backend, string profileId, Profile savageProfile, LocationSettingsClass.GClass1097 location, ExitStatus exitStatus, TimeSpan exitTime, ERaidMode raidMode)
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "PostRaidHealthScreenClass");
var desiredMethod = desiredType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Single(IsTargetMethod);
var desiredMethod = typeof(PostRaidHealthScreenClass).GetMethods(BindingFlags.Static | BindingFlags.Public).SingleCustom(IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
@ -40,10 +38,9 @@ namespace Aki.SinglePlayer.Patches.Healing
}
[PatchPrefix]
private static bool PatchPrefix(TarkovApplication __instance, ref ERaidMode raidMode)
private static bool PatchPrefix(ref ERaidMode raidMode)
{
raidMode = ERaidMode.Online;
return true; // Perform original method
}
}

View File

@ -1,6 +1,7 @@
using Aki.Reflection.Patching;
using EFT;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.MainMenu
{
@ -17,7 +18,7 @@ namespace Aki.SinglePlayer.Patches.MainMenu
protected override MethodBase GetTargetMethod()
{
//[CompilerGenerated]
//private void method_67()
//private void method_XX()
//{
// if (this.raidSettings_0.SelectedLocation.Id == "laboratory")
// {
@ -31,13 +32,7 @@ namespace Aki.SinglePlayer.Patches.MainMenu
// this.method_41();
//}
var desiredType = typeof(MainMenuController);
var desiredMethod = desiredType.GetMethod("method_71", BindingFlags.NonPublic | BindingFlags.Instance);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_71));
}
[PatchPrefix]

View File

@ -1,20 +1,21 @@
using System.Reflection;
using System.Linq;
using System.Reflection;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.UI;
using EFT.UI.Matchmaker;
namespace Aki.SinglePlayer.Patches.MainMenu
{
/// <summary>
/// Removes the 'ready' button from the map preview screen - accessible by choosing map to deply to > clicking 'map' bottom left of screen
/// Clicking the ready button makes a call to client/match/available, something we dont want
/// Removes the 'ready' button from the map preview screen - accessible by choosing map to deploy to > clicking 'map' bottom left of screen
/// Clicking the ready button makes a call to client/match/available, something we don't want that
/// </summary>
public class MapReadyButtonPatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return typeof(MatchmakerMapPointsScreen).GetMethod("Show", PatchConstants.PrivateFlags);
// We don't really care which "Show" method is returned - either will do
return typeof(MatchmakerMapPointsScreen).GetMethods().First(m => m.Name == nameof(MatchmakerMapPointsScreen.Show));
}
[PatchPostfix]

View File

@ -1,10 +1,8 @@
using Aki.Common.Utils;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using BepInEx.Bootstrap;
using EFT.Communications;
using EFT.UI;
using HarmonyLib;
using System.Linq;
using System.Reflection;
using System.Text;
@ -19,16 +17,12 @@ namespace Aki.SinglePlayer.Patches.MainMenu
**/
internal class PluginErrorNotifierPatch : ModulePatch
{
private static MethodInfo _displayMessageNotificationMethod;
private static bool _messageShown = false;
protected override MethodBase GetTargetMethod()
{
_displayMessageNotificationMethod = AccessTools.Method(typeof(NotificationManagerClass), "DisplayMessageNotification");
var desiredType = typeof(MenuScreen);
var desiredMethod = desiredType.GetMethod("Show", PatchConstants.PrivateFlags);
return desiredMethod;
// We don't really care which "Show" method is returned - either will do
return typeof(MenuScreen).GetMethods().First(m => m.Name == nameof(MenuScreen.Show));
}
[PatchPostfix]
@ -45,7 +39,7 @@ namespace Aki.SinglePlayer.Patches.MainMenu
// Show a toast in the bottom right of the screen indicating how many plugins failed to load
var consoleHeaderMessage = $"{failedPluginCount} plugin{(failedPluginCount > 1 ? "s" : "")} failed to load due to errors";
var toastMessage = $"{consoleHeaderMessage}. Please check the console for details.";
_displayMessageNotificationMethod.Invoke(null, new object[] { toastMessage, ENotificationDurationType.Infinite, ENotificationIconType.Alert, Color.red });
NotificationManagerClass.DisplayMessageNotification(toastMessage, ENotificationDurationType.Infinite, ENotificationIconType.Alert, Color.red);
// Build the error message we'll put in the BepInEx and in-game consoles
var stringBuilder = new StringBuilder();

View File

@ -1,8 +1,8 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.UI;
using EFT.UI.Matchmaker;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.MainMenu
{
@ -13,13 +13,7 @@ namespace Aki.SinglePlayer.Patches.MainMenu
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(MatchMakerSelectionLocationScreen);
var desiredMethod = desiredType.GetMethod("Awake", PatchConstants.PrivateFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(MatchMakerSelectionLocationScreen), nameof(MatchMakerSelectionLocationScreen.Awake));
}
[PatchPostfix]

View File

@ -1,8 +1,8 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT.UI.SessionEnd;
using System.Linq;
using System.Reflection;
using EFT;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Progression
{
@ -10,23 +10,7 @@ namespace Aki.SinglePlayer.Patches.Progression
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(SessionResultExperienceCount);
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).FirstOrDefault(IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
}
private static bool IsTargetMethod(MethodInfo mi)
{
var parameters = mi.GetParameters();
return (parameters.Length == 3
&& parameters[0].Name == "profile"
&& parameters[1].Name == "isOnline"
&& parameters[2].Name == "exitStatus"
&& parameters[1].ParameterType == typeof(bool));
return AccessTools.Method(typeof(SessionResultExperienceCount), nameof(SessionResultExperienceCount.Show), new []{ typeof(Profile), typeof(bool), typeof(ExitStatus) });
}
[PatchPrefix]

View File

@ -3,6 +3,7 @@ using Aki.SinglePlayer.Models.Progression;
using Comfort.Common;
using EFT;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Progression
{
@ -10,7 +11,7 @@ namespace Aki.SinglePlayer.Patches.Progression
{
protected override MethodBase GetTargetMethod()
{
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
return AccessTools.Method(typeof(GameWorld), nameof(GameWorld.OnGameStarted));
}
[PatchPostfix]

View File

@ -2,6 +2,7 @@
using Comfort.Common;
using EFT;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Progression
{
@ -9,7 +10,7 @@ namespace Aki.SinglePlayer.Patches.Progression
{
protected override MethodBase GetTargetMethod()
{
return typeof(RadioTransmitterHandlerClass).GetMethod("method_4", BindingFlags.NonPublic | BindingFlags.Instance);
return AccessTools.Method(typeof(RadioTransmitterHandlerClass), nameof(RadioTransmitterHandlerClass.method_4));
}
[PatchPrefix]

View File

@ -8,13 +8,13 @@ using System.Reflection;
namespace Aki.SinglePlayer.Patches.Progression
{
/// <summary>
/// After picking up a quest item, trigger CheckForStatusChange() from the questController to fully update a quest subtasks to show (e.g. `survive and extract item from raid` task)
/// After picking up a quest item, trigger CheckForStatusChange() from the questController to fully update a quest sub-tasks to show (e.g. `survive and extract item from raid` task)
/// </summary>
public class MidRaidQuestChangePatch : ModulePatch
{
protected override MethodBase GetTargetMethod()
{
return typeof(Profile).GetMethod("AddToCarriedQuestItems", BindingFlags.Public | BindingFlags.Instance);
return AccessTools.Method(typeof(Profile), nameof(Profile.AddToCarriedQuestItems));
}
[PatchPostfix]
@ -23,7 +23,7 @@ namespace Aki.SinglePlayer.Patches.Progression
var gameWorld = Singleton<GameWorld>.Instance;
if (gameWorld == null)
{
Logger.LogDebug($"[MidRaidQuestChangePatch] gameWorld instance was null");
Logger.LogDebug("[MidRaidQuestChangePatch] gameWorld instance was null");
return;
}

View File

@ -14,7 +14,7 @@ using System.Reflection;
namespace Aki.SinglePlayer.Patches.Progression
{
/// <summary>
/// After a raid, the client doesnt update the server on what occurred during the raid. To persist loot/quest changes etc we
/// After a raid, the client doesn't update the server on what occurred during the raid. To persist loot/quest changes etc we
/// make the client send the active profile to a spt-specific endpoint `/raid/profile/save` where we can update the players profile json
/// </summary>
public class OfflineSaveProfilePatch : ModulePatch
@ -35,8 +35,8 @@ namespace Aki.SinglePlayer.Patches.Progression
{
// method_45 - as of 16432
// method_43 - as of 18876
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "TarkovApplication");
var desiredMethod = Array.Find(desiredType.GetMethods(PatchConstants.PrivateFlags), IsTargetMethod);
var desiredType = typeof(TarkovApplication);
var desiredMethod = Array.Find(desiredType.GetMethods(PatchConstants.PublicDeclaredFlags), IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
@ -66,7 +66,7 @@ namespace Aki.SinglePlayer.Patches.Progression
{
Exit = result.Value0.ToString().ToLowerInvariant(), // Exit player used to leave raid
Profile = profile, // players scav or pmc profile, depending on type of raid they did
Health = Utils.Healing.HealthListener.Instance.CurrentHealth, // Speicifc limb/effect damage data the player has at end of raid
Health = Utils.Healing.HealthListener.Instance.CurrentHealth, // Specific limb/effect damage data the player has at end of raid
Insurance = Utils.Insurance.InsuredItemManager.Instance.GetTrackedItems(), // A copy of items insured by player with durability values as of raid end (if item is returned, send it back with correct durability values)
IsPlayerScav = ____raidSettings.IsScav
};

View File

@ -18,9 +18,9 @@ namespace Aki.SinglePlayer.Patches.Progression
protected override MethodBase GetTargetMethod()
{
var desiredType = PatchConstants.EftTypes.First(IsTargetType);
var desiredType = typeof(SpawnSystemClass);
var desiredMethod = desiredType
.GetMethods(PatchConstants.PrivateFlags)
.GetMethods(PatchConstants.PublicDeclaredFlags)
.First(m => m.Name.Contains("SelectSpawnPoint"));
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
@ -34,7 +34,7 @@ namespace Aki.SinglePlayer.Patches.Progression
// GClass1812 as of 17349
// GClass1886 as of 18876
// Remapped to SpawnSystemClass
return (type.GetMethods(PatchConstants.PrivateFlags).Any(x => x.Name.IndexOf("CheckFarthestFromOtherPlayers", StringComparison.OrdinalIgnoreCase) != -1)
return (type.GetMethods(PatchConstants.PublicDeclaredFlags).Any(x => x.Name.IndexOf("CheckFarthestFromOtherPlayers", StringComparison.OrdinalIgnoreCase) != -1)
&& type.IsClass);
}
@ -48,7 +48,7 @@ namespace Aki.SinglePlayer.Patches.Progression
IPlayer person,
string infiltration)
{
var spawnPointsField = (ISpawnPoints)__instance.GetType().GetFields(PatchConstants.PrivateFlags).SingleOrDefault(f => f.FieldType == typeof(ISpawnPoints))?.GetValue(__instance);
var spawnPointsField = (ISpawnPoints)__instance.GetType().GetFields(PatchConstants.PublicDeclaredFlags).SingleOrDefault(f => f.FieldType == typeof(ISpawnPoints))?.GetValue(__instance);
if (spawnPointsField == null)
{
throw new Exception($"OfflineSpawnPointPatch: Failed to locate field: {nameof(ISpawnPoints)} on class instance: {__instance.GetType().Name}");

View File

@ -1,10 +1,10 @@
using System;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using EFT.Counters;
using EFT.UI.SessionEnd;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Progression
{
@ -19,15 +19,13 @@ namespace Aki.SinglePlayer.Patches.Progression
/// <returns></returns>
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(SessionResultExitStatus);
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).FirstOrDefault(IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(
typeof(SessionResultExitStatus),
nameof(SessionResultExitStatus.Show),
new []{ typeof(Profile), typeof(PlayerVisualRepresentation), typeof(ESideType), typeof(ExitStatus), typeof(TimeSpan), typeof(ISession), typeof(bool) });
}
// Unused, but left here in case patch breaks and finding the intended method is difficult
private static bool IsTargetMethod(MethodInfo mi)
{
var parameters = mi.GetParameters();

View File

@ -3,26 +3,21 @@ using EFT;
using EFT.InventoryLogic;
using System;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Quests
{
public class DogtagPatch : ModulePatch
{
private static BindingFlags _flags;
private static PropertyInfo _getEquipmentProperty;
static DogtagPatch()
{
_ = nameof(EquipmentClass.GetSlot);
_ = nameof(DamageInfo.Weapon);
_flags = BindingFlags.Instance | BindingFlags.NonPublic;
_getEquipmentProperty = typeof(Player).GetProperty("Equipment", _flags);
}
protected override MethodBase GetTargetMethod()
{
return typeof(Player).GetMethod("OnBeenKilledByAggressor", _flags);
return AccessTools.Method(typeof(Player), nameof(Player.OnBeenKilledByAggressor));
}
/// <summary>
@ -65,7 +60,7 @@ namespace Aki.SinglePlayer.Patches.Quests
private static Item GetDogTagItemFromPlayerWhoDied(Player __instance)
{
var equipment = (EquipmentClass)_getEquipmentProperty.GetValue(__instance);
var equipment = __instance.Equipment;
if (equipment == null)
{
Logger.LogError("DogtagPatch error > Player has no equipment");

View File

@ -1,9 +1,8 @@
using Aki.Common.Http;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System.Linq;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.Quests
{
@ -15,15 +14,10 @@ namespace Aki.SinglePlayer.Patches.Quests
{
protected override MethodBase GetTargetMethod()
{
var desiredType = PatchConstants.LocalGameType.BaseType;
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags).SingleOrDefault(IsStopRaidMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(BaseLocalGame<GamePlayerOwner>), nameof(BaseLocalGame<GamePlayerOwner>.Stop));
}
// Unused, but left here in case patch breaks and finding the intended method is difficult
private static bool IsStopRaidMethod(MethodInfo mi)
{
var parameters = mi.GetParameters();
@ -39,13 +33,13 @@ namespace Aki.SinglePlayer.Patches.Quests
}
[PatchPrefix]
private static bool PrefixPatch(object __instance, ref ExitStatus exitStatus, ref string exitName)
private static bool PrefixPatch(ref ExitStatus exitStatus, ref string exitName)
{
var isParsed = bool.TryParse(RequestHandler.GetJson("/singleplayer/settings/raid/endstate"), out bool MIAOnRaidEnd);
if (isParsed)
{
// No extract name and successful, its a MIA
if (MIAOnRaidEnd == true && string.IsNullOrEmpty(exitName?.Trim()) && exitStatus == ExitStatus.Survived)
if (MIAOnRaidEnd && string.IsNullOrEmpty(exitName?.Trim()) && exitStatus == ExitStatus.Survived)
{
exitStatus = ExitStatus.MissingInAction;
exitName = null;

View File

@ -11,8 +11,8 @@ namespace Aki.SinglePlayer.Patches.Quests
{
protected override MethodBase GetTargetMethod()
{
var desiredType = PatchConstants.EftTypes.Single(IsTargetType);
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
var desiredType = PatchConstants.EftTypes.SingleCustom(IsTargetType);
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PublicDeclaredFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
@ -22,7 +22,7 @@ namespace Aki.SinglePlayer.Patches.Quests
private static bool IsTargetType(Type type)
{
if (!typeof(IGetProfileData).IsAssignableFrom(type) || type.GetMethod("method_1", PatchConstants.PrivateFlags) == null)
if (!typeof(IGetProfileData).IsAssignableFrom(type) || type.GetMethod("method_1", PatchConstants.PublicDeclaredFlags) == null)
{
return false;
}
@ -32,7 +32,7 @@ namespace Aki.SinglePlayer.Patches.Quests
}
[PatchPrefix]
private static bool PatchPrefix(ref bool __result, object __instance, WildSpawnType ___wildSpawnType_0, BotDifficulty ___botDifficulty_0, Profile x)
private static bool PatchPrefix(ref bool __result, WildSpawnType ___wildSpawnType_0, BotDifficulty ___botDifficulty_0, Profile x)
{
if (x == null)
{

View File

@ -1,9 +1,9 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using Aki.Common.Http;
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.RaidFix
{
@ -17,13 +17,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(BotsPresets);
var desiredMethod = desiredType.GetMethod("method_1", PatchConstants.PrivateFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(BotsPresets), nameof(BotsPresets.method_1));
}
[PatchPostfix]

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Aki.Reflection.Patching;
@ -7,9 +6,7 @@ using Aki.Reflection.Utils;
using Comfort.Common;
using EFT;
using EFT.Game.Spawning;
using EFT.UI;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Aki.SinglePlayer.Patches.RaidFix
{
@ -23,8 +20,8 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
var desiredType = PatchConstants.LocalGameType.BaseType;
var desiredMethod = desiredType
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.CreateInstance)
.Single(IsTargetMethod);
.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.CreateInstance)
.SingleCustom(IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");

View File

@ -7,13 +7,12 @@ using Aki.Reflection.Utils;
using Aki.SinglePlayer.Models.RaidFix;
using System;
using System.Collections.Generic;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.RaidFix
{
public class GetNewBotTemplatesPatch : ModulePatch
{
private static MethodInfo _getNewProfileMethod;
static GetNewBotTemplatesPatch()
{
_ = nameof(IGetProfileData.PrepareToLoadBackend);
@ -22,29 +21,16 @@ namespace Aki.SinglePlayer.Patches.RaidFix
_ = nameof(JobPriority.General);
}
/// <summary>
/// BotsPresets.GetNewProfile()
/// </summary>
public GetNewBotTemplatesPatch()
{
var desiredType = typeof(BotsPresets);
_getNewProfileMethod = desiredType
.GetMethod(nameof(BotsPresets.GetNewProfile), BindingFlags.Instance | BindingFlags.NonPublic); // want the func with 2 params (protected + inherited from base)
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {_getNewProfileMethod?.Name}");
}
/// <summary>
/// Looking for CreateProfile()
/// </summary>
/// <returns></returns>
protected override MethodBase GetTargetMethod()
{
return typeof(BotsPresets).GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Single(x => IsTargetMethod(x));
return AccessTools.DeclaredMethod(typeof(BotsPresets), nameof(BotsPresets.CreateProfile));
}
// Unused, but left here in case patch breaks and finding the intended method is difficult
private bool IsTargetMethod(MethodInfo mi)
{
var parameters = mi.GetParameters();
@ -72,7 +58,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
try
{
// Force true to ensure bot profile is deleted after use
_getNewProfileMethod.Invoke(__instance, new object[] { data, true });
__instance.GetNewProfile(data, true);
}
catch (Exception e)
{

View File

@ -3,6 +3,7 @@ using EFT;
using System.Reflection;
using Aki.SinglePlayer.Utils.Insurance;
using Comfort.Common;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.RaidFix
{
@ -10,7 +11,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
return AccessTools.Method(typeof(GameWorld), nameof(GameWorld.OnGameStarted));
}
[PatchPostfix]

View File

@ -16,7 +16,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
protected override MethodBase GetTargetMethod()
{
return typeof(GameWorld).GetMethod(nameof(GameWorld.OnGameStarted));
return AccessTools.Method(typeof(GameWorld), nameof(GameWorld.OnGameStarted));
}
[PatchPostfix]

View File

@ -1,7 +1,6 @@
using Aki.Common.Http;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using System.Linq;
using System.Reflection;
namespace Aki.SinglePlayer.Patches.RaidFix
@ -15,7 +14,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
const string methodName = "SetSettings";
var desiredType = PatchConstants.EftTypes.Single(x => x.GetMethod(methodName, flags) != null && IsTargetMethod(x.GetMethod(methodName, flags)));
var desiredType = PatchConstants.EftTypes.SingleCustom(x => x.GetMethod(methodName, flags) != null && IsTargetMethod(x.GetMethod(methodName, flags)));
var desiredMethod = desiredType.GetMethod(methodName, flags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");

View File

@ -2,6 +2,7 @@ using System.Reflection;
using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using HarmonyLib;
using UnityEngine;
namespace Aki.SinglePlayer.Patches.RaidFix
@ -13,7 +14,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
return typeof(Player).GetMethod("PlayToggleSound", BindingFlags.Instance | BindingFlags.NonPublic);
return AccessTools.Method(typeof(Player), nameof(Player.PlayToggleSound));
}
[PatchPrefix]

View File

@ -10,13 +10,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(Profile.TraderInfo);
var desiredMethod = desiredType.GetMethod("UpdateLevel", BindingFlags.NonPublic | BindingFlags.Instance);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(Profile.TraderInfo), nameof(Profile.TraderInfo.UpdateLevel));
}
[PatchPrefix]
@ -36,7 +30,8 @@ namespace Aki.SinglePlayer.Patches.RaidFix
}
// Backing field of the "CurrentLoyalty" property
Traverse.Create(__instance).Field("<CurrentLoyalty>k__BackingField").SetValue(loyaltyLevelSettings.Value);
// Traverse.Create(__instance).Field("<CurrentLoyalty>k__BackingField").SetValue(loyaltyLevelSettings.Value);
__instance.CurrentLoyalty = loyaltyLevelSettings.Value;
}
}
}

View File

@ -1,48 +1,25 @@
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using System;
using System.Linq;
using System.Reflection;
using Aki.Reflection.Utils;
namespace Aki.SinglePlayer.Patches.RaidFix
{
public class RemoveUsedBotProfilePatch : ModulePatch
{
private static readonly BindingFlags _flags;
private static readonly Type _targetInterface;
private static readonly Type _targetType;
private static readonly FieldInfo _profilesField;
static RemoveUsedBotProfilePatch()
{
_ = nameof(IGetProfileData.ChooseProfile);
_flags = BindingFlags.Instance | BindingFlags.NonPublic;
_targetInterface = PatchConstants.EftTypes.Single(IsTargetInterface);
_targetType = typeof(BotsPresets);
_profilesField = _targetType.GetField("list_0", _flags);
}
protected override MethodBase GetTargetMethod()
{
return _targetType.GetMethod("GetNewProfile", _flags);
}
private static bool IsTargetInterface(Type type)
{
return type.IsInterface && type.GetProperty("StartProfilesLoaded") != null && type.GetMethod("CreateProfile") != null;
}
private static bool IsTargetType(Type type)
{
return _targetInterface.IsAssignableFrom(type) && _targetInterface.IsAssignableFrom(type.BaseType);
return typeof(BotsPresets).BaseType.GetMethods().SingleCustom(m => m.Name == nameof(BotsPresets.GetNewProfile) && m.IsVirtual);
}
/// <summary>
/// BotsPresets.GetNewProfile()
[PatchPrefix]
private static bool PatchPrefix(ref Profile __result, object __instance, GClass560 data, ref bool withDelete)
private static bool PatchPrefix(ref bool withDelete)
{
withDelete = true;

View File

@ -16,7 +16,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
return typeof(GrenadeEmission).GetMethod(nameof(GrenadeEmission.StartEmission));
return AccessTools.Method(typeof(GrenadeEmission), nameof(GrenadeEmission.StartEmission));
}
[PatchTranspiler]

View File

@ -3,6 +3,7 @@ using Aki.Reflection.Utils;
using EFT;
using System;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.RaidFix
{
@ -19,19 +20,12 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(BotSpawner);
var desiredMethod = desiredType.GetMethod("CheckOnMax", PatchConstants.PublicFlags);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(BotSpawner), nameof(BotSpawner.CheckOnMax));
}
[PatchPrefix]
private static bool PatchPreFix(int wantSpawn, ref int toDelay, ref int toSpawn, ref int ____maxBots, int ____allBotsCount, int ____inSpawnProcess)
{
// Set bots to delay if alive bots + spawning bots count > maxbots
// ____inSpawnProcess can be negative, don't go below 0 when calculating
if ((____allBotsCount + Math.Max(____inSpawnProcess, 0)) > ____maxBots)

View File

@ -4,6 +4,7 @@ using System.Reflection;
using Aki.Reflection.Patching;
using System.Collections;
using EFT.HealthSystem;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.RaidFix
{
@ -11,15 +12,16 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
return typeof(BetterAudio).GetMethod("StartTinnitusEffect", BindingFlags.Instance | BindingFlags.Public);
return AccessTools.Method(typeof(BetterAudio), nameof(BetterAudio.StartTinnitusEffect));
}
// checks on invoke whether the player is stunned before allowing tinnitus
[PatchPrefix]
static bool PatchPrefix()
{
bool shouldInvoke = typeof(ActiveHealthController)
.GetMethod("FindActiveEffect", BindingFlags.Instance | BindingFlags.Public)
var baseMethod = AccessTools.Method(typeof(ActiveHealthController), nameof(ActiveHealthController.FindActiveEffect));
bool shouldInvoke = baseMethod
.MakeGenericMethod(typeof(ActiveHealthController)
.GetNestedType("Stun", BindingFlags.Instance | BindingFlags.NonPublic))
.Invoke(Singleton<GameWorld>.Instance.MainPlayer.ActiveHealthController, new object[] { EBodyPart.Common }) != null;

View File

@ -1,7 +1,7 @@
using System.Reflection;
using Aki.Reflection.Patching;
using Aki.Reflection.Utils;
using EFT;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.RaidFix
{
@ -9,7 +9,7 @@ namespace Aki.SinglePlayer.Patches.RaidFix
{
protected override MethodBase GetTargetMethod()
{
return typeof(ForceMuteVoIPToggler).GetMethod("Awake", PatchConstants.PrivateFlags);
return AccessTools.Method(typeof(ForceMuteVoIPToggler), nameof(ForceMuteVoIPToggler.Awake));
}
[PatchPrefix]

View File

@ -2,6 +2,7 @@ using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using System.Reflection;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.ScavMode
{
@ -12,13 +13,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
{
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(GameWorld);
var desiredMethod = desiredType.GetMethod("OnGameStarted", BindingFlags.Public | BindingFlags.Instance);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
return desiredMethod;
return AccessTools.Method(typeof(GameWorld), nameof(GameWorld.OnGameStarted));
}
[PatchPostfix]
@ -26,21 +21,19 @@ namespace Aki.SinglePlayer.Patches.ScavMode
{
var gameWorld = Singleton<GameWorld>.Instance;
// checks nothing is null otherwise woopsies happen.
// checks nothing is null otherwise bad things happen
if (gameWorld == null || gameWorld.RegisteredPlayers == null || gameWorld.ExfiltrationController == null)
{
Logger.LogError("Unable to Find Gameworld or RegisterPlayers... Unable to Disable Extracts for Scav raid");
Logger.LogError("Could not find GameWorld or RegisterPlayers... Unable to disable extracts for Scav raid");
}
Player player = gameWorld.MainPlayer;
var exfilController = gameWorld.ExfiltrationController;
// Only disable PMC extracts if current player is a scav.
// Only disable PMC extracts if current player is a scav
if (player.Fraction == ETagStatus.Scav && player.Location != "hideout")
{
// these are PMC extracts only, scav extracts are under a different field called ScavExfiltrationPoints.
foreach (var exfil in exfilController.ExfiltrationPoints)
// these are PMC extracts only, scav extracts are under a different field called ScavExfiltrationPoints
foreach (var exfil in gameWorld.ExfiltrationController.ExfiltrationPoints)
{
exfil.Disable();
}

View File

@ -31,12 +31,10 @@ namespace Aki.SinglePlayer.Patches.ScavMode
_ = nameof(WavesSettings.IsBosses);
_ = GClass3164.MAX_SCAV_COUNT; // UPDATE REFS TO THIS CLASS BELOW !!!
var menuControllerType = typeof(MainMenuController);
// `MatchmakerInsuranceScreen` OnShowNextScreen
_onReadyScreenMethod = menuControllerType.GetMethod("method_42", PatchConstants.PrivateFlags);
_onReadyScreenMethod = AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_42));
_isLocalField = menuControllerType.GetField("bool_0", PatchConstants.PrivateFlags);
_isLocalField = AccessTools.Field(typeof(MainMenuController), "bool_0");
_menuControllerField = typeof(TarkovApplication).GetFields(PatchConstants.PrivateFlags).FirstOrDefault(x => x.FieldType == typeof(MainMenuController));
if (_menuControllerField == null)
@ -48,7 +46,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
protected override MethodBase GetTargetMethod()
{
// `MatchMakerSelectionLocationScreen` OnShowNextScreen
return typeof(MainMenuController).GetMethod("method_68", PatchConstants.PrivateFlags);
return AccessTools.Method(typeof(MainMenuController), nameof(MainMenuController.method_68));
}
[PatchTranspiler]
@ -125,7 +123,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
gclass.OnShowNextScreen += LoadOfflineRaidNextScreen;
// `MatchmakerOfflineRaidScreen` OnShowReadyScreen
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, "method_72");
gclass.OnShowReadyScreen += (OfflineRaidAction)Delegate.CreateDelegate(typeof(OfflineRaidAction), menuController, nameof(MainMenuController.method_72));
gclass.ShowScreen(EScreenState.Queued);
}

View File

@ -3,6 +3,7 @@ using Aki.Reflection.Patching;
using Comfort.Common;
using EFT;
using EFT.Interactive;
using HarmonyLib;
namespace Aki.SinglePlayer.Patches.ScavMode
{
@ -10,7 +11,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
{
protected override MethodBase GetTargetMethod()
{
return typeof(ExfiltrationControllerClass).GetMethod("EligiblePoints", new []{ typeof(Profile) });
return AccessTools.Method(typeof(ExfiltrationControllerClass), nameof(ExfiltrationControllerClass.EligiblePoints), new[] { typeof(Profile) });
}
[PatchPrefix]
@ -29,7 +30,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
}
// Running this prepares all the data for getting scav exfil points
__instance.ScavExfiltrationClaim(Singleton<GameWorld>.Instance.MainPlayer.Position, profile.Id, profile.FenceInfo.AvailableExitsCount);
__instance.ScavExfiltrationClaim(((IPlayer)Singleton<GameWorld>.Instance.MainPlayer).Position, profile.Id, profile.FenceInfo.AvailableExitsCount);
// Get the required mask value and retrieve a list of exfil points, setting it as the result
var mask = __instance.GetScavExfiltrationMask(profile.Id);

View File

@ -25,8 +25,8 @@ namespace Aki.SinglePlayer.Patches.ScavMode
protected override MethodBase GetTargetMethod()
{
var desiredType = PatchConstants.EftTypes.Single(x => x.Name == "TarkovApplication");
var desiredMethod = Array.Find(desiredType.GetMethods(PatchConstants.PrivateFlags), IsTargetMethod);
var desiredType = typeof(TarkovApplication);
var desiredMethod = Array.Find(desiredType.GetMethods(PatchConstants.PublicDeclaredFlags), IsTargetMethod);
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
Logger.LogDebug($"{this.GetType().Name} Method: {desiredMethod?.Name}");
@ -95,11 +95,10 @@ namespace Aki.SinglePlayer.Patches.ScavMode
foreach (var exitChange in exitChangesToApply)
{
// Find the client exit we want to make changes to
var exitToChange = location.exits.First(x => x.Name == exitChange.Name);
var exitToChange = location.exits.FirstOrDefault(x => x.Name == exitChange.Name);
if (exitToChange == null)
{
Logger.LogDebug($"Exit with Id: {exitChange.Name} not found, skipping");
continue;
}
@ -111,6 +110,10 @@ namespace Aki.SinglePlayer.Patches.ScavMode
if (exitChange.MinTime.HasValue)
{
exitToChange.MinTime = exitChange.MinTime.Value;
}
if (exitChange.MaxTime.HasValue)
{
exitToChange.MaxTime = exitChange.MaxTime.Value;
}
}
@ -131,19 +134,9 @@ namespace Aki.SinglePlayer.Patches.ScavMode
}
// Reset values to those from cache
if (clientLocationExit.Chance != cachedExit.Chance)
{
clientLocationExit.Chance = cachedExit.Chance;
}
if (clientLocationExit.MinTime != cachedExit.MinTime)
{
clientLocationExit.MinTime = cachedExit.MinTime;
}
if (clientLocationExit.MaxTime != cachedExit.MaxTime)
{
clientLocationExit.MaxTime = cachedExit.MaxTime;
}
clientLocationExit.Chance = cachedExit.Chance;
clientLocationExit.MinTime = cachedExit.MinTime;
clientLocationExit.MaxTime = cachedExit.MaxTime;
}
}

View File

@ -15,13 +15,13 @@ namespace Aki.SinglePlayer.Patches.ScavMode
protected override MethodBase GetTargetMethod()
{
var desiredType = typeof(TarkovApplication)
.GetNestedTypes(PatchConstants.PrivateFlags)
.Single(x => x.GetField("timeAndWeather") != null
&& x.GetField("tarkovApplication_0") != null
&& x.GetField("timeHasComeScreenController") == null
&& x.Name.Contains("Struct"));
.GetNestedTypes(PatchConstants.PublicDeclaredFlags)
.SingleCustom(x => x.GetField("timeAndWeather") != null
&& x.GetField("tarkovApplication_0") != null
&& x.GetField("timeHasComeScreenController") == null
&& x.Name.Contains("Struct"));
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags)
var desiredMethod = desiredType.GetMethods(PatchConstants.PublicDeclaredFlags)
.FirstOrDefault(x => x.Name == "MoveNext");
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");

View File

@ -17,12 +17,12 @@ namespace Aki.SinglePlayer.Patches.ScavMode
{
// Struct225 - 20575
var desiredType = typeof(TarkovApplication)
.GetNestedTypes(PatchConstants.PrivateFlags)
.Single(x => x.GetField("timeAndWeather") != null
.GetNestedTypes(PatchConstants.PublicDeclaredFlags)
.SingleCustom(x => x.GetField("timeAndWeather") != null
&& x.GetField("timeHasComeScreenController") != null
&& x.Name.Contains("Struct"));
var desiredMethod = desiredType.GetMethods(PatchConstants.PrivateFlags)
var desiredMethod = desiredType.GetMethods(PatchConstants.PublicDeclaredFlags)
.FirstOrDefault(x => x.Name == "MoveNext");
Logger.LogDebug($"{this.GetType().Name} Type: {desiredType?.Name}");
@ -72,7 +72,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
new Code(OpCodes.Callvirt, PatchConstants.BackendSessionInterfaceType, "get_Profile"),
new Code(OpCodes.Br, brLabel),
new CodeWithLabel(OpCodes.Callvirt, brFalseLabel, PatchConstants.SessionInterfaceType, "get_ProfileOfPet"),
new CodeWithLabel(OpCodes.Stfld, brLabel, typeof(TarkovApplication).GetNestedTypes(BindingFlags.NonPublic).Single(IsTargetNestedType), "profile")
new CodeWithLabel(OpCodes.Stfld, brLabel, typeof(TarkovApplication).GetNestedTypes(BindingFlags.Public).SingleCustom(IsTargetNestedType), "profile")
});
codes.RemoveRange(searchIndex, 4);
@ -83,7 +83,7 @@ namespace Aki.SinglePlayer.Patches.ScavMode
private static bool IsTargetNestedType(System.Type nestedType)
{
return nestedType.GetMethods(PatchConstants.PrivateFlags)
return nestedType.GetMethods(PatchConstants.PublicDeclaredFlags)
.Count(x => x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(IResult)) > 0 && nestedType.GetField("savageProfile") != null;
}
}

View File

@ -33,7 +33,7 @@ namespace Aki.SinglePlayer.Utils.Insurance
public List<AkiInsuredItemClass> GetTrackedItems()
{
var itemsToSend = new List<AkiInsuredItemClass>();
if (_items == null || _items.Count() == 0)
if (_items == null || !_items.Any())
{
return itemsToSend;
}

Binary file not shown.