Decompiled source of CruiserDisconnectFix v1.0.5

BepInEx/plugins/CruiserDisconnectFix/CruiserDisconnectFix.dll

Decompiled 4 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

[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("CruiserDisconnectFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CruiserDisconnectFix")]
[assembly: AssemblyTitle("CruiserDisconnectFix")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 CruiserDisconnectFix
{
	[BepInPlugin("CruiserDisconnectFix", "CruiserDisconnectFix", "1.0.5")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string PluginGuid = "CruiserDisconnectFix";

		public const string PluginName = "CruiserDisconnectFix";

		public const string PluginVersion = "1.0.5";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> EnableLogging;

		internal static bool LoggingEnabled
		{
			get
			{
				if (EnableLogging != null)
				{
					return EnableLogging.Value;
				}
				return true;
			}
		}

		internal static void LogInfo(object data)
		{
			if (LoggingEnabled)
			{
				Log.LogInfo(data);
			}
		}

		internal static void LogWarning(object data)
		{
			if (LoggingEnabled)
			{
				Log.LogWarning(data);
			}
		}

		internal static void LogError(object data)
		{
			if (LoggingEnabled)
			{
				Log.LogError(data);
			}
		}

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			EnableLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Logging", "EnableLogging", false, "Enables CruiserDisconnectFix logging.");
			((BaseUnityPlugin)this).Config.Save();
			LogInfo("CruiserDisconnectFix 1.0.5 loaded.");
			LogWarning("CruiserDisconnectFix installing STATIC Unity callbacks. No runtime MonoBehaviour, Update(), coroutine, or Harmony disconnect hook is used.");
			StaticDisconnectMonitor.Install();
		}
	}
	internal static class StaticDisconnectMonitor
	{
		private sealed class PendingRepair
		{
			internal float DueTime;

			internal StartOfRound Round;

			internal TrackedClientState State;

			internal string Phase = "";
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static UnityAction <0>__OnBeforeRender;

			public static LogCallback <1>__OnUnityLogMessage;
		}

		private static bool installed;

		private static float nextSnapshotTime;

		private static float nextHeartbeatTime;

		private static readonly Dictionary<ulong, TrackedClientState> trackedClients = new Dictionary<ulong, TrackedClientState>();

		private static readonly Dictionary<ulong, float> recentDisconnects = new Dictionary<ulong, float>();

		private static readonly List<PendingRepair> pendingRepairs = new List<PendingRepair>();

		internal static void Install()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			if (installed)
			{
				Plugin.LogWarning("CruiserDisconnectFix static monitor was already installed.");
				return;
			}
			installed = true;
			object obj = <>O.<0>__OnBeforeRender;
			if (obj == null)
			{
				UnityAction val = OnBeforeRender;
				<>O.<0>__OnBeforeRender = val;
				obj = (object)val;
			}
			Application.onBeforeRender += (UnityAction)obj;
			object obj2 = <>O.<1>__OnUnityLogMessage;
			if (obj2 == null)
			{
				LogCallback val2 = OnUnityLogMessage;
				<>O.<1>__OnUnityLogMessage = val2;
				obj2 = (object)val2;
			}
			Application.logMessageReceived += (LogCallback)obj2;
			Plugin.LogWarning("CruiserDisconnectFix STATIC MONITOR INSTALLED: Application.onBeforeRender + Application.logMessageReceived.");
		}

		private static void OnBeforeRender()
		{
			try
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (realtimeSinceStartup >= nextHeartbeatTime)
				{
					nextHeartbeatTime = realtimeSinceStartup + 5f;
					NetworkManager singleton = NetworkManager.Singleton;
					Plugin.LogInfo($"CruiserDisconnectFix STATIC HEARTBEAT: frame={Time.frameCount}; " + "networkManager=" + (((Object)(object)singleton == (Object)null) ? "null" : "present") + "; " + $"isServer={(Object)(object)singleton != (Object)null && singleton.IsServer}; " + $"trackedClients={trackedClients.Count}; " + $"pendingRepairs={pendingRepairs.Count}.");
				}
				ProcessPendingRepairs(realtimeSinceStartup);
				NetworkManager singleton2 = NetworkManager.Singleton;
				if (!((Object)(object)singleton2 == (Object)null) && singleton2.IsServer && !(realtimeSinceStartup < nextSnapshotTime))
				{
					nextSnapshotTime = realtimeSinceStartup + 0.05f;
					SnapshotRemotePlayers(singleton2);
				}
			}
			catch (Exception arg)
			{
				Plugin.LogError($"CruiserDisconnectFix static frame callback failed: {arg}");
			}
		}

		private static void SnapshotRemotePlayers(NetworkManager manager)
		{
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || instance.allPlayerScripts == null)
			{
				return;
			}
			for (int i = 0; i < instance.allPlayerScripts.Length; i++)
			{
				PlayerControllerB val = instance.allPlayerScripts[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				ulong ownerClientId;
				try
				{
					ownerClientId = ((NetworkBehaviour)val).OwnerClientId;
				}
				catch
				{
					continue;
				}
				if (ownerClientId == manager.LocalClientId)
				{
					continue;
				}
				if (!trackedClients.TryGetValue(ownerClientId, out TrackedClientState value))
				{
					value = new TrackedClientState
					{
						ClientId = ownerClientId,
						PlayerObjectNumber = i,
						Player = val
					};
					trackedClients[ownerClientId] = value;
					Plugin.LogInfo($"CruiserDisconnectFix tracking remote clientId={ownerClientId} " + $"at player slot {i}.");
				}
				else
				{
					value.PlayerObjectNumber = i;
					value.Player = val;
				}
				value.LastSeenTime = Time.realtimeSinceStartup;
				TrackedCruiserState trackedCruiserState = StrongCruiserTracker.FindStrongCruiserMatch(val, i, ownerClientId);
				if (trackedCruiserState != null)
				{
					bool num = value.Cruiser == null || value.Cruiser.Vehicle != trackedCruiserState.Vehicle;
					value.Cruiser = trackedCruiserState;
					if (num)
					{
						Plugin.LogWarning("CruiserDisconnectFix CACHED STRONG CRUISER MATCH for " + $"clientId={ownerClientId}, playerSlot={i}. Reason(s): " + string.Join("; ", trackedCruiserState.MatchReasons));
					}
				}
				else if (value.Cruiser != null && Time.realtimeSinceStartup - value.Cruiser.LastMatchedTime > 5f)
				{
					value.Cruiser = null;
				}
			}
		}

		private static void OnUnityLogMessage(string condition, string stackTrace, LogType type)
		{
			try
			{
				int num = condition.IndexOf("Disconnect callback called in gamenetworkmanager; disconnecting clientId:", StringComparison.OrdinalIgnoreCase);
				if (num < 0)
				{
					return;
				}
				string text = condition.Substring(num + "Disconnect callback called in gamenetworkmanager; disconnecting clientId:".Length).Trim();
				int i;
				for (i = 0; i < text.Length && char.IsDigit(text[i]); i++)
				{
				}
				if (i == 0 || !ulong.TryParse(text.Substring(0, i), out var result))
				{
					Plugin.LogWarning("CruiserDisconnectFix saw vanilla disconnect log but could not parse clientId: " + condition);
					return;
				}
				NetworkManager singleton = NetworkManager.Singleton;
				Plugin.LogWarning("CruiserDisconnectFix VANILLA DISCONNECT LOG INTERCEPTED SYNCHRONOUSLY: " + $"clientId={result}; frame={Time.frameCount}; " + $"isServer={(Object)(object)singleton != (Object)null && singleton.IsServer}.");
				if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
				{
					return;
				}
				if (result == singleton.LocalClientId)
				{
					Plugin.LogInfo($"CruiserDisconnectFix ignoring local host disconnect clientId={result}.");
					return;
				}
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (recentDisconnects.TryGetValue(result, out var value) && realtimeSinceStartup - value < 3f)
				{
					Plugin.LogInfo($"CruiserDisconnectFix ignoring duplicate parsed disconnect for clientId={result}.");
					return;
				}
				recentDisconnects[result] = realtimeSinceStartup;
				StartOfRound instance = StartOfRound.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					Plugin.LogWarning("CruiserDisconnectFix parsed disconnect but StartOfRound.Instance was null.");
					return;
				}
				TrackedCruiserState trackedCruiserState = DirectSeatRepair.ReleaseDepartingClientSeat(result, "vanilla-log-prefix");
				TrackedClientState trackedClientState = ResolveTrackedState(instance, result);
				if (trackedClientState.Cruiser == null && trackedCruiserState != null)
				{
					trackedClientState.Cruiser = trackedCruiserState;
				}
				Plugin.LogWarning("CruiserDisconnectFix REMOTE CLIENT DISCONNECT REPAIR STARTED: " + $"clientId={result}; " + $"playerSlot={trackedClientState.PlayerObjectNumber}; " + "player=" + (((Object)(object)trackedClientState.Player == (Object)null) ? "null" : PlayerResolver.SafeName(trackedClientState.Player)) + "; cachedCruiser=" + ((trackedClientState.Cruiser == null) ? "NO" : "YES") + ".");
				Repair(instance, trackedClientState, useVanillaRelease: true, "vanilla-log-prefix");
				ScheduleRepair(realtimeSinceStartup + 0.02f, instance, trackedClientState, "after-0.02s");
				ScheduleRepair(realtimeSinceStartup + 0.15f, instance, trackedClientState, "after-0.15s");
				ScheduleRepair(realtimeSinceStartup + 0.75f, instance, trackedClientState, "after-0.75s");
				ScheduleRepair(realtimeSinceStartup + 2f, instance, trackedClientState, "after-2.0s");
			}
			catch (Exception arg)
			{
				Plugin.LogError($"CruiserDisconnectFix static log callback failed: {arg}");
			}
		}

		private static TrackedClientState ResolveTrackedState(StartOfRound round, ulong clientId)
		{
			if (trackedClients.TryGetValue(clientId, out TrackedClientState value))
			{
				return value;
			}
			int playerObjectNumber;
			PlayerControllerB val = PlayerResolver.Resolve(round, clientId, out playerObjectNumber);
			TrackedClientState trackedClientState = new TrackedClientState
			{
				ClientId = clientId,
				PlayerObjectNumber = playerObjectNumber,
				Player = val
			};
			if ((Object)(object)val != (Object)null)
			{
				trackedClientState.Cruiser = StrongCruiserTracker.FindStrongCruiserMatch(val, playerObjectNumber, clientId);
			}
			trackedClients[clientId] = trackedClientState;
			return trackedClientState;
		}

		private static void ScheduleRepair(float dueTime, StartOfRound round, TrackedClientState state, string phase)
		{
			pendingRepairs.Add(new PendingRepair
			{
				DueTime = dueTime,
				Round = round,
				State = state,
				Phase = phase
			});
		}

		private static void ProcessPendingRepairs(float now)
		{
			for (int num = pendingRepairs.Count - 1; num >= 0; num--)
			{
				PendingRepair pendingRepair = pendingRepairs[num];
				if (!(now < pendingRepair.DueTime))
				{
					pendingRepairs.RemoveAt(num);
					try
					{
						if ((Object)(object)pendingRepair.Round != (Object)null && ((NetworkBehaviour)pendingRepair.Round).IsServer)
						{
							Repair(pendingRepair.Round, pendingRepair.State, useVanillaRelease: true, pendingRepair.Phase);
						}
					}
					catch (Exception arg)
					{
						Plugin.LogWarning($"[{pendingRepair.Phase}] pending repair failed: {arg}");
					}
					if (pendingRepair.Phase == "after-2.0s")
					{
						trackedClients.Remove(pendingRepair.State.ClientId);
					}
				}
			}
		}

		private static void Repair(StartOfRound round, TrackedClientState state, bool useVanillaRelease, string phase)
		{
			TrackedCruiserState trackedCruiserState = DirectSeatRepair.ReleaseDepartingClientSeat(state.ClientId, phase);
			if (state.Cruiser == null && trackedCruiserState != null)
			{
				state.Cruiser = trackedCruiserState;
			}
			if (state.Cruiser != null)
			{
				CruiserTracker.RepairCachedCruiser(state.Cruiser, state.Player, useVanillaRelease, phase);
			}
			CruiserRepair.RepairForDisconnect(state.Player, state.PlayerObjectNumber, state.ClientId, useVanillaRelease, phase);
			if ((Object)(object)state.Player != (Object)null && phase != "vanilla-log-prefix")
			{
				StorageRepair.ForceToNotSpawnedPosition(round, state.Player, phase);
			}
		}
	}
	internal static class DirectSeatRepair
	{
		private static readonly FieldInfo? LockedPlayerField = AccessTools.Field(typeof(InteractTrigger), "lockedPlayer");

		private static readonly MethodInfo? StopSpecialAnimationMethod = AccessTools.Method(typeof(InteractTrigger), "StopSpecialAnimation", (Type[])null, (Type[])null);

		private static readonly MethodInfo? LoseControlMethod = AccessTools.Method(typeof(VehicleController), "LoseControlOfVehicle", (Type[])null, (Type[])null);

		internal static TrackedCruiserState? ReleaseDepartingClientSeat(ulong clientId, string phase)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			VehicleController[] array = Resources.FindObjectsOfTypeAll<VehicleController>();
			foreach (VehicleController val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				InteractTrigger[] array2;
				try
				{
					array2 = ((Component)val).GetComponentsInChildren<InteractTrigger>(true);
				}
				catch
				{
					array2 = Array.Empty<InteractTrigger>();
				}
				InteractTrigger[] array3 = array2;
				foreach (InteractTrigger val2 in array3)
				{
					if ((Object)(object)val2 == (Object)null || (((Object)val2).name.IndexOf("DriverSeat", StringComparison.OrdinalIgnoreCase) < 0 && ((Object)val2).name.IndexOf("PassengerSeat", StringComparison.OrdinalIgnoreCase) < 0))
					{
						continue;
					}
					Transform val3 = null;
					PlayerControllerB val4 = null;
					try
					{
						object? obj2 = LockedPlayerField?.GetValue(val2);
						val3 = (Transform)((obj2 is Transform) ? obj2 : null);
						if ((Object)(object)val3 != (Object)null)
						{
							val4 = ((Component)val3).GetComponent<PlayerControllerB>() ?? ((Component)val3).GetComponentInParent<PlayerControllerB>();
						}
					}
					catch
					{
					}
					if ((Object)(object)val3 == (Object)null || (Object)(object)val4 == (Object)null)
					{
						continue;
					}
					ulong ownerClientId;
					try
					{
						ownerClientId = ((NetworkBehaviour)val4).OwnerClientId;
					}
					catch
					{
						continue;
					}
					if (ownerClientId != clientId)
					{
						continue;
					}
					bool value;
					bool flag = ReflectionUtil.TryGetBool(val2, "interactable", out value) && value;
					Plugin.LogWarning("[" + phase + "] DIRECT SEAT MATCH: " + ((Object)val2).name + "; lockedPlayerTransform=" + ((Object)val3).name + "; lockedPlayer=" + PlayerResolver.SafeName(val4) + "; " + $"OwnerClientId={ownerClientId}; " + $"interactableBefore={flag}.");
					try
					{
						if (LockedPlayerField != null)
						{
							LockedPlayerField.SetValue(val2, null);
						}
					}
					catch (Exception ex)
					{
						Plugin.LogWarning("[" + phase + "] Failed to clear " + ((Object)val2).name + ".lockedPlayer: " + ReflectionUtil.Unwrap(ex));
					}
					ReflectionUtil.TrySetBool(val2, "interactable", value: true);
					if (LoseControlMethod != null)
					{
						try
						{
							LoseControlMethod.Invoke(val, null);
							Plugin.LogInfo("[" + phase + "] DIRECT VehicleController.LoseControlOfVehicle() completed.");
						}
						catch (Exception ex2)
						{
							Plugin.LogWarning("[" + phase + "] DIRECT LoseControlOfVehicle failed: " + ReflectionUtil.Unwrap(ex2));
						}
					}
					Transform val5 = null;
					try
					{
						object? obj5 = LockedPlayerField?.GetValue(val2);
						val5 = (Transform)((obj5 is Transform) ? obj5 : null);
					}
					catch
					{
					}
					bool value2;
					bool flag2 = ReflectionUtil.TryGetBool(val2, "interactable", out value2) && value2;
					Plugin.LogWarning("[" + phase + "] DIRECT SEAT AFTER: " + ((Object)val2).name + "; " + $"lockedPlayerNull={(Object)(object)val5 == (Object)null}; " + $"interactableAfter={flag2}.");
					return new TrackedCruiserState
					{
						Vehicle = val,
						LastMatchedTime = Time.realtimeSinceStartup,
						SeatTriggers = { val2 },
						MatchReasons = { $"{((Object)val2).name}.lockedPlayerTransform.OwnerClientId={clientId}" }
					};
				}
			}
			return null;
		}
	}
	internal static class StrongCruiserTracker
	{
		private static readonly FieldInfo? LockedPlayerField = AccessTools.Field(typeof(InteractTrigger), "lockedPlayer");

		internal static TrackedCruiserState? FindStrongCruiserMatch(PlayerControllerB player, int playerObjectNumber, ulong clientId)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			VehicleController[] array = Resources.FindObjectsOfTypeAll<VehicleController>();
			foreach (VehicleController val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				TrackedCruiserState trackedCruiserState = new TrackedCruiserState
				{
					Vehicle = val,
					LastMatchedTime = Time.realtimeSinceStartup
				};
				foreach (FieldInfo allInstanceField in ReflectionUtil.GetAllInstanceFields(((object)val).GetType()))
				{
					if (!typeof(PlayerControllerB).IsAssignableFrom(allInstanceField.FieldType))
					{
						continue;
					}
					try
					{
						if (allInstanceField.GetValue(val) == player)
						{
							trackedCruiserState.MatchReasons.Add("VehicleController." + allInstanceField.Name + "=player");
						}
					}
					catch
					{
					}
				}
				try
				{
					if (((Component)player).transform.IsChildOf(((Component)val).transform))
					{
						trackedCruiserState.MatchReasons.Add("player transform is parented under Cruiser");
					}
				}
				catch
				{
				}
				InteractTrigger[] array2;
				try
				{
					array2 = ((Component)val).GetComponentsInChildren<InteractTrigger>(true);
				}
				catch
				{
					array2 = Array.Empty<InteractTrigger>();
				}
				InteractTrigger[] array3 = array2;
				foreach (InteractTrigger val2 in array3)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						bool flag = ((Object)val2).name.IndexOf("DriverSeat", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)val2).name.IndexOf("PassengerSeat", StringComparison.OrdinalIgnoreCase) >= 0;
						Transform val3 = null;
						try
						{
							object? obj4 = LockedPlayerField?.GetValue(val2);
							val3 = (Transform)((obj4 is Transform) ? obj4 : null);
						}
						catch
						{
						}
						if (val3 == ((Component)player).transform)
						{
							trackedCruiserState.MatchReasons.Add(((Object)val2).name + ".lockedPlayer=player");
							flag = true;
						}
						if (flag)
						{
							trackedCruiserState.SeatTriggers.Add(val2);
						}
					}
				}
				if (trackedCruiserState.MatchReasons.Count > 0)
				{
					return trackedCruiserState;
				}
			}
			return null;
		}
	}
	internal sealed class TrackedClientState
	{
		internal ulong ClientId;

		internal int PlayerObjectNumber = -1;

		internal PlayerControllerB? Player;

		internal TrackedCruiserState? Cruiser;

		internal float LastSeenTime;
	}
	internal sealed class TrackedCruiserState
	{
		internal VehicleController Vehicle;

		internal readonly List<InteractTrigger> SeatTriggers = new List<InteractTrigger>();

		internal readonly List<string> MatchReasons = new List<string>();

		internal float LastMatchedTime;

		internal bool NetworkSeatReleaseSent;
	}
	internal static class CruiserTracker
	{
		private static readonly FieldInfo? LockedPlayerField = AccessTools.Field(typeof(InteractTrigger), "lockedPlayer");

		private static readonly MethodInfo? StopSpecialAnimationMethod = AccessTools.Method(typeof(InteractTrigger), "StopSpecialAnimation", (Type[])null, (Type[])null);

		private static readonly MethodInfo? CancelSpecialTriggerAnimationsMethod = AccessTools.Method(typeof(PlayerControllerB), "CancelSpecialTriggerAnimations", (Type[])null, (Type[])null);

		private static readonly MethodInfo? StopUsingClientRpcMethod = AccessTools.Method(typeof(InteractTrigger), "StopUsingClientRpc", new Type[1] { typeof(int) }, (Type[])null);

		private static readonly MethodInfo? CancelPlayerInControlClientRpcMethod = AccessTools.Method(typeof(VehicleController), "CancelPlayerInControlOfVehicleClientRpc", new Type[1] { typeof(int) }, (Type[])null);

		private static readonly MethodInfo? RemovePlayerControlClientRpcMethod = AccessTools.Method(typeof(VehicleController), "RemovePlayerControlOfVehicleClientRpc", new Type[2]
		{
			typeof(int),
			typeof(bool)
		}, (Type[])null);

		private static bool _dumpedNetworkMethodDiagnostics;

		private static readonly MethodInfo? LoseControlMethod = AccessTools.Method(typeof(VehicleController), "LoseControlOfVehicle", (Type[])null, (Type[])null);

		private static readonly FieldInfo? CurrentDriverField = AccessTools.Field(typeof(VehicleController), "currentDriver");

		internal static TrackedCruiserState? FindCruiserForPlayer(PlayerControllerB player, int playerObjectNumber, ulong clientId)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)player == (Object)null)
			{
				return null;
			}
			VehicleController[] array = Resources.FindObjectsOfTypeAll<VehicleController>();
			foreach (VehicleController val in array)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				TrackedCruiserState trackedCruiserState = new TrackedCruiserState
				{
					Vehicle = val,
					LastMatchedTime = Time.realtimeSinceStartup
				};
				foreach (FieldInfo allInstanceField in ReflectionUtil.GetAllInstanceFields(((object)val).GetType()))
				{
					string text = allInstanceField.Name.ToLowerInvariant();
					try
					{
						object value = allInstanceField.GetValue(val);
						if (typeof(PlayerControllerB).IsAssignableFrom(allInstanceField.FieldType) && value == player)
						{
							trackedCruiserState.MatchReasons.Add("VehicleController." + allInstanceField.Name + "=player");
						}
						else if (text.Contains("driver") || text.Contains("control") || text.Contains("client") || text.Contains("player") || text.Contains("owner"))
						{
							if (PlayerResolver.NumericEquals(value, clientId))
							{
								trackedCruiserState.MatchReasons.Add($"VehicleController.{allInstanceField.Name}={value} matched clientId");
							}
							if (playerObjectNumber >= 0 && value is int num && num == playerObjectNumber)
							{
								trackedCruiserState.MatchReasons.Add($"VehicleController.{allInstanceField.Name}={num} matched player slot");
							}
						}
					}
					catch
					{
					}
				}
				try
				{
					if (((Component)player).transform.IsChildOf(((Component)val).transform))
					{
						trackedCruiserState.MatchReasons.Add("player transform is parented under Cruiser");
					}
				}
				catch
				{
				}
				InteractTrigger[] array2;
				try
				{
					array2 = ((Component)val).GetComponentsInChildren<InteractTrigger>(true);
				}
				catch
				{
					array2 = Array.Empty<InteractTrigger>();
				}
				InteractTrigger[] array3 = array2;
				foreach (InteractTrigger val2 in array3)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						bool flag = ((Object)val2).name.IndexOf("DriverSeat", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)val2).name.IndexOf("PassengerSeat", StringComparison.OrdinalIgnoreCase) >= 0;
						Transform val3 = null;
						try
						{
							object? obj4 = LockedPlayerField?.GetValue(val2);
							val3 = (Transform)((obj4 is Transform) ? obj4 : null);
						}
						catch
						{
						}
						if (val3 == ((Component)player).transform)
						{
							trackedCruiserState.MatchReasons.Add(((Object)val2).name + ".lockedPlayer=player");
							flag = true;
						}
						if (flag)
						{
							trackedCruiserState.SeatTriggers.Add(val2);
						}
					}
				}
				if (trackedCruiserState.MatchReasons.Count > 0)
				{
					return trackedCruiserState;
				}
			}
			return null;
		}

		internal static void RepairCachedCruiser(TrackedCruiserState cached, PlayerControllerB? player, bool useVanillaRelease, string phase)
		{
			if (cached == null || (Object)(object)cached.Vehicle == (Object)null)
			{
				return;
			}
			Plugin.LogWarning("[" + phase + "] Repairing CACHED Cruiser. Cached reason(s): " + string.Join("; ", cached.MatchReasons));
			InteractTrigger val = null;
			foreach (InteractTrigger seatTrigger in cached.SeatTriggers)
			{
				if (!((Object)(object)seatTrigger == (Object)null) && ((Object)seatTrigger).name.IndexOf("DriverSeat", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					val = seatTrigger;
					break;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				try
				{
					InteractTrigger[] componentsInChildren = ((Component)cached.Vehicle).GetComponentsInChildren<InteractTrigger>(true);
					foreach (InteractTrigger val2 in componentsInChildren)
					{
						if ((Object)(object)val2 != (Object)null && ((Object)val2).name.IndexOf("DriverSeat", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							val = val2;
							break;
						}
					}
				}
				catch
				{
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				Transform lockedTransform = GetLockedTransform(val);
				bool value;
				bool flag = ReflectionUtil.TryGetBool(val, "interactable", out value) && value;
				Plugin.LogWarning("[" + phase + "] CACHED DRIVER SEAT BEFORE: " + $"lockedPlayerNull={(Object)(object)lockedTransform == (Object)null}; " + "lockedPlayer=" + (((Object)(object)lockedTransform == (Object)null) ? "null" : ((Object)lockedTransform).name) + "; " + $"interactable={flag}; enabled={((Behaviour)val).enabled}.");
				if (useVanillaRelease && !cached.NetworkSeatReleaseSent && (Object)(object)player != (Object)null)
				{
					try
					{
						DumpNetworkMethodDiagnosticsOnce();
						InteractTrigger currentTriggerInAnimationWith = player.currentTriggerInAnimationWith;
						Plugin.LogWarning("[" + phase + "] NETWORK SYNC BEFORE CancelSpecialTriggerAnimations: currentTrigger=" + (((Object)(object)currentTriggerInAnimationWith == (Object)null) ? "null" : ((Object)currentTriggerInAnimationWith).name) + "; lockedPlayer=" + (((Object)(object)lockedTransform == (Object)null) ? "null" : ((Object)lockedTransform).name) + "; " + $"inVehicleAnimation={player.inVehicleAnimation}; " + $"inSpecialInteractAnimation={player.inSpecialInteractAnimation}.");
						if (currentTriggerInAnimationWith != val)
						{
							player.currentTriggerInAnimationWith = val;
							Plugin.LogWarning("[" + phase + "] NETWORK SYNC: temporarily set player.currentTriggerInAnimationWith=DriverSeatTrigger.");
						}
						if (LockedPlayerField != null && LockedPlayerField.GetValue(val) == null)
						{
							LockedPlayerField.SetValue(val, ((Component)player).transform);
							Plugin.LogWarning("[" + phase + "] NETWORK SYNC: temporarily restored DriverSeatTrigger.lockedPlayer=" + ((Object)((Component)player).transform).name + ".");
						}
						if (CancelSpecialTriggerAnimationsMethod != null)
						{
							CancelSpecialTriggerAnimationsMethod.Invoke(player, null);
							cached.NetworkSeatReleaseSent = true;
							Plugin.LogWarning("[" + phase + "] NETWORK SYNC: vanilla PlayerControllerB.CancelSpecialTriggerAnimations() completed.");
							int num = -1;
							try
							{
								StartOfRound instance = StartOfRound.Instance;
								if ((Object)(object)instance != (Object)null)
								{
									num = PlayerResolver.GetPlayerObjectNumber(instance, player);
								}
							}
							catch
							{
							}
							if (num < 0)
							{
								try
								{
									num = (int)player.playerClientId;
								}
								catch
								{
								}
							}
							Plugin.LogWarning($"[{phase}] NETWORK RPC RELEASE: resolved playerId={num}.");
							if (num >= 0)
							{
								if (StopUsingClientRpcMethod != null)
								{
									try
									{
										StopUsingClientRpcMethod.Invoke(val, new object[1] { num });
										Plugin.LogWarning("[" + phase + "] NETWORK RPC RELEASE: " + $"DriverSeatTrigger.StopUsingClientRpc({num}) sent.");
									}
									catch (Exception ex)
									{
										Plugin.LogWarning("[" + phase + "] StopUsingClientRpc failed: " + ReflectionUtil.Unwrap(ex));
									}
								}
								else
								{
									Plugin.LogError("[" + phase + "] NETWORK RPC RELEASE FAILED: StopUsingClientRpc(int) not found.");
								}
								if (CancelPlayerInControlClientRpcMethod != null)
								{
									try
									{
										CancelPlayerInControlClientRpcMethod.Invoke(cached.Vehicle, new object[1] { num });
										Plugin.LogWarning("[" + phase + "] NETWORK RPC RELEASE: " + $"CancelPlayerInControlOfVehicleClientRpc({num}) sent.");
									}
									catch (Exception ex2)
									{
										Plugin.LogWarning("[" + phase + "] CancelPlayerInControlOfVehicleClientRpc failed: " + ReflectionUtil.Unwrap(ex2));
									}
								}
								else
								{
									Plugin.LogError("[" + phase + "] NETWORK RPC RELEASE FAILED: CancelPlayerInControlOfVehicleClientRpc(int) not found.");
								}
								bool value2 = false;
								ReflectionUtil.TryGetBool(cached.Vehicle, "ignitionStarted", out value2);
								if (RemovePlayerControlClientRpcMethod != null)
								{
									try
									{
										RemovePlayerControlClientRpcMethod.Invoke(cached.Vehicle, new object[2] { num, value2 });
										Plugin.LogWarning("[" + phase + "] NETWORK RPC RELEASE: RemovePlayerControlOfVehicleClientRpc(" + $"{num}, ignitionStarted={value2}) sent.");
									}
									catch (Exception ex3)
									{
										Plugin.LogWarning("[" + phase + "] RemovePlayerControlOfVehicleClientRpc failed: " + ReflectionUtil.Unwrap(ex3));
									}
								}
								else
								{
									Plugin.LogError("[" + phase + "] NETWORK RPC RELEASE FAILED: RemovePlayerControlOfVehicleClientRpc(int,bool) not found.");
								}
							}
							else
							{
								Plugin.LogError("[" + phase + "] NETWORK RPC RELEASE FAILED: could not resolve departing player slot/playerId.");
							}
						}
						else
						{
							Plugin.LogError("[" + phase + "] NETWORK SYNC FAILED: PlayerControllerB.CancelSpecialTriggerAnimations() was not found.");
						}
					}
					catch (Exception ex4)
					{
						Plugin.LogWarning("[" + phase + "] NETWORK SYNC CancelSpecialTriggerAnimations failed: " + ReflectionUtil.Unwrap(ex4));
					}
					finally
					{
						try
						{
							if (LockedPlayerField != null && LockedPlayerField.GetValue(val) == ((Component)player).transform)
							{
								LockedPlayerField.SetValue(val, null);
							}
						}
						catch
						{
						}
					}
				}
			}
			if (useVanillaRelease && LoseControlMethod != null)
			{
				try
				{
					LoseControlMethod.Invoke(cached.Vehicle, null);
					Plugin.LogInfo("[" + phase + "] VehicleController.LoseControlOfVehicle() called.");
				}
				catch (Exception ex5)
				{
					Plugin.LogWarning("[" + phase + "] VehicleController.LoseControlOfVehicle failed: " + ReflectionUtil.Unwrap(ex5));
				}
			}
			if (CurrentDriverField != null)
			{
				try
				{
					object? value3 = CurrentDriverField.GetValue(cached.Vehicle);
					PlayerControllerB val3 = (PlayerControllerB)((value3 is PlayerControllerB) ? value3 : null);
					Plugin.LogWarning("[" + phase + "] VEHICLE CURRENT DRIVER BEFORE FORCE CLEAR: " + (((Object)(object)val3 == (Object)null) ? "null" : PlayerResolver.SafeName(val3)) + ".");
					CurrentDriverField.SetValue(cached.Vehicle, null);
					object? value4 = CurrentDriverField.GetValue(cached.Vehicle);
					PlayerControllerB val4 = (PlayerControllerB)((value4 is PlayerControllerB) ? value4 : null);
					Plugin.LogWarning("[" + phase + "] VEHICLE CURRENT DRIVER AFTER FORCE CLEAR: " + (((Object)(object)val4 == (Object)null) ? "null" : PlayerResolver.SafeName(val4)) + ".");
				}
				catch (Exception ex6)
				{
					Plugin.LogWarning("[" + phase + "] Failed to force VehicleController.currentDriver=null: " + ReflectionUtil.Unwrap(ex6));
				}
			}
			if (!((Object)(object)val != (Object)null))
			{
				return;
			}
			try
			{
				if (LockedPlayerField != null)
				{
					LockedPlayerField.SetValue(val, null);
				}
			}
			catch (Exception ex7)
			{
				Plugin.LogWarning("[" + phase + "] Failed to force DriverSeatTrigger.lockedPlayer=null: " + ReflectionUtil.Unwrap(ex7));
			}
			try
			{
				((Behaviour)val).enabled = true;
			}
			catch
			{
			}
			ReflectionUtil.TrySetBool(val, "interactable", value: true);
			ReflectionUtil.TrySetBool(val, "isPlayingSpecialAnimation", value: false);
			Transform lockedTransform2 = GetLockedTransform(val);
			bool value5;
			bool flag2 = ReflectionUtil.TryGetBool(val, "interactable", out value5) && value5;
			Plugin.LogWarning("[" + phase + "] CACHED DRIVER SEAT AFTER: " + $"lockedPlayerNull={(Object)(object)lockedTransform2 == (Object)null}; " + $"interactable={flag2}; enabled={((Behaviour)val).enabled}; " + $"vanillaCancelPathCalled={cached.NetworkSeatReleaseSent}.");
			LogRelevantDriverSeatBools(val, phase);
		}

		private static Transform? GetLockedTransform(InteractTrigger trigger)
		{
			try
			{
				object? obj = LockedPlayerField?.GetValue(trigger);
				return (Transform?)((obj is Transform) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		private static void DumpNetworkMethodDiagnosticsOnce()
		{
			if (_dumpedNetworkMethodDiagnostics)
			{
				return;
			}
			_dumpedNetworkMethodDiagnostics = true;
			try
			{
				List<string> list = new List<string>();
				MethodInfo[] methods = typeof(InteractTrigger).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo in methods)
				{
					string text = methodInfo.Name.ToLowerInvariant();
					if (text.Contains("rpc") || text.Contains("anim") || text.Contains("vehicle") || text.Contains("interact") || text.Contains("used"))
					{
						ParameterInfo[] parameters = methodInfo.GetParameters();
						string item = methodInfo.Name + "(" + string.Join(", ", Array.ConvertAll(parameters, (ParameterInfo p) => p.ParameterType.Name + " " + p.Name)) + ")";
						if (!list.Contains(item))
						{
							list.Add(item);
						}
					}
				}
				list.Sort(StringComparer.Ordinal);
				Plugin.LogWarning("CruiserDisconnectFix DIAGNOSTIC InteractTrigger methods: " + string.Join(" | ", list));
			}
			catch (Exception ex)
			{
				Plugin.LogWarning("CruiserDisconnectFix failed to dump InteractTrigger methods: " + ReflectionUtil.Unwrap(ex));
			}
			try
			{
				List<string> list2 = new List<string>();
				MethodInfo[] methods = typeof(VehicleController).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo methodInfo2 in methods)
				{
					string text2 = methodInfo2.Name.ToLowerInvariant();
					if (text2.Contains("rpc") || text2.Contains("control") || text2.Contains("driver") || text2.Contains("vehicle") || text2.Contains("player"))
					{
						ParameterInfo[] parameters2 = methodInfo2.GetParameters();
						string item2 = methodInfo2.Name + "(" + string.Join(", ", Array.ConvertAll(parameters2, (ParameterInfo p) => p.ParameterType.Name + " " + p.Name)) + ")";
						if (!list2.Contains(item2))
						{
							list2.Add(item2);
						}
					}
				}
				list2.Sort(StringComparer.Ordinal);
				Plugin.LogWarning("CruiserDisconnectFix DIAGNOSTIC VehicleController methods: " + string.Join(" | ", list2));
			}
			catch (Exception ex2)
			{
				Plugin.LogWarning("CruiserDisconnectFix failed to dump VehicleController methods: " + ReflectionUtil.Unwrap(ex2));
			}
		}

		private static void LogRelevantDriverSeatBools(InteractTrigger trigger, string phase)
		{
			try
			{
				List<string> list = new List<string>();
				foreach (FieldInfo allInstanceField in ReflectionUtil.GetAllInstanceFields(((object)trigger).GetType()))
				{
					if (allInstanceField.FieldType != typeof(bool))
					{
						continue;
					}
					string text = allInstanceField.Name.ToLowerInvariant();
					if (text.Contains("interact") || text.Contains("disable") || text.Contains("lock") || text.Contains("anim") || text.Contains("vehicle") || text.Contains("used") || text.Contains("hold"))
					{
						try
						{
							list.Add($"{allInstanceField.Name}={allInstanceField.GetValue(trigger)}");
						}
						catch
						{
						}
					}
				}
				Plugin.LogInfo("[" + phase + "] DriverSeatTrigger relevant bools: " + ((list.Count == 0) ? "(none found)" : string.Join(", ", list)));
			}
			catch
			{
			}
		}
	}
	internal static class PlayerResolver
	{
		internal static PlayerControllerB? Resolve(StartOfRound round, ulong clientId, out int playerObjectNumber)
		{
			playerObjectNumber = -1;
			if ((Object)(object)round == (Object)null || round.allPlayerScripts == null)
			{
				return null;
			}
			for (int i = 0; i < round.allPlayerScripts.Length; i++)
			{
				PlayerControllerB val = round.allPlayerScripts[i];
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				try
				{
					if (((NetworkBehaviour)val).OwnerClientId == clientId)
					{
						playerObjectNumber = i;
						Plugin.LogInfo($"Resolved clientId={clientId} from PlayerControllerB.OwnerClientId at slot {i}.");
						return val;
					}
				}
				catch
				{
				}
			}
			for (int j = 0; j < round.allPlayerScripts.Length; j++)
			{
				PlayerControllerB val2 = round.allPlayerScripts[j];
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				foreach (FieldInfo allInstanceField in ReflectionUtil.GetAllInstanceFields(((object)val2).GetType()))
				{
					string text = allInstanceField.Name.ToLowerInvariant();
					if (!text.Contains("client") && !text.Contains("owner"))
					{
						continue;
					}
					try
					{
						object value = allInstanceField.GetValue(val2);
						if (NumericEquals(value, clientId))
						{
							playerObjectNumber = j;
							Plugin.LogInfo($"Resolved clientId={clientId} from PlayerControllerB.{allInstanceField.Name}={value} at slot {j}.");
							return val2;
						}
					}
					catch
					{
					}
				}
			}
			return null;
		}

		internal static int GetPlayerObjectNumber(StartOfRound round, PlayerControllerB player)
		{
			if ((Object)(object)round == (Object)null || round.allPlayerScripts == null || (Object)(object)player == (Object)null)
			{
				return -1;
			}
			for (int i = 0; i < round.allPlayerScripts.Length; i++)
			{
				if (round.allPlayerScripts[i] == player)
				{
					return i;
				}
			}
			return -1;
		}

		internal static bool NumericEquals(object? value, ulong target)
		{
			if (target == ulong.MaxValue)
			{
				return false;
			}
			if (value is ulong num)
			{
				return num == target;
			}
			if (value is uint num2)
			{
				return num2 == target;
			}
			if (value is long num3)
			{
				if (num3 >= 0)
				{
					return num3 == (long)target;
				}
				return false;
			}
			if (value is int num4)
			{
				if (num4 >= 0)
				{
					return (ulong)num4 == target;
				}
				return false;
			}
			if (value is ushort num5)
			{
				return num5 == target;
			}
			if (value is short num6)
			{
				if (num6 >= 0)
				{
					return (ulong)num6 == target;
				}
				return false;
			}
			if (value is byte b)
			{
				return b == target;
			}
			return false;
		}

		internal static string SafeName(PlayerControllerB player)
		{
			try
			{
				return string.IsNullOrEmpty(player.playerUsername) ? "unknown" : player.playerUsername;
			}
			catch
			{
				return "unknown";
			}
		}
	}
	internal static class StorageRepair
	{
		private static readonly FieldInfo? NotSpawnedPositionField = AccessTools.Field(typeof(StartOfRound), "notSpawnedPosition");

		internal static void ForceToNotSpawnedPosition(StartOfRound round, PlayerControllerB player, string phase)
		{
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)round == (Object)null || (Object)(object)player == (Object)null)
			{
				return;
			}
			Transform val = null;
			try
			{
				object? obj = NotSpawnedPositionField?.GetValue(round);
				val = (Transform)((obj is Transform) ? obj : null);
			}
			catch
			{
			}
			if ((Object)(object)val == (Object)null)
			{
				Plugin.LogWarning("[" + phase + "] StartOfRound.notSpawnedPosition was not found.");
				return;
			}
			try
			{
				ReflectionUtil.TrySetBool(player, "inSpecialInteractAnimation", value: false);
				ReflectionUtil.TrySetBool(player, "inAnimationWithEnemy", value: false);
				ReflectionUtil.TrySetBool(player, "inVehicleAnimation", value: false);
				ReflectionUtil.TrySetBool(player, "isClimbingLadder", value: false);
				ReflectionUtil.TryClearTransformReference(player, "physicsParent");
				ReflectionUtil.TryClearTransformReference(player, "overridePhysicsParent");
				((Component)player).transform.SetParent((Transform)null, true);
				bool flag = TryInvokeTeleportPlayer(player, val.position);
				if (!flag)
				{
					((Component)player).transform.position = val.position;
					((Component)player).transform.rotation = val.rotation;
				}
				Plugin.LogWarning("[" + phase + "] Forced disconnected player '" + PlayerResolver.SafeName(player) + "' " + $"to notSpawnedPosition {val.position}. " + "Teleport=" + (flag ? "vanilla" : "transform fallback") + ".");
			}
			catch (Exception arg)
			{
				Plugin.LogWarning($"[{phase}] Storage repair failed: {arg}");
			}
		}

		private static bool TryInvokeTeleportPlayer(PlayerControllerB player, Vector3 position)
		{
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			MethodInfo[] methods = typeof(PlayerControllerB).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name != "TeleportPlayer")
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 0 || parameters[0].ParameterType != typeof(Vector3))
				{
					continue;
				}
				try
				{
					object[] array = new object[parameters.Length];
					array[0] = position;
					for (int j = 1; j < parameters.Length; j++)
					{
						Type parameterType = parameters[j].ParameterType;
						if (parameters[j].HasDefaultValue)
						{
							array[j] = parameters[j].DefaultValue;
						}
						else if (parameterType == typeof(bool))
						{
							array[j] = j == parameters.Length - 1;
						}
						else if (parameterType == typeof(float))
						{
							array[j] = 0f;
						}
						else if (parameterType.IsValueType)
						{
							array[j] = Activator.CreateInstance(parameterType);
						}
						else
						{
							array[j] = null;
						}
					}
					methodInfo.Invoke(player, array);
					return true;
				}
				catch
				{
				}
			}
			return false;
		}
	}
	internal static class CruiserRepair
	{
		private static readonly FieldInfo? LockedPlayerField = AccessTools.Field(typeof(InteractTrigger), "lockedPlayer");

		private static readonly MethodInfo? StopSpecialAnimationMethod = AccessTools.Method(typeof(InteractTrigger), "StopSpecialAnimation", (Type[])null, (Type[])null);

		private static readonly MethodInfo? LoseControlMethod = AccessTools.Method(typeof(VehicleController), "LoseControlOfVehicle", (Type[])null, (Type[])null);

		private static readonly FieldInfo? CurrentDriverField = AccessTools.Field(typeof(VehicleController), "currentDriver");

		internal static void RepairForDisconnect(PlayerControllerB? leavingPlayer, int playerObjectNumber, ulong clientId, bool useVanillaRelease, string phase)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			VehicleController[] array = Resources.FindObjectsOfTypeAll<VehicleController>();
			int num = 0;
			int num2 = 0;
			VehicleController[] array2 = array;
			foreach (VehicleController val in array2)
			{
				if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					continue;
				}
				List<string> list = FindMatchingState(val, leavingPlayer, playerObjectNumber, clientId);
				InteractTrigger[] array3;
				try
				{
					array3 = ((Component)val).GetComponentsInChildren<InteractTrigger>(true);
				}
				catch
				{
					array3 = Array.Empty<InteractTrigger>();
				}
				InteractTrigger[] array4 = array3;
				foreach (InteractTrigger val2 in array4)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						Transform lockedTransform = GetLockedTransform(val2);
						if ((Object)(object)leavingPlayer != (Object)null && lockedTransform == ((Component)leavingPlayer).transform)
						{
							list.Add(((Object)val2).name + ".lockedPlayer matched departing player");
						}
					}
				}
				if (list.Count == 0)
				{
					continue;
				}
				num++;
				Plugin.LogWarning("[" + phase + "] Cruiser matched disconnected player/client. Reason(s): " + string.Join("; ", list));
				if ((Object)(object)leavingPlayer != (Object)null)
				{
					array4 = array3;
					foreach (InteractTrigger val3 in array4)
					{
						if ((Object)(object)val3 == (Object)null || GetLockedTransform(val3) != ((Component)leavingPlayer).transform)
						{
							continue;
						}
						if (useVanillaRelease && StopSpecialAnimationMethod != null)
						{
							try
							{
								StopSpecialAnimationMethod.Invoke(val3, null);
								Plugin.LogInfo("[" + phase + "] Called " + ((Object)val3).name + ".StopSpecialAnimation().");
							}
							catch (Exception ex)
							{
								Plugin.LogWarning("[" + phase + "] StopSpecialAnimation failed: " + ReflectionUtil.Unwrap(ex));
							}
						}
						ClearLockedPlayer(val3, leavingPlayer);
						ReflectionUtil.TrySetBool(val3, "interactable", value: true);
						num2++;
					}
				}
				if (useVanillaRelease && LoseControlMethod != null)
				{
					try
					{
						LoseControlMethod.Invoke(val, null);
						Plugin.LogInfo("[" + phase + "] Called VehicleController.LoseControlOfVehicle().");
					}
					catch (Exception ex2)
					{
						Plugin.LogWarning("[" + phase + "] LoseControlOfVehicle failed: " + ReflectionUtil.Unwrap(ex2));
					}
				}
				if (CurrentDriverField != null)
				{
					try
					{
						CurrentDriverField.SetValue(val, null);
					}
					catch (Exception ex3)
					{
						Plugin.LogWarning("[" + phase + "] Generic scan failed to clear currentDriver: " + ReflectionUtil.Unwrap(ex3));
					}
				}
				array4 = array3;
				foreach (InteractTrigger val4 in array4)
				{
					if (!((Object)(object)val4 == (Object)null))
					{
						if (((Object)val4).name.IndexOf("DriverSeat", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							ReflectionUtil.TrySetBool(val4, "interactable", value: true);
							ReflectionUtil.TrySetBool(val4, "isPlayingSpecialAnimation", value: false);
						}
						else if (((Object)val4).name.IndexOf("PassengerSeat", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							ReflectionUtil.TrySetBool(val4, "interactable", value: true);
						}
					}
				}
			}
			Plugin.LogInfo($"[{phase}] Cruiser scan complete: implicated={num}, " + $"releasedTriggers={num2}.");
		}

		private static List<string> FindMatchingState(VehicleController vehicle, PlayerControllerB? leavingPlayer, int playerObjectNumber, ulong clientId)
		{
			List<string> list = new List<string>();
			foreach (FieldInfo allInstanceField in ReflectionUtil.GetAllInstanceFields(((object)vehicle).GetType()))
			{
				string text = allInstanceField.Name.ToLowerInvariant();
				try
				{
					object value = allInstanceField.GetValue(vehicle);
					if ((Object)(object)leavingPlayer != (Object)null && typeof(PlayerControllerB).IsAssignableFrom(allInstanceField.FieldType) && value == leavingPlayer)
					{
						list.Add(allInstanceField.Name + "=departing PlayerControllerB");
					}
					else if (text.Contains("driver") || text.Contains("control") || text.Contains("client") || text.Contains("player") || text.Contains("owner"))
					{
						if (PlayerResolver.NumericEquals(value, clientId))
						{
							list.Add($"{allInstanceField.Name}={value} matched clientId");
						}
						if (playerObjectNumber >= 0 && value is int num && num == playerObjectNumber)
						{
							list.Add($"{allInstanceField.Name}={num} matched playerObjectNumber");
						}
					}
				}
				catch
				{
				}
			}
			return list;
		}

		private static Transform? GetLockedTransform(InteractTrigger trigger)
		{
			try
			{
				object? obj = LockedPlayerField?.GetValue(trigger);
				return (Transform?)((obj is Transform) ? obj : null);
			}
			catch
			{
				return null;
			}
		}

		private static void ClearLockedPlayer(InteractTrigger trigger, PlayerControllerB player)
		{
			if (LockedPlayerField == null)
			{
				return;
			}
			try
			{
				if (LockedPlayerField.GetValue(trigger) == ((Component)player).transform)
				{
					LockedPlayerField.SetValue(trigger, null);
					Plugin.LogInfo("Cleared " + ((Object)trigger).name + ".lockedPlayer.");
				}
			}
			catch
			{
			}
		}
	}
	internal static class ReflectionUtil
	{
		internal static IEnumerable<FieldInfo> GetAllInstanceFields(Type type)
		{
			for (Type current = type; current != null; current = current.BaseType)
			{
				FieldInfo[] fields;
				try
				{
					fields = current.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				}
				catch
				{
					continue;
				}
				FieldInfo[] array = fields;
				for (int i = 0; i < array.Length; i++)
				{
					yield return array[i];
				}
			}
		}

		internal static void ClearPlayerReferences(object obj, PlayerControllerB player)
		{
			foreach (FieldInfo allInstanceField in GetAllInstanceFields(obj.GetType()))
			{
				if (!typeof(PlayerControllerB).IsAssignableFrom(allInstanceField.FieldType) || allInstanceField.IsInitOnly)
				{
					continue;
				}
				try
				{
					if (allInstanceField.GetValue(obj) == player)
					{
						allInstanceField.SetValue(obj, null);
						Plugin.LogInfo("Cleared stale " + obj.GetType().Name + "." + allInstanceField.Name + " reference.");
					}
				}
				catch
				{
				}
			}
		}

		internal static bool TryGetBool(object obj, string name, out bool value)
		{
			value = false;
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), name);
				if (fieldInfo != null && fieldInfo.FieldType == typeof(bool))
				{
					value = (bool)fieldInfo.GetValue(obj);
					return true;
				}
				PropertyInfo propertyInfo = AccessTools.Property(obj.GetType(), name);
				if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool) && propertyInfo.CanRead)
				{
					value = (bool)propertyInfo.GetValue(obj, null);
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		internal static void TrySetBool(object obj, string name, bool value)
		{
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), name);
				if (fieldInfo != null && fieldInfo.FieldType == typeof(bool) && !fieldInfo.IsInitOnly)
				{
					fieldInfo.SetValue(obj, value);
					return;
				}
				PropertyInfo propertyInfo = AccessTools.Property(obj.GetType(), name);
				if (propertyInfo != null && propertyInfo.PropertyType == typeof(bool) && propertyInfo.CanWrite)
				{
					propertyInfo.SetValue(obj, value, null);
				}
			}
			catch
			{
			}
		}

		internal static void TryClearTransformReference(object obj, string name)
		{
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(obj.GetType(), name);
				if (fieldInfo != null && typeof(Transform).IsAssignableFrom(fieldInfo.FieldType) && !fieldInfo.IsInitOnly)
				{
					fieldInfo.SetValue(obj, null);
				}
			}
			catch
			{
			}
		}

		internal static string Unwrap(Exception ex)
		{
			if (ex is TargetInvocationException { InnerException: not null } ex2)
			{
				return ex2.InnerException.ToString();
			}
			return ex.ToString();
		}
	}
}