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 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("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 trackedClients = new Dictionary(); private static readonly Dictionary recentDisconnects = new Dictionary(); private static readonly List pendingRepairs = new List(); 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(); 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(true); } catch { array2 = Array.Empty(); } 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() ?? ((Component)val3).GetComponentInParent(); } } 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(); 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(true); } catch { array2 = Array.Empty(); } 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 SeatTriggers = new List(); internal readonly List MatchReasons = new List(); 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(); 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(true); } catch { array2 = Array.Empty(); } 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(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 list = new List(); 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 list2 = new List(); 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 list = new List(); 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(); 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 list = FindMatchingState(val, leavingPlayer, playerObjectNumber, clientId); InteractTrigger[] array3; try { array3 = ((Component)val).GetComponentsInChildren(true); } catch { array3 = Array.Empty(); } 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 FindMatchingState(VehicleController vehicle, PlayerControllerB? leavingPlayer, int playerObjectNumber, ulong clientId) { List list = new List(); 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 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(); } } }