Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CruiserDisconnectFix v1.0.5
BepInEx/plugins/CruiserDisconnectFix/CruiserDisconnectFix.dll
Decompiled 4 hours agousing 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(); } } }