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 FidelityReviveFix v0.1.5
BepInEx/plugins/FidelityReviveFix/FidelityReviveFix.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace FidelityReviveFix; [BepInPlugin("AngelcoMilk.FidelityReviveFix", "FidelityReviveFix", "0.1.5")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "AngelcoMilk.FidelityReviveFix"; public const string PluginName = "FidelityReviveFix"; public const string PluginVersion = "0.1.5"; internal static Plugin Instance; internal static ManualLogSource Log; private Harmony _harmony; private float _nextCapabilityPublishTime; private void Awake() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); InstantReviveController.Reset(); ClientReviveProtection.Reset(); MultiplayerCapabilitySync.PublishLocalCapabilities(); _harmony = new Harmony("AngelcoMilk.FidelityReviveFix"); _harmony.PatchAll(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"FidelityReviveFix 0.1.5 loaded."); } private void Update() { if (!(Time.time < _nextCapabilityPublishTime)) { _nextCapabilityPublishTime = Time.time + 10f; MultiplayerCapabilitySync.PublishLocalCapabilities(); } } private void OnDestroy() { InstantReviveController.Reset(); ClientReviveProtection.Reset(); MultiplayerCapabilitySync.Clear(); if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } internal Coroutine StartReviveRoutine(IEnumerator routine) { return ((MonoBehaviour)this).StartCoroutine(routine); } internal Coroutine StartProtectionRoutine(PlayerAvatar player) { return ((MonoBehaviour)this).StartCoroutine(ClientReviveProtection.Run(player)); } internal Coroutine StartProtectionRoutine(PlayerAvatar player, bool hasExpectedPosition, Vector3 expectedPosition, string reason) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) return ((MonoBehaviour)this).StartCoroutine(ClientReviveProtection.Run(player, hasExpectedPosition, expectedPosition, reason)); } } internal enum ClientProtectionMode { Auto, Always, Off } internal enum ReviveTimingPolicy { Auto, Instant, StableDelayed } internal enum UnknownClientPolicy { HostLocal, StableDelayed } internal static class ModConfig { internal static ConfigEntry<bool> EnableInstantExtractionRevive; internal static ConfigEntry<ReviveTimingPolicy> ReviveTimingPolicyEntry; internal static ConfigEntry<UnknownClientPolicy> UnknownClientPolicyEntry; internal static ConfigEntry<float> StableDelayedReviveDelay; internal static ConfigEntry<bool> EnableFallbackExtractionDetection; internal static ConfigEntry<ClientProtectionMode> REPOFidelityClientProtection; internal static ConfigEntry<float> PostReviveProtectionWindow; internal static ConfigEntry<bool> DebugLogging; internal static void Bind(ConfigFile config) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown EnableInstantExtractionRevive = config.Bind<bool>("Instant Revive", "Enable Instant Extraction Revive", true, "Host/singleplayer only. When a triggered death head enters an extraction point, immediately triggers the vanilla revive."); ReviveTimingPolicyEntry = config.Bind<ReviveTimingPolicy>("Instant Revive", "Revive Timing Policy", ReviveTimingPolicy.Auto, "Auto uses the revived player's reported REPOFidelity status in multiplayer, or local REPOFidelity status in singleplayer. Instant revives during PlayerDeathHead.Update. StableDelayed waits briefly for vanilla camera/spectate state before reviving."); UnknownClientPolicyEntry = config.Bind<UnknownClientPolicy>("Instant Revive", "Unknown Client Policy", UnknownClientPolicy.HostLocal, "HostLocal uses the host/local policy when a remote player did not report FidelityReviveFix capabilities. StableDelayed conservatively delays unknown remote players."); StableDelayedReviveDelay = config.Bind<float>("Instant Revive", "Stable Delayed Revive Delay", 0.75f, new ConfigDescription("Seconds to wait before automatic revive when StableDelayed timing is active.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), new object[0])); EnableFallbackExtractionDetection = config.Bind<bool>("Instant Revive", "Enable Fallback Extraction Detection", false, "Diagnostic compatibility fallback. Disabled by default so revive follows vanilla extraction point state. When enabled, an extra extraction-volume scan is allowed only near known extraction points."); REPOFidelityClientProtection = config.Bind<ClientProtectionMode>("REPOFidelity Compatibility", "REPOFidelity Client Protection", ClientProtectionMode.Auto, "Auto enables local protection only when Vippy.REPOFidelity is loaded. Always runs it after every revive. Off disables the client-side compatibility protection."); PostReviveProtectionWindow = config.Bind<float>("REPOFidelity Compatibility", "Post Revive Protection Window", 0.75f, new ConfigDescription("Seconds to keep refreshing non-destructive local camera/audio/post-processing state after vanilla ReviveRPC returns.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), new object[0])); DebugLogging = config.Bind<bool>("Diagnostics", "Debug Logging", false, "Write instant revive and REPOFidelity client-protection details to the BepInEx log."); } internal static float SafeProtectionWindow() { if (PostReviveProtectionWindow != null) { return Mathf.Clamp(PostReviveProtectionWindow.Value, 0.1f, 3f); } return 0.75f; } internal static float SafeStableDelayedReviveDelay() { if (StableDelayedReviveDelay != null) { return Mathf.Clamp(StableDelayedReviveDelay.Value, 0.1f, 3f); } return 0.75f; } internal static ReviveTimingPolicy SafeReviveTimingPolicy() { if (ReviveTimingPolicyEntry != null) { return ReviveTimingPolicyEntry.Value; } return ReviveTimingPolicy.Auto; } internal static UnknownClientPolicy SafeUnknownClientPolicy() { if (UnknownClientPolicyEntry != null) { return UnknownClientPolicyEntry.Value; } return UnknownClientPolicy.HostLocal; } internal static ReviveTimingPolicy EffectiveReviveTimingPolicy(PlayerAvatar targetPlayer, out string reason) { ReviveTimingPolicy reviveTimingPolicy = SafeReviveTimingPolicy(); if (reviveTimingPolicy != 0) { reason = "forced=" + reviveTimingPolicy; return reviveTimingPolicy; } return MultiplayerCapabilitySync.ResolveReviveTimingFor(targetPlayer, out reason); } internal static ReviveTimingPolicy EffectiveReviveTimingPolicy() { string reason; return EffectiveReviveTimingPolicy(null, out reason); } } internal static class InstantReviveController { private sealed class ReviveState { internal bool ReviveCompleted; internal bool WasTriggered; internal bool ReviveQueued; internal float FirstInsideTime; internal float LastAttemptTime; internal float NextDebugLogTime; } private const float RetryIntervalSeconds = 0.12f; private const float DebugLogIntervalSeconds = 0.75f; private static readonly Dictionary<int, ReviveState> States = new Dictionary<int, ReviveState>(); private static readonly Collider[] OverlapBuffer = (Collider[])(object)new Collider[32]; private static readonly FieldInfo PlayerDeathHeadTriggeredField = typeof(PlayerDeathHead).GetField("triggered", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerDeathHeadRoomVolumeCheckField = typeof(PlayerDeathHead).GetField("roomVolumeCheck", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerDeathHeadInExtractionPointField = typeof(PlayerDeathHead).GetField("inExtractionPoint", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerDeathHeadPhysGrabObjectField = typeof(PlayerDeathHead).GetField("physGrabObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarDeadSetField = typeof(PlayerAvatar).GetField("deadSet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarIsDisabledField = typeof(PlayerAvatar).GetField("isDisabled", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RoomVolumeCheckInExtractionPointField = typeof(RoomVolumeCheck).GetField("inExtractionPoint", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RoomVolumeCheckMaskField = typeof(RoomVolumeCheck).GetField("Mask", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RoundDirectorExtractionPointCurrentField = typeof(RoundDirector).GetField("extractionPointCurrent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo RoundDirectorExtractionPointListField = typeof(RoundDirector).GetField("extractionPointList", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static void Reset() { States.Clear(); } internal static void ProcessHead(PlayerDeathHead head) { if (!IsHostOrSingleplayer() || ModConfig.EnableInstantExtractionRevive == null || !ModConfig.EnableInstantExtractionRevive.Value || (Object)(object)head == (Object)null) { return; } int instanceID = ((Object)head).GetInstanceID(); if (!States.TryGetValue(instanceID, out var value)) { value = new ReviveState(); States[instanceID] = value; } if (!IsTriggered(head)) { value.ReviveCompleted = false; value.WasTriggered = false; value.ReviveQueued = false; value.FirstInsideTime = 0f; value.LastAttemptTime = 0f; return; } if (!value.WasTriggered) { value.ReviveCompleted = false; value.WasTriggered = true; value.ReviveQueued = false; value.FirstInsideTime = 0f; value.LastAttemptTime = 0f; } if (value.ReviveCompleted || value.ReviveQueued) { return; } if (value.FirstInsideTime <= 0f) { value.FirstInsideTime = Time.time; } if (!IsInsideExtractionPoint(head, out var roomCheckInside, out var headInside, out var fallbackInside)) { DebugLogState(value, "Revive scan: host=True, triggered=True, roomCheck=" + roomCheckInside + ", headField=" + headInside + ", fallback=" + fallbackInside + ", inside=False."); } else { if (value.LastAttemptTime > 0f && Time.time - value.LastAttemptTime < 0.12f) { return; } if (value.LastAttemptTime <= 0f) { value.NextDebugLogTime = 0f; } value.LastAttemptTime = Time.time; string reason; ReviveTimingPolicy reviveTimingPolicy = ModConfig.EffectiveReviveTimingPolicy(head.playerAvatar, out reason); DebugLogState(value, string.Concat("Revive timing selected: policy=", reviveTimingPolicy, ", reason=", reason, ", target=", MultiplayerCapabilitySync.DescribePlayer(head.playerAvatar), ", firstInsideAge=", (Time.time - value.FirstInsideTime).ToString("F3"), ".")); if (reviveTimingPolicy != ReviveTimingPolicy.Instant && !((Object)(object)Plugin.Instance == (Object)null)) { value.ReviveQueued = true; DebugLogState(value, string.Concat("queued stable delayed revive. policy=", reviveTimingPolicy, ", repoFidelityLoaded=", ClientReviveProtection.IsREPOFidelityLoaded(), ", delay=", ModConfig.SafeStableDelayedReviveDelay().ToString("F2"), ", roomCheck=", roomCheckInside, ", headField=", headInside, ", fallback=", fallbackInside, ".")); try { Plugin.Instance.StartReviveRoutine(RunStableDelayedRevive(head, value)); return; } catch (Exception ex) { value.ReviveQueued = false; LogException("queue stable delayed revive", ex); return; } } ExecuteReviveAttempt(head, value, roomCheckInside, headInside, fallbackInside, "instant revive", requirePreflight: false, reviveTimingPolicy, reason); } } private static IEnumerator RunStableDelayedRevive(PlayerDeathHead head, ReviveState state) { float reviveAllowedAt = Time.time + ModConfig.SafeStableDelayedReviveDelay(); bool roomCheckInside; bool headInside; bool fallbackInside; while (true) { if ((Object)(object)head == (Object)null || ModConfig.EnableInstantExtractionRevive == null || !ModConfig.EnableInstantExtractionRevive.Value || !IsHostOrSingleplayer() || !IsTriggered(head) || (Object)(object)head.playerAvatar == (Object)null) { if (state != null) { state.ReviveQueued = false; } DebugLog("stable delayed revive aborted before attempt; death head/player state changed."); yield break; } if (!IsInsideExtractionPoint(head, out roomCheckInside, out headInside, out fallbackInside)) { if (state != null) { state.ReviveQueued = false; } DebugLogState(state, "stable delayed revive aborted; head left extraction point. roomCheck=" + roomCheckInside + ", headField=" + headInside + ", fallback=" + fallbackInside + "."); yield break; } if (Time.time < reviveAllowedAt) { DebugLogState(state, "stable delayed revive waiting for delay. remaining=" + Mathf.Max(0f, reviveAllowedAt - Time.time).ToString("F2") + ", roomCheck=" + roomCheckInside + ", headField=" + headInside + "."); yield return null; } else { if (ReviveDiagnostics.AreCriticalDependenciesReadyNoLog(head.playerAvatar)) { break; } DebugLogState(state, "stable delayed revive waiting; vanilla ReviveRPC dependencies are not ready. " + ReviveDiagnostics.BuildReadinessReport(head.playerAvatar, ready: false)); yield return null; } } if (state != null) { state.ReviveQueued = false; } ExecuteReviveAttempt(policy: ModConfig.EffectiveReviveTimingPolicy(head.playerAvatar, out var policyReason), head: head, state: state, roomCheckInside: roomCheckInside, headInside: headInside, fallbackInside: fallbackInside, stage: "stable delayed revive", requirePreflight: false, policyReason: policyReason); } private static void ExecuteReviveAttempt(PlayerDeathHead head, ReviveState state, bool roomCheckInside, bool headInside, bool fallbackInside, string stage, bool requirePreflight, ReviveTimingPolicy policy, string policyReason) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)head == (Object)null || (Object)(object)head.playerAvatar == (Object)null) { return; } PlayerAvatar playerAvatar = head.playerAvatar; Vector3 position; bool hasExpectedPosition = TryGetExpectedRevivePosition(head, out position); if (requirePreflight && !ReviveDiagnostics.AreCriticalDependenciesReady(playerAvatar, stage + " preflight")) { DebugLogState(state, stage + ": revive delayed; vanilla ReviveRPC dependencies are not ready."); return; } SetExtractionState(head, value: true); if (TryRevive(head, state, roomCheckInside, headInside, fallbackInside, stage, hasExpectedPosition, position, policy, policyReason, out var hadException, out var _) && !hadException && state != null) { state.ReviveCompleted = true; } if (hadException) { DebugLogState(state, stage + ": skipped post revive protection because vanilla ReviveRPC threw."); } } private static bool TryRevive(PlayerDeathHead head, ReviveState state, bool roomCheckInside, bool headInside, bool fallbackInside, string stage, bool hasExpectedPosition, Vector3 expectedPosition, ReviveTimingPolicy policy, string policyReason, out bool hadException, out bool successObserved) { //IL_00e0: Unknown result type (might be due to invalid IL or missing references) hadException = false; successObserved = false; try { float num = ((state == null || state.FirstInsideTime <= 0f) ? 0f : (Time.time - state.FirstInsideTime)); DebugLogState(state, string.Concat(stage, ": revive attempt. effectivePolicy=", policy, ", policyReason=", policyReason, ", target=", MultiplayerCapabilitySync.DescribePlayer(head.playerAvatar), ", firstInsideAge=", num.ToString("F3"), ", roomCheck=", roomCheckInside, ", headFieldBefore=", headInside, ", fallback=", fallbackInside, ", expectedPosition=", FormatExpectedPosition(hasExpectedPosition, expectedPosition), ", repoFidelityProtection=", ClientReviveProtection.WouldRunFor(head.playerAvatar), ".")); head.Revive(); successObserved = HasReviveClearlySucceeded(head); bool flag = IsMultiplayer(); DebugLogState(state, stage + ": revive returned. multiplayer=" + flag + ", headFieldAfter=" + GetBool(PlayerDeathHeadInExtractionPointField, head, defaultValue: false, "PlayerDeathHead.inExtractionPoint") + ", triggeredAfter=" + IsTriggered(head) + ", playerDeadSet=" + GetPlayerBool(head, PlayerAvatarDeadSetField, "PlayerAvatar.deadSet") + ", playerIsDisabled=" + GetPlayerBool(head, PlayerAvatarIsDisabledField, "PlayerAvatar.isDisabled") + ", successObserved=" + successObserved + "."); return successObserved || flag; } catch (Exception ex) { hadException = true; LogException(stage + " revive attempt", ex); successObserved = HasReviveClearlySucceeded(head); DebugLogState(state, stage + ": revive threw. triggeredAfter=" + IsTriggered(head) + ", playerDeadSet=" + GetPlayerBool(head, PlayerAvatarDeadSetField, "PlayerAvatar.deadSet") + ", playerIsDisabled=" + GetPlayerBool(head, PlayerAvatarIsDisabledField, "PlayerAvatar.isDisabled") + ", successObservedAfterException=" + successObserved + "."); return successObserved; } } private static bool IsTriggered(PlayerDeathHead head) { return GetBool(PlayerDeathHeadTriggeredField, head, defaultValue: false, "PlayerDeathHead.triggered"); } private static bool IsInsideExtractionPoint(PlayerDeathHead head, out bool roomCheckInside, out bool headInside, out bool fallbackInside) { roomCheckInside = false; headInside = GetBool(PlayerDeathHeadInExtractionPointField, head, defaultValue: false, "PlayerDeathHead.inExtractionPoint"); fallbackInside = false; RoomVolumeCheck roomVolumeCheck = GetRoomVolumeCheck(head); if ((Object)(object)roomVolumeCheck != (Object)null && RoomVolumeCheckInExtractionPointField != null) { roomCheckInside = GetBool(RoomVolumeCheckInExtractionPointField, roomVolumeCheck, defaultValue: false, "RoomVolumeCheck.inExtractionPoint"); if (roomCheckInside) { return true; } } if (IsFallbackExtractionDetectionEnabled()) { fallbackInside = IsIndependentlyInsideExtractionPoint(head, roomVolumeCheck); } if (!headInside) { return fallbackInside; } return true; } private static bool HasReviveClearlySucceeded(PlayerDeathHead head) { if ((Object)(object)head == (Object)null) { return true; } PlayerAvatar playerAvatar = head.playerAvatar; if ((Object)(object)playerAvatar == (Object)null) { return !IsTriggered(head); } bool @bool = GetBool(PlayerAvatarDeadSetField, playerAvatar, defaultValue: true, "PlayerAvatar.deadSet"); bool bool2 = GetBool(PlayerAvatarIsDisabledField, playerAvatar, defaultValue: true, "PlayerAvatar.isDisabled"); if (!@bool) { return !bool2; } return false; } private static RoomVolumeCheck GetRoomVolumeCheck(PlayerDeathHead head) { if (PlayerDeathHeadRoomVolumeCheckField == null || (Object)(object)head == (Object)null) { return null; } try { object? value = PlayerDeathHeadRoomVolumeCheckField.GetValue(head); return (RoomVolumeCheck)((value is RoomVolumeCheck) ? value : null); } catch (Exception ex) { DebugLog("PlayerDeathHead.roomVolumeCheck read failed: " + ex.Message); return null; } } private static bool IsIndependentlyInsideExtractionPoint(PlayerDeathHead head, RoomVolumeCheck check) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: 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_0032: 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) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)check != (Object)null) { Vector3 val = check.currentSize; if (val == Vector3.zero) { val = ((Component)check).transform.localScale; } LayerMask roomVolumeMask = GetRoomVolumeMask(check); Vector3 val2 = ((Component)check).transform.position + ((Component)check).transform.rotation * check.CheckPosition; int count = Physics.OverlapBoxNonAlloc(val2, val * 0.5f, OverlapBuffer, ((Component)check).transform.rotation, LayerMask.op_Implicit(roomVolumeMask), (QueryTriggerInteraction)2); if (AnyExtractionRoomVolume(count)) { return IsNearKnownExtractionPoint(((Component)head).transform.position); } return false; } int count2 = Physics.OverlapSphereNonAlloc(((Component)head).transform.position, 0.35f, OverlapBuffer, -1, (QueryTriggerInteraction)2); if (AnyExtractionRoomVolume(count2)) { return IsNearKnownExtractionPoint(((Component)head).transform.position); } return false; } private static bool IsFallbackExtractionDetectionEnabled() { if (ModConfig.EnableFallbackExtractionDetection != null) { return ModConfig.EnableFallbackExtractionDetection.Value; } return false; } private static bool IsNearKnownExtractionPoint(Vector3 position) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)RoundDirector.instance == (Object)null) { return false; } ExtractionPoint field = GetField<ExtractionPoint>(RoundDirectorExtractionPointCurrentField, RoundDirector.instance); if ((Object)(object)field != (Object)null && Vector3.Distance(position, ((Component)field).transform.position) <= 12f) { return true; } List<GameObject> field2 = GetField<List<GameObject>>(RoundDirectorExtractionPointListField, RoundDirector.instance); if (field2 == null) { return false; } for (int i = 0; i < field2.Count; i++) { GameObject val = field2[i]; if ((Object)(object)val != (Object)null && Vector3.Distance(position, val.transform.position) <= 12f) { return true; } } } catch (Exception ex) { DebugLog("Known extraction point fallback check failed: " + ex.Message); } return false; } private static LayerMask GetRoomVolumeMask(RoomVolumeCheck check) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)check != (Object)null && RoomVolumeCheckMaskField != null) { object value = RoomVolumeCheckMaskField.GetValue(check); if (value is LayerMask) { return (LayerMask)value; } } return LayerMask.op_Implicit(-1); } private static bool AnyExtractionRoomVolume(int count) { for (int i = 0; i < count; i++) { Collider val = OverlapBuffer[i]; if (!((Object)(object)val == (Object)null)) { RoomVolume val2 = ((Component)val).GetComponent<RoomVolume>(); if ((Object)(object)val2 == (Object)null) { val2 = ((Component)val).GetComponentInParent<RoomVolume>(); } if ((Object)(object)val2 != (Object)null && val2.Extraction) { return true; } } } return false; } private static void SetExtractionState(PlayerDeathHead head, bool value) { SetBool(PlayerDeathHeadInExtractionPointField, head, value, "PlayerDeathHead.inExtractionPoint"); RoomVolumeCheck roomVolumeCheck = GetRoomVolumeCheck(head); if ((Object)(object)roomVolumeCheck != (Object)null) { SetBool(RoomVolumeCheckInExtractionPointField, roomVolumeCheck, value, "RoomVolumeCheck.inExtractionPoint"); } } private static bool TryGetExpectedRevivePosition(PlayerDeathHead head, out Vector3 position) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //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_0037: Unknown result type (might be due to invalid IL or missing references) //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) position = Vector3.zero; if ((Object)(object)head == (Object)null) { return false; } try { PhysGrabObject physGrabObject = GetPhysGrabObject(head); if ((Object)(object)physGrabObject != (Object)null) { position = physGrabObject.centerPoint - Vector3.up * 0.25f; return true; } } catch (Exception ex) { DebugLog("Expected revive position from death head PhysGrabObject failed: " + ex.Message); } position = ((Component)head).transform.position; return true; } private static PhysGrabObject GetPhysGrabObject(PlayerDeathHead head) { if (PlayerDeathHeadPhysGrabObjectField == null || (Object)(object)head == (Object)null) { return null; } try { object? value = PlayerDeathHeadPhysGrabObjectField.GetValue(head); return (PhysGrabObject)((value is PhysGrabObject) ? value : null); } catch (Exception ex) { DebugLog("PlayerDeathHead.physGrabObject read failed: " + ex.Message); return null; } } private static bool IsHostOrSingleplayer() { try { return SemiFunc.IsMasterClientOrSingleplayer(); } catch { return true; } } private static bool IsMultiplayer() { try { return GameManager.Multiplayer(); } catch { return false; } } private static bool GetBool(FieldInfo field, object instance, bool defaultValue, string name) { if (field == null || instance == null) { return defaultValue; } try { object value = field.GetValue(instance); if (value is bool) { return (bool)value; } } catch (Exception ex) { DebugLog(name + " read failed: " + ex.Message); } return defaultValue; } internal static T GetField<T>(FieldInfo field, object instance) where T : class { if (field == null || instance == null) { return null; } try { return field.GetValue(instance) as T; } catch (Exception ex) { DebugLog("Field read failed: " + ex.Message); return null; } } private static string GetPlayerBool(PlayerDeathHead head, FieldInfo field, string name) { PlayerAvatar val = (((Object)(object)head == (Object)null) ? null : head.playerAvatar); if (field == null || (Object)(object)val == (Object)null) { return "unknown"; } try { object value = field.GetValue(val); if (value is bool) { return ((bool)value).ToString(); } } catch (Exception ex) { DebugLog(name + " read failed: " + ex.Message); } return "unknown"; } private static void SetBool(FieldInfo field, object instance, bool value, string name) { if (field == null || instance == null) { DebugLog(name + " is not available; vanilla Revive may no-op if the game field is still false."); return; } try { field.SetValue(instance, value); } catch (Exception ex) { LogException(name + " write", ex); } } private static void DebugLogState(ReviveState state, string message) { if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value && state != null && !(Time.time < state.NextDebugLogTime)) { state.NextDebugLogTime = Time.time + 0.75f; Plugin.Log.LogInfo((object)message); } } private static void DebugLog(string message) { if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)message); } } private static string FormatExpectedPosition(bool hasExpectedPosition, Vector3 expectedPosition) { if (!hasExpectedPosition) { return "unknown"; } return "(" + expectedPosition.x.ToString("F2") + ", " + expectedPosition.y.ToString("F2") + ", " + expectedPosition.z.ToString("F2") + ")"; } internal static void LogException(string stage, Exception ex) { if (Plugin.Log == null) { return; } Exception ex2 = ex; if (ex is TargetInvocationException && ex.InnerException != null) { ex2 = ex.InnerException; } Plugin.Log.LogWarning((object)(stage + " failed: " + ex2.GetType().Name + ": " + ex2.Message)); if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value) { if (ex2.StackTrace != null) { Plugin.Log.LogWarning((object)(stage + " stack: " + ex2.StackTrace)); } if (!object.ReferenceEquals(ex2, ex)) { Plugin.Log.LogWarning((object)(stage + " wrapper: " + ex.GetType().Name + ": " + ex.Message)); } } } } internal static class ClientReviveProtection { internal const string REPOFidelityGuid = "Vippy.REPOFidelity"; private static readonly FieldInfo SpectateCameraMainCameraField = typeof(SpectateCamera).GetField("MainCamera", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo SpectateCameraParentObjectField = typeof(SpectateCamera).GetField("ParentObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo SpectateCameraPreviousParentField = typeof(SpectateCamera).GetField("PreviousParent", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo AudioManagerAudioListenerField = typeof(AudioManager).GetField("AudioListener", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarIsLocalField = typeof(PlayerAvatar).GetField("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static void Reset() { } internal static void OnReviveRpc(PlayerAvatar player) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (WouldRunFor(player) && !((Object)(object)Plugin.Instance == (Object)null)) { Plugin.Instance.StartProtectionRoutine(player, hasExpectedPosition: false, Vector3.zero, "post revive rpc protection"); } } internal static IEnumerator Run(PlayerAvatar player) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return Run(player, hasExpectedPosition: false, Vector3.zero, "post revive protection"); } internal static IEnumerator Run(PlayerAvatar player, bool hasExpectedPosition, Vector3 expectedPosition, string reason) { if (WouldRunFor(player)) { float endTime = Time.time + ModConfig.SafeProtectionWindow(); DebugLog("Starting local " + reason + "."); yield return null; while (Time.time <= endTime) { ApplyLocalFixes(player); yield return null; } ApplyLocalFixes(player); DebugLog("Finished local " + reason + "."); } } internal static bool WouldRunFor(PlayerAvatar player) { if (ModConfig.REPOFidelityClientProtection == null || ModConfig.REPOFidelityClientProtection.Value == ClientProtectionMode.Off || (Object)(object)player == (Object)null || !IsLocalPlayer(player)) { return false; } if (ModConfig.REPOFidelityClientProtection.Value == ClientProtectionMode.Always) { return true; } return IsREPOFidelityLoaded(); } private static bool IsLocalPlayer(PlayerAvatar player) { try { if (PlayerAvatarIsLocalField != null) { object value = PlayerAvatarIsLocalField.GetValue(player); if (value is bool) { return (bool)value; } } } catch { } return (Object)(object)player == (Object)(object)PlayerAvatar.instance; } internal static bool IsREPOFidelityLoaded() { try { return Chainloader.PluginInfos.ContainsKey("Vippy.REPOFidelity"); } catch { return false; } } private static void ApplyLocalFixes(PlayerAvatar player) { if ((Object)(object)player == (Object)null) { player = PlayerAvatar.instance; } InspectSpectateCamera(SpectateCamera.instance); if ((Object)(object)player != (Object)null && (Object)(object)player.localCamera != (Object)null) { SafeCall((Action)player.localCamera.Teleported, "PlayerLocalCamera.Teleported"); } EnsureObject("CameraZoom.Instance", (Object)(object)CameraZoom.Instance); EnsureObject("PostProcessing.Instance", (Object)(object)PostProcessing.Instance); EnsureObject("AudioManager.instance", (Object)(object)AudioManager.instance); if ((Object)(object)AudioManager.instance != (Object)null) { EnsureObject("AudioManager.AudioListener", (Object)(object)GetField<AudioListenerFollow>(AudioManagerAudioListenerField, AudioManager.instance)); } } private static void InspectSpectateCamera(SpectateCamera spectate) { if ((Object)(object)spectate == (Object)null) { DebugLog("SpectateCamera.instance is not ready during local revive protection."); return; } EnsureObject("SpectateCamera.MainCamera", (Object)(object)GetField<Camera>(SpectateCameraMainCameraField, spectate)); EnsureObject("SpectateCamera.ParentObject", (Object)(object)GetField<Transform>(SpectateCameraParentObjectField, spectate)); EnsureObject("SpectateCamera.PreviousParent", (Object)(object)GetField<Transform>(SpectateCameraPreviousParentField, spectate)); EnsureObject("SpectateCamera.normalTransformPivot", (Object)(object)spectate.normalTransformPivot); } private static void SafeCall(Action action, string name) { try { action(); } catch (Exception ex) { DebugLog(name + " failed: " + ex.Message); } } private static T GetField<T>(FieldInfo field, object instance) where T : class { if (field == null || instance == null) { return null; } try { return field.GetValue(instance) as T; } catch (Exception ex) { DebugLog("Field read failed: " + ex.Message); return null; } } private static void EnsureObject(string name, Object obj) { if (obj == (Object)null) { DebugLog(name + " is not ready during local revive protection."); } } private static void DebugLog(string message) { if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)message); } } } internal static class MultiplayerCapabilitySync { private const byte HasModKey = 201; private const byte ModVersionKey = 202; private const byte HasREPOFidelityKey = 203; internal static void Clear() { } internal static void PublishLocalCapabilities() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown try { if (GameManager.Multiplayer() && PhotonNetwork.LocalPlayer != null) { Hashtable val = new Hashtable(); val[(byte)201] = true; val[(byte)202] = "0.1.5"; val[(byte)203] = ClientReviveProtection.IsREPOFidelityLoaded(); PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); DebugLog("Published multiplayer capabilities: hasMod=True, version=0.1.5, hasREPOFidelity=" + ClientReviveProtection.IsREPOFidelityLoaded() + "."); } } catch (Exception ex) { DebugLog("Publish multiplayer capabilities failed: " + ex.Message); } } internal static ReviveTimingPolicy ResolveReviveTimingFor(PlayerAvatar targetPlayer, out string reason) { Player photonPlayer = GetPhotonPlayer(targetPlayer); if (photonPlayer == null) { ReviveTimingPolicy reviveTimingPolicy = LocalPolicy(); reason = string.Concat("noPhotonPlayer; localPolicy=", reviveTimingPolicy, ", localRepoFidelity=", ClientReviveProtection.IsREPOFidelityLoaded()); return reviveTimingPolicy; } bool value; bool known = TryGetBool(photonPlayer, 201, out value); bool value2; bool flag = TryGetBool(photonPlayer, 203, out value2); string @string = GetString(photonPlayer, 202); if (flag && value2) { reason = "targetRepoFidelity=True, targetHasMod=" + FormatKnownBool(known, value) + ", targetVersion=" + FormatString(@string); return ReviveTimingPolicy.StableDelayed; } if (flag && !value2) { reason = "targetRepoFidelity=False, targetHasMod=" + FormatKnownBool(known, value) + ", targetVersion=" + FormatString(@string); return ReviveTimingPolicy.Instant; } if (photonPlayer.IsLocal) { ReviveTimingPolicy reviveTimingPolicy2 = LocalPolicy(); reason = string.Concat("targetLocalNoReport; localPolicy=", reviveTimingPolicy2, ", localRepoFidelity=", ClientReviveProtection.IsREPOFidelityLoaded()); return reviveTimingPolicy2; } if (ModConfig.SafeUnknownClientPolicy() == UnknownClientPolicy.StableDelayed) { reason = "targetReportUnknown; unknownClientPolicy=StableDelayed, targetHasMod=" + FormatKnownBool(known, value) + ", targetVersion=" + FormatString(@string); return ReviveTimingPolicy.StableDelayed; } ReviveTimingPolicy reviveTimingPolicy3 = LocalPolicy(); reason = string.Concat("targetReportUnknown; unknownClientPolicy=HostLocal, hostPolicy=", reviveTimingPolicy3, ", targetHasMod=", FormatKnownBool(known, value), ", targetVersion=", FormatString(@string)); return reviveTimingPolicy3; } internal static string DescribePlayer(PlayerAvatar targetPlayer) { Player photonPlayer = GetPhotonPlayer(targetPlayer); if (photonPlayer == null) { return "unknown"; } return "actor=" + photonPlayer.ActorNumber + ", nick=" + FormatString(photonPlayer.NickName); } private static ReviveTimingPolicy LocalPolicy() { if (!ClientReviveProtection.IsREPOFidelityLoaded()) { return ReviveTimingPolicy.Instant; } return ReviveTimingPolicy.StableDelayed; } private static Player GetPhotonPlayer(PlayerAvatar targetPlayer) { try { if (!GameManager.Multiplayer()) { return PhotonNetwork.LocalPlayer; } if ((Object)(object)targetPlayer == (Object)null) { return PhotonNetwork.LocalPlayer; } if ((Object)(object)targetPlayer.photonView != (Object)null && targetPlayer.photonView.Owner != null) { return targetPlayer.photonView.Owner; } if ((Object)(object)targetPlayer == (Object)(object)PlayerAvatar.instance) { return PhotonNetwork.LocalPlayer; } } catch (Exception ex) { DebugLog("Resolve target Photon player failed: " + ex.Message); } return null; } private static bool TryGetBool(Player player, byte key, out bool value) { value = false; try { if (player == null || player.CustomProperties == null || !player.CustomProperties.ContainsKey(key)) { return false; } object obj = player.CustomProperties[key]; if (obj is bool) { value = (bool)obj; return true; } } catch (Exception ex) { DebugLog("Read multiplayer capability bool failed: " + ex.Message); } return false; } private static string GetString(Player player, byte key) { try { if (player == null || player.CustomProperties == null || !player.CustomProperties.ContainsKey(key)) { return null; } return player.CustomProperties[key] as string; } catch (Exception ex) { DebugLog("Read multiplayer capability string failed: " + ex.Message); return null; } } private static string FormatKnownBool(bool known, bool value) { if (!known) { return "unknown"; } return value.ToString(); } private static string FormatString(string value) { if (!string.IsNullOrEmpty(value)) { return value; } return "unknown"; } private static void DebugLog(string message) { if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)message); } } } internal static class ReviveDiagnostics { private static readonly FieldInfo PlayerAvatarPlayerDeathHeadField = typeof(PlayerAvatar).GetField("playerDeathHead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarPlayerAvatarCollisionField = typeof(PlayerAvatar).GetField("playerAvatarCollision", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerAvatarIsLocalField = typeof(PlayerAvatar).GetField("isLocal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo PlayerDeathHeadPhysGrabObjectField = typeof(PlayerDeathHead).GetField("physGrabObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); internal static bool AreCriticalDependenciesReady(PlayerAvatar player, string reason) { PlayerDeathHead deathHead = GetDeathHead(player); bool flag = (Object)(object)player != (Object)null && (Object)(object)deathHead != (Object)null && (Object)(object)GetPhysGrabObject(deathHead) != (Object)null && (Object)(object)player.playerHealth != (Object)null && (Object)(object)player.playerAvatarVisuals != (Object)null && (Object)(object)player.playerDeathEffects != (Object)null && (Object)(object)player.playerReviveEffects != (Object)null && (Object)(object)GetAvatarCollision(player) != (Object)null && (Object)(object)player.RoomVolumeCheck != (Object)null; if ((Object)(object)player != (Object)null && IsLocalPlayer(player)) { flag = flag && (Object)(object)player.playerTransform != (Object)null && (Object)(object)player.playerTransform.parent != (Object)null && (Object)(object)CameraAim.Instance != (Object)null && (Object)(object)CameraPosition.instance != (Object)null && (Object)(object)GameDirector.instance != (Object)null && (Object)(object)SpectateCamera.instance != (Object)null && (Object)(object)PlayerController.instance != (Object)null && (Object)(object)CameraGlitch.Instance != (Object)null; } if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)(reason + ": " + BuildReadinessReport(player, flag))); } return flag; } internal static void LogReviveRpcPrefix(PlayerAvatar player) { if (ModConfig.DebugLogging != null && ModConfig.DebugLogging.Value) { Plugin.Log.LogInfo((object)("ReviveRPC prefix: " + BuildReadinessReport(player, AreCriticalDependenciesReadyNoLog(player)))); } } internal static bool AreCriticalDependenciesReadyNoLog(PlayerAvatar player) { if ((Object)(object)player == (Object)null || (Object)(object)player.playerHealth == (Object)null || (Object)(object)player.playerAvatarVisuals == (Object)null || (Object)(object)player.playerDeathEffects == (Object)null || (Object)(object)player.playerReviveEffects == (Object)null || (Object)(object)player.RoomVolumeCheck == (Object)null) { return false; } PlayerDeathHead deathHead = GetDeathHead(player); if ((Object)(object)deathHead == (Object)null || (Object)(object)GetPhysGrabObject(deathHead) == (Object)null || (Object)(object)GetAvatarCollision(player) == (Object)null) { return false; } if (!IsLocalPlayer(player)) { return true; } if ((Object)(object)player.playerTransform != (Object)null && (Object)(object)player.playerTransform.parent != (Object)null && (Object)(object)CameraAim.Instance != (Object)null && (Object)(object)CameraPosition.instance != (Object)null && (Object)(object)GameDirector.instance != (Object)null && (Object)(object)SpectateCamera.instance != (Object)null && (Object)(object)PlayerController.instance != (Object)null) { return (Object)(object)CameraGlitch.Instance != (Object)null; } return false; } internal static string BuildReadinessReport(PlayerAvatar player, bool ready) { if ((Object)(object)player == (Object)null) { return "ready=False, playerAvatar=null."; } PlayerDeathHead deathHead = GetDeathHead(player); bool flag = IsLocalPlayer(player); string text = (((Object)(object)player.playerTransform == (Object)null) ? "unknown" : FormatReady((Object)(object)player.playerTransform.parent != (Object)null)); return "ready=" + ready + ", isLocal=" + flag + ", playerDeathHead=" + FormatReady((Object)(object)deathHead != (Object)null) + ", physGrabObject=" + FormatReady((Object)(object)deathHead != (Object)null && (Object)(object)GetPhysGrabObject(deathHead) != (Object)null) + ", playerHealth=" + FormatReady((Object)(object)player.playerHealth != (Object)null) + ", playerAvatarVisuals=" + FormatReady((Object)(object)player.playerAvatarVisuals != (Object)null) + ", playerDeathEffects=" + FormatReady((Object)(object)player.playerDeathEffects != (Object)null) + ", playerReviveEffects=" + FormatReady((Object)(object)player.playerReviveEffects != (Object)null) + ", playerAvatarCollision=" + FormatReady((Object)(object)GetAvatarCollision(player) != (Object)null) + ", playerTransform=" + FormatReady((Object)(object)player.playerTransform != (Object)null) + ", playerTransform.parent=" + text + ", CameraAim.Instance=" + FormatReady((Object)(object)CameraAim.Instance != (Object)null) + ", CameraPosition.instance=" + FormatReady((Object)(object)CameraPosition.instance != (Object)null) + ", GameDirector.instance=" + FormatReady((Object)(object)GameDirector.instance != (Object)null) + ", SpectateCamera.instance=" + FormatReady((Object)(object)SpectateCamera.instance != (Object)null) + ", PlayerController.instance=" + FormatReady((Object)(object)PlayerController.instance != (Object)null) + ", CameraGlitch.Instance=" + FormatReady((Object)(object)CameraGlitch.Instance != (Object)null) + ", RoomVolumeCheck=" + FormatReady((Object)(object)player.RoomVolumeCheck != (Object)null) + "."; } private static bool IsLocalPlayer(PlayerAvatar player) { if ((Object)(object)player == (Object)null) { return false; } try { if (PlayerAvatarIsLocalField != null) { object value = PlayerAvatarIsLocalField.GetValue(player); if (value is bool) { return (bool)value; } } } catch { } return (Object)(object)player == (Object)(object)PlayerAvatar.instance; } private static PlayerDeathHead GetDeathHead(PlayerAvatar player) { return InstantReviveController.GetField<PlayerDeathHead>(PlayerAvatarPlayerDeathHeadField, player); } private static PhysGrabObject GetPhysGrabObject(PlayerDeathHead head) { return InstantReviveController.GetField<PhysGrabObject>(PlayerDeathHeadPhysGrabObjectField, head); } private static PlayerAvatarCollision GetAvatarCollision(PlayerAvatar player) { return InstantReviveController.GetField<PlayerAvatarCollision>(PlayerAvatarPlayerAvatarCollisionField, player); } private static string FormatReady(bool ready) { if (!ready) { return "null"; } return "ok"; } } [HarmonyPatch(typeof(PlayerDeathHead), "Update")] internal static class PlayerDeathHeadUpdatePatch { private static void Postfix(PlayerDeathHead __instance) { InstantReviveController.ProcessHead(__instance); } } [HarmonyPatch(typeof(PlayerAvatar), "ReviveRPC")] internal static class PlayerAvatarReviveRpcPatch { private static void Prefix(PlayerAvatar __instance) { ReviveDiagnostics.LogReviveRpcPrefix(__instance); } private static void Postfix(PlayerAvatar __instance) { ClientReviveProtection.OnReviveRpc(__instance); } } [HarmonyPatch(typeof(RunManager), "ChangeLevel")] internal static class RunManagerChangeLevelPatch { private static void Prefix() { InstantReviveController.Reset(); ClientReviveProtection.Reset(); MultiplayerCapabilitySync.PublishLocalCapabilities(); } }