using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using LethalOptimizer.Patches;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("LethalOptimizer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Micro-optimizations for smoother gameplay")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: AssemblyProduct("LethalOptimizer")]
[assembly: AssemblyTitle("LethalOptimizer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace LethalOptimizer
{
[BepInPlugin("mrbub.lethaloptimizer", "LethalOptimizer", "1.0.1")]
public class Plugin : BaseUnityPlugin
{
internal static ManualLogSource Logger;
internal static ConfigFile ConfigFile;
internal static ConfigEntry<bool> CacheCollisionComponents;
internal static ConfigEntry<bool> OptimizeStringAllocations;
internal static ConfigEntry<bool> CacheRepeatedLookups;
private void Awake()
{
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_0027: Expected O, but got Unknown
Logger = ((BaseUnityPlugin)this).Logger;
ConfigFile = ((BaseUnityPlugin)this).Config;
InitializeConfig();
Harmony val = new Harmony("mrbub.lethaloptimizer");
if (CacheCollisionComponents.Value)
{
val.PatchAll(typeof(CollisionCachePatches));
Logger.LogInfo((object)"Collision component caching enabled");
}
if (OptimizeStringAllocations.Value)
{
val.PatchAll(typeof(StringOptimizationPatches));
Logger.LogInfo((object)"String allocation optimization enabled");
}
if (CacheRepeatedLookups.Value)
{
val.PatchAll(typeof(ComponentLookupPatches));
Logger.LogInfo((object)"Component lookup caching enabled");
}
val.PatchAll(typeof(ShipItemOptimizationPatches));
Logger.LogInfo((object)"Ship item collection optimization enabled");
Logger.LogInfo((object)"LethalOptimizer v1.0.1 loaded successfully!");
}
private void InitializeConfig()
{
CacheCollisionComponents = ((BaseUnityPlugin)this).Config.Bind<bool>("Caching", "CacheCollisionComponents", true, "Cache PlayerControllerB lookups in collision/trigger handlers");
OptimizeStringAllocations = ((BaseUnityPlugin)this).Config.Bind<bool>("Strings", "OptimizeStringAllocations", true, "Use StringBuilder for string formatting to reduce allocations");
CacheRepeatedLookups = ((BaseUnityPlugin)this).Config.Bind<bool>("Caching", "CacheRepeatedLookups", true, "Cache NetworkObject lookups during enemy and scrap spawning");
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "mrbub.lethaloptimizer";
public const string PLUGIN_NAME = "LethalOptimizer";
public const string PLUGIN_VERSION = "1.0.1";
}
}
namespace LethalOptimizer.Patches
{
[HarmonyPatch]
internal class CollisionCachePatches
{
private static readonly Dictionary<GameObject, PlayerControllerB> playerControllerCache = new Dictionary<GameObject, PlayerControllerB>();
public static PlayerControllerB GetCachedPlayerController(GameObject gameObject)
{
if ((Object)(object)gameObject == (Object)null)
{
return null;
}
if (!playerControllerCache.TryGetValue(gameObject, out var value))
{
value = gameObject.GetComponent<PlayerControllerB>();
if ((Object)(object)value != (Object)null)
{
playerControllerCache[gameObject] = value;
}
}
return value;
}
[HarmonyPatch(typeof(QuicksandTrigger), "OnTriggerStay")]
[HarmonyPrefix]
private static bool QuicksandTriggerStayPrefix(QuicksandTrigger __instance, Collider other)
{
try
{
if (__instance.movementHinderance < 1f)
{
return false;
}
PlayerControllerB cachedPlayerController = GetCachedPlayerController(((Component)other).gameObject);
if ((Object)(object)cachedPlayerController != (Object)(object)GameNetworkManager.Instance.localPlayerController && (Object)(object)cachedPlayerController != (Object)null && (Object)(object)cachedPlayerController.underwaterCollider != (Object)(object)__instance)
{
cachedPlayerController.underwaterCollider = ((Component)__instance).gameObject.GetComponent<Collider>();
return false;
}
if (!__instance.sinkingLocalPlayer)
{
return false;
}
PlayerControllerB cachedPlayerController2 = GetCachedPlayerController(((Component)other).gameObject);
if ((Object)(object)cachedPlayerController2 != (Object)(object)GameNetworkManager.Instance.localPlayerController)
{
return false;
}
if (cachedPlayerController2.isClimbingLadder || cachedPlayerController2.inSpecialInteractAnimation || StartOfRound.Instance.suckingPlayersOutOfShip)
{
return false;
}
if (__instance.isWater && !cachedPlayerController2.isUnderwater)
{
cachedPlayerController2.underwaterCollider = ((Component)__instance).gameObject.GetComponent<Collider>();
cachedPlayerController2.isUnderwater = true;
}
return false;
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Error in QuicksandTriggerStay patch: " + ex.Message));
return true;
}
}
[HarmonyPatch(typeof(QuicksandTrigger), "OnTriggerExit")]
[HarmonyPrefix]
private static bool QuicksandTriggerExitPrefix(QuicksandTrigger __instance, Collider other)
{
try
{
if (__instance.sinkingLocalPlayer)
{
if (!__instance.isWater && ((Component)other).gameObject.CompareTag("Player"))
{
PlayerControllerB cachedPlayerController = GetCachedPlayerController(((Component)other).gameObject);
if ((Object)(object)cachedPlayerController == (Object)(object)GameNetworkManager.Instance.localPlayerController)
{
return false;
}
if ((Object)(object)cachedPlayerController != (Object)null)
{
cachedPlayerController.isUnderwater = false;
}
}
__instance.sinkingLocalPlayer = false;
return false;
}
return false;
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Error in QuicksandTriggerExit patch: " + ex.Message));
return true;
}
}
[HarmonyPatch(typeof(RoundManager), "LoadNewLevel")]
[HarmonyPostfix]
private static void ClearCacheOnLevelChange()
{
playerControllerCache.Clear();
}
[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
[HarmonyPostfix]
private static void ClearCacheOnShipReset()
{
playerControllerCache.Clear();
}
}
[HarmonyPatch]
internal class ComponentLookupPatches
{
private static readonly Dictionary<GameObject, NetworkObject> networkObjectCache = new Dictionary<GameObject, NetworkObject>();
public static NetworkObject GetCachedNetworkObject(GameObject gameObject)
{
if ((Object)(object)gameObject == (Object)null)
{
return null;
}
if (!networkObjectCache.TryGetValue(gameObject, out var value))
{
value = gameObject.GetComponent<NetworkObject>();
if ((Object)(object)value != (Object)null)
{
networkObjectCache[gameObject] = value;
}
}
return value;
}
public static NetworkObject GetCachedNetworkObjectInChildren(GameObject gameObject)
{
if ((Object)(object)gameObject == (Object)null)
{
return null;
}
NetworkObject cachedNetworkObject = GetCachedNetworkObject(gameObject);
if ((Object)(object)cachedNetworkObject != (Object)null)
{
return cachedNetworkObject;
}
cachedNetworkObject = gameObject.GetComponentInChildren<NetworkObject>();
if ((Object)(object)cachedNetworkObject != (Object)null)
{
networkObjectCache[gameObject] = cachedNetworkObject;
}
return cachedNetworkObject;
}
[HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> OptimizeNetworkObjectLookups(IEnumerable<CodeInstruction> instructions)
{
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Expected O, but got Unknown
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
MethodInfo methodInfo = AccessTools.Method(typeof(GameObject), "GetComponent", (Type[])null, new Type[1] { typeof(NetworkObject) });
MethodInfo methodInfo2 = AccessTools.Method(typeof(ComponentLookupPatches), "GetCachedNetworkObject", (Type[])null, (Type[])null);
if (methodInfo == null || methodInfo2 == null)
{
Plugin.Logger.LogWarning((object)"Could not find methods for NetworkObject caching in SpawnScrapInLevel");
return list;
}
int num = 0;
for (int i = 0; i < list.Count; i++)
{
if (CodeInstructionExtensions.Calls(list[i], methodInfo))
{
list[i] = new CodeInstruction(OpCodes.Call, (object)methodInfo2);
num++;
}
}
if (num > 0)
{
Plugin.Logger.LogInfo((object)$"Replaced {num} GetComponent<NetworkObject> calls in SpawnScrapInLevel");
}
return list;
}
[HarmonyPatch(typeof(RoundManager), "SpawnEnemyOnServer")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> OptimizeEnemySpawnLookups(IEnumerable<CodeInstruction> instructions)
{
//IL_008a: Unknown result type (might be due to invalid IL or missing references)
//IL_0094: Expected O, but got Unknown
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
MethodInfo methodInfo = AccessTools.Method(typeof(GameObject), "GetComponentInChildren", (Type[])null, new Type[1] { typeof(NetworkObject) });
MethodInfo methodInfo2 = AccessTools.Method(typeof(ComponentLookupPatches), "GetCachedNetworkObjectInChildren", (Type[])null, (Type[])null);
if (methodInfo == null || methodInfo2 == null)
{
Plugin.Logger.LogWarning((object)"Could not find methods for NetworkObject caching in SpawnEnemyOnServer");
return list;
}
int num = 0;
for (int i = 0; i < list.Count; i++)
{
if (CodeInstructionExtensions.Calls(list[i], methodInfo))
{
list[i] = new CodeInstruction(OpCodes.Call, (object)methodInfo2);
num++;
}
}
if (num > 0)
{
Plugin.Logger.LogInfo((object)$"Replaced {num} GetComponentInChildren<NetworkObject> calls in SpawnEnemyOnServer");
}
return list;
}
[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
[HarmonyPostfix]
private static void ClearCacheOnRoundEnd()
{
networkObjectCache.Clear();
}
[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
[HarmonyPostfix]
private static void ClearCacheOnShipReset()
{
networkObjectCache.Clear();
}
}
[HarmonyPatch]
internal class DebugLogPatches
{
[HarmonyPatch(typeof(RoundManager), "SpawnScrapInLevel")]
[HarmonyTranspiler]
private static IEnumerable<CodeInstruction> RemoveDebugLogsFromSpawnScrap(IEnumerable<CodeInstruction> instructions)
{
List<CodeInstruction> list = new List<CodeInstruction>(instructions);
MethodInfo methodInfo = AccessTools.Method(typeof(Debug), "Log", new Type[1] { typeof(object) }, (Type[])null);
if (methodInfo == null)
{
Plugin.Logger.LogWarning((object)"Could not find Debug.Log method, skipping transpiler");
return list;
}
int num = 0;
for (int num2 = list.Count - 1; num2 >= 0; num2--)
{
if (CodeInstructionExtensions.Calls(list[num2], methodInfo))
{
int num3 = 1;
int num4 = num2 - 1;
while (num4 >= 0 && num3 < 10 && (list[num4].opcode == OpCodes.Ldstr || list[num4].opcode == OpCodes.Ldarg || list[num4].opcode == OpCodes.Ldloc))
{
num3++;
num4--;
}
for (int i = 0; i < num3 && num2 - i >= 0; i++)
{
list[num2 - i].opcode = OpCodes.Nop;
}
num++;
}
}
if (num > 0)
{
Plugin.Logger.LogInfo((object)$"Removed {num} Debug.Log calls from SpawnScrapInLevel");
}
return list;
}
}
[HarmonyPatch]
internal class ShipItemOptimizationPatches
{
private static readonly HashSet<GrabbableObject> scrapCollectedSet = new HashSet<GrabbableObject>();
private static bool setInitialized = false;
[HarmonyPatch(typeof(RoundManager), "CollectNewScrapForThisRound")]
[HarmonyPrefix]
private static bool CollectNewScrapPrefix(RoundManager __instance, GrabbableObject scrapObject)
{
try
{
if (!scrapObject.itemProperties.isScrap)
{
return false;
}
if (!setInitialized && __instance.scrapCollectedThisRound != null)
{
scrapCollectedSet.Clear();
foreach (GrabbableObject item in __instance.scrapCollectedThisRound)
{
if ((Object)(object)item != (Object)null)
{
scrapCollectedSet.Add(item);
}
}
setInitialized = true;
}
if (scrapCollectedSet.Contains(scrapObject))
{
return false;
}
if (scrapObject.scrapPersistedThroughRounds)
{
return false;
}
__instance.scrapCollectedThisRound.Add(scrapObject);
scrapCollectedSet.Add(scrapObject);
HUDManager.Instance.AddNewScrapFoundToDisplay(scrapObject);
return false;
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Error in CollectNewScrapForThisRound patch: " + ex.Message));
return true;
}
}
[HarmonyPatch(typeof(RoundManager), "DespawnPropsAtEndOfRound")]
[HarmonyPostfix]
private static void ClearScrapSetOnRoundEnd()
{
scrapCollectedSet.Clear();
setInitialized = false;
}
[HarmonyPatch(typeof(StartOfRound), "ResetShip")]
[HarmonyPostfix]
private static void ClearScrapSetOnShipReset()
{
scrapCollectedSet.Clear();
setInitialized = false;
}
}
[HarmonyPatch]
internal class StringOptimizationPatches
{
private static readonly StringBuilder stringBuilder = new StringBuilder(256);
[HarmonyPatch(typeof(GrabbableObject), "SetScrapValue")]
[HarmonyPrefix]
private static bool OptimizeSetScrapValue(GrabbableObject __instance, int setValueTo)
{
try
{
__instance.scrapValue = setValueTo;
ScanNodeProperties componentInChildren = ((Component)__instance).gameObject.GetComponentInChildren<ScanNodeProperties>();
if ((Object)(object)componentInChildren == (Object)null)
{
return true;
}
stringBuilder.Clear();
stringBuilder.Append("Value: $");
stringBuilder.Append(setValueTo);
componentInChildren.subText = stringBuilder.ToString();
componentInChildren.scrapValue = setValueTo;
return false;
}
catch (Exception ex)
{
Plugin.Logger.LogError((object)("Error in SetScrapValue patch: " + ex.Message));
return true;
}
}
}
}