using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using Agents;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem.Collections.Generic;
using LevelGeneration;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
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;
}
}
}
namespace HawChow.GTFOPause
{
internal static class PauseCoordinator
{
private sealed class PlayerSnapshot
{
internal PlayerAgent Agent { get; }
internal IntPtr AgentPointer { get; }
internal IntPtr ControllerPointer { get; }
internal bool ControlsWereEnabled { get; }
internal eFocusState InputFilter { get; }
internal InventorySlot WieldedSlot { get; }
internal bool PauseApplied { get; set; }
internal bool PauseApplyWarningLogged { get; set; }
internal PlayerSnapshot(PlayerAgent agent, IntPtr agentPointer, IntPtr controllerPointer, bool controlsWereEnabled, eFocusState inputFilter, InventorySlot wieldedSlot)
{
//IL_0024: 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_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
Agent = agent;
AgentPointer = agentPointer;
ControllerPointer = controllerPointer;
ControlsWereEnabled = controlsWereEnabled;
InputFilter = inputFilter;
WieldedSlot = wieldedSlot;
}
}
private const float PlayerRefreshSeconds = 0.25f;
private const float ReactorRefreshSeconds = 0.5f;
private const float RemoteControlResendSeconds = 0.25f;
private const float PendingPlayerWarningSeconds = 5f;
private static readonly PauseSessionState Session = new PauseSessionState();
private static readonly RosterSnapshot<ulong> PauseRoster = new RosterSnapshot<ulong>();
private static readonly BooleanStateLedger<IntPtr> ReactorProgress = new BooleanStateLedger<IntPtr>();
private static readonly Dictionary<IntPtr, LG_WardenObjective_Reactor> ReactorReferences = new Dictionary<IntPtr, LG_WardenObjective_Reactor>();
private static readonly Dictionary<ulong, PlayerSnapshot> PlayerSnapshots = new Dictionary<ulong, PlayerSnapshot>();
private static readonly Dictionary<ulong, float> NextRemoteControlEnforcementAt = new Dictionary<ulong, float>();
private static bool _hasMigrationSnapshot;
private static bool _migrationRefreshPending;
private static string _migrationRefreshReason;
private static int _rosterReadWarnings;
private static float _nextPlayerRefreshAt;
private static float _nextReactorRefreshAt;
private static float _pendingPlayerSince = -1f;
private static bool _pendingPlayerWarningLogged;
internal static bool IsPaused => Session.IsPaused;
internal static bool IsHostInLevel
{
get
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Invalid comparison between Unknown and I4
try
{
return SNet.IsMaster && (int)GameStateManager.CurrentStateName == 10;
}
catch
{
return false;
}
}
}
internal static void Toggle()
{
if (IsHostInLevel)
{
if (Session.IsPaused)
{
ForceResume("host hotkey", requestNetworkRecall: true);
}
else
{
BeginPause();
}
}
}
internal static void EnforcePause(float unscaledNow)
{
if (!Session.TryGetSnapshot(out var snapshot))
{
return;
}
if (!IsHostInLevel)
{
ForceResume("host or in-level state changed", requestNetworkRecall: false);
return;
}
try
{
if (!Clock.IsPaused)
{
Clock.SetPaused(true);
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not re-apply GTFO Clock pause: " + ex.Message));
}
}
try
{
if (Clock.ExpeditionProgressionTime != snapshot.ExpeditionProgressionTime)
{
Clock.ExpeditionProgressionTime = snapshot.ExpeditionProgressionTime;
}
}
catch (Exception ex2)
{
ManualLogSource pluginLog2 = Plugin.PluginLog;
if (pluginLog2 != null)
{
pluginLog2.LogWarning((object)("Could not re-apply expedition progression snapshot: " + ex2.Message));
}
}
if (float.IsNaN(unscaledNow) || float.IsInfinity(unscaledNow))
{
ApplyPauseTimeScale(snapshot);
return;
}
if (unscaledNow >= _nextPlayerRefreshAt)
{
CaptureAndReconcilePlayers(pauseEdge: false);
_nextPlayerRefreshAt = unscaledNow + 0.25f;
ApplyPauseTimeScale(snapshot);
}
if (unscaledNow >= _nextReactorRefreshAt)
{
CaptureAndDisableReactors();
_nextReactorRefreshAt = unscaledNow + 0.5f;
}
}
private static void ApplyPauseTimeScale(PauseStateSnapshot snapshot)
{
bool flag = HasPendingDropInOrUncapturedPlayer();
float num = PauseTimeScalePolicy.SelectTarget(snapshot.TimeScale, flag);
if (!flag)
{
_pendingPlayerSince = -1f;
_pendingPlayerWarningLogged = false;
}
else
{
float unscaledTime = Time.unscaledTime;
if (!float.IsNaN(unscaledTime) && !float.IsInfinity(unscaledTime))
{
if (_pendingPlayerSince < 0f || unscaledTime < _pendingPlayerSince)
{
_pendingPlayerSince = unscaledTime;
_pendingPlayerWarningLogged = false;
}
if (!_pendingPlayerWarningLogged && unscaledTime - _pendingPlayerSince >= 5f)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)"Unity time remains live because a slotted player is still in drop-in or lacks an applied pause snapshot.");
}
_pendingPlayerWarningLogged = true;
}
}
}
try
{
if (Time.timeScale != num)
{
Time.timeScale = num;
}
}
catch (Exception ex)
{
ManualLogSource pluginLog2 = Plugin.PluginLog;
if (pluginLog2 != null)
{
pluginLog2.LogWarning((object)("Could not apply paused time-scale mode: " + ex.Message));
}
}
}
private static bool HasPendingDropInOrUncapturedPlayer()
{
try
{
SNet_PlayerSlotManager slots = SNet.Slots;
if ((Object)(object)slots == (Object)null || slots.SlottedPlayers == null)
{
return PauseTimeScalePolicy.HasPendingPlayer(null, rosterReadable: false);
}
List<PauseRosterMemberState> list = new List<PauseRosterMemberState>();
Enumerator<SNet_Player> enumerator = slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if ((Object)(object)current == (Object)null)
{
list.Add(new PauseRosterMemberState(isSlotted: true, hasLookup: false, isInDropIn: false, hasPauseSnapshot: false, pauseApplied: false));
continue;
}
bool isInSlot = current.IsInSlot;
if (isInSlot)
{
ulong lookup = current.Lookup;
bool flag = lookup != 0;
bool isInDropIn = current.IsInDropIn;
bool flag2 = false;
bool pauseApplied = false;
if (flag && !isInDropIn && PlayerSnapshots.TryGetValue(lookup, out var value))
{
PlayerAgent agent = FindAgent(lookup);
flag2 = IsSameAgent(value, agent);
pauseApplied = flag2 && value.PauseApplied;
}
list.Add(new PauseRosterMemberState(isInSlot, flag, isInDropIn, flag2, pauseApplied));
}
}
return PauseTimeScalePolicy.HasPendingPlayer(list, rosterReadable: true);
}
catch (Exception ex)
{
if (_rosterReadWarnings++ < 3)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not resolve pending drop-in state; keeping Unity time live: " + ex.Message));
}
}
return true;
}
}
private static PlayerAgent FindAgent(ulong lookup)
{
List<PlayerAgent> playerAgentsInLevel = PlayerManager.PlayerAgentsInLevel;
if (playerAgentsInLevel == null)
{
return null;
}
Enumerator<PlayerAgent> enumerator = playerAgentsInLevel.GetEnumerator();
while (enumerator.MoveNext())
{
PlayerAgent current = enumerator.Current;
if ((Object)(object)current != (Object)null && (Object)(object)current.Owner != (Object)null && current.Owner.Lookup == lookup)
{
return current;
}
}
return null;
}
internal static void FlushPendingMigrationSnapshot()
{
if (_migrationRefreshPending)
{
if (!Session.IsPaused || !IsHostInLevel)
{
_migrationRefreshPending = false;
_migrationRefreshReason = null;
return;
}
string reason = _migrationRefreshReason ?? "roster changed during pause";
_migrationRefreshPending = false;
_migrationRefreshReason = null;
CaptureMigrationSnapshotNow(reason);
}
}
internal static void ForceResume(string reason, bool requestNetworkRecall)
{
if (requestNetworkRecall && _migrationRefreshPending)
{
FlushPendingMigrationSnapshot();
}
bool hasMigrationSnapshot = _hasMigrationSnapshot;
if (!Session.TryEnd(out var snapshot))
{
if (PlayerSnapshots.Count != 0 || ReactorReferences.Count != 0)
{
RestoreReactors();
RestorePlayers();
ClearSessionData();
}
return;
}
bool flag = PauseResumeMigrationPolicy.ShouldCaptureFreshSnapshot(requestNetworkRecall, hasMigrationSnapshot, IsHostInLevel);
try
{
Clock.SetPaused(snapshot.ClockWasPaused);
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogError((object)("Could not restore GTFO Clock pause flag: " + ex));
}
}
try
{
Clock.ExpeditionProgressionTime = snapshot.ExpeditionProgressionTime;
}
catch (Exception ex2)
{
ManualLogSource pluginLog2 = Plugin.PluginLog;
if (pluginLog2 != null)
{
pluginLog2.LogError((object)("Could not restore expedition progression time: " + ex2));
}
}
try
{
if (Time.timeScale != snapshot.TimeScale)
{
Time.timeScale = snapshot.TimeScale;
}
}
catch (Exception ex3)
{
ManualLogSource pluginLog3 = Plugin.PluginLog;
if (pluginLog3 != null)
{
pluginLog3.LogError((object)("Could not restore Unity timeScale: " + ex3));
}
}
RestoreReactors();
RestorePlayers();
bool flag2 = false;
if (flag)
{
CaptureMigrationSnapshotNow("resume after local state restoration");
flag2 = _hasMigrationSnapshot;
if (!flag2)
{
ManualLogSource pluginLog4 = Plugin.PluginLog;
if (pluginLog4 != null)
{
pluginLog4.LogWarning((object)"Migration_A resume recall skipped because the fresh post-resume snapshot failed; the stale pause-edge buffer was not replayed.");
}
}
}
if (PauseResumeMigrationPolicy.ShouldRecall(requestNetworkRecall, flag2, IsHostInLevel))
{
try
{
SNet.Sync.StartRecallWithAllSyncedPlayers((eBufferType)4, false);
}
catch (Exception ex4)
{
ManualLogSource pluginLog5 = Plugin.PluginLog;
if (pluginLog5 != null)
{
pluginLog5.LogError((object)("Migration_A resume recall failed: " + ex4));
}
}
}
ClearSessionData();
ManualLogSource pluginLog6 = Plugin.PluginLog;
if (pluginLog6 != null)
{
pluginLog6.LogInfo((object)("Game resumed; prior GTFO Clock, reactor progress, player control states and wielded slots restored (" + reason + ")."));
}
}
internal static void HandleRemoteLocomotion(PlayerAgent agent, Vector3 incomingPosition)
{
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Invalid comparison between Unknown and I4
//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
if (!Session.IsPaused || !SNet.IsMaster || (Object)(object)agent == (Object)null || ((Agent)agent).IsLocallyOwned)
{
return;
}
try
{
SNet_Player owner = agent.Owner;
if ((Object)(object)owner == (Object)null || !owner.IsInSlot || owner.IsOutOfSync || !PlayerSnapshots.ContainsKey(owner.Lookup))
{
return;
}
float unscaledTime = Time.unscaledTime;
if (NextRemoteControlEnforcementAt.TryGetValue(owner.Lookup, out var value) && unscaledTime < value)
{
return;
}
NextRemoteControlEnforcementAt[owner.Lookup] = unscaledTime + 0.25f;
bool flag = (Object)(object)agent.Inventory != (Object)null && (int)agent.Inventory.WieldedSlot > 0;
bool flag2 = Vector3.Distance(incomingPosition, ((Agent)agent).Position) >= 2f;
if (flag || flag2)
{
agent.RequestToggleControlsEnabled(false);
if (flag && (Object)(object)agent.Sync != (Object)null)
{
agent.Sync.WantsToWieldSlot((InventorySlot)0, false);
}
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not hold a remote player at the paused snapshot: " + ex.Message));
}
}
}
private static void BeginPause()
{
PauseStateSnapshot snapshot;
try
{
snapshot = new PauseStateSnapshot(Time.timeScale, Clock.IsPaused, Clock.ExpeditionProgressionTime);
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogError((object)("Pause could not snapshot the game clocks: " + ex));
}
return;
}
if (!Session.TryBegin(snapshot))
{
ManualLogSource pluginLog2 = Plugin.PluginLog;
if (pluginLog2 != null)
{
pluginLog2.LogWarning((object)"Pause request ignored: a pause session is already active or clock values are invalid.");
}
return;
}
_hasMigrationSnapshot = false;
_migrationRefreshPending = false;
_migrationRefreshReason = null;
_nextPlayerRefreshAt = 0f;
_nextReactorRefreshAt = 0f;
_pendingPlayerSince = -1f;
_pendingPlayerWarningLogged = false;
try
{
CaptureMigrationSnapshotNow("pause edge");
CaptureAndReconcilePlayers(pauseEdge: true);
PauseRoster.Reset(PlayerSnapshots.Keys);
CaptureAndDisableReactors();
Clock.SetPaused(true);
Clock.ExpeditionProgressionTime = snapshot.ExpeditionProgressionTime;
ApplyPauseTimeScale(snapshot);
ManualLogSource pluginLog3 = Plugin.PluginLog;
if (pluginLog3 != null)
{
pluginLog3.LogInfo((object)"Game paused; GTFO Clock and reactor progress are frozen. Unity timeScale is zero when all slotted players are captured; it returns to the pause-edge value while a drop-in or uncaptured player needs to finish joining.");
}
}
catch (Exception ex2)
{
ManualLogSource pluginLog4 = Plugin.PluginLog;
if (pluginLog4 != null)
{
pluginLog4.LogError((object)("Pause setup failed; restoring captured state: " + ex2));
}
ForceResume("pause setup rollback", requestNetworkRecall: true);
}
}
private static void CaptureAndReconcilePlayers(bool pauseEdge)
{
Dictionary<ulong, PlayerAgent> dictionary = new Dictionary<ulong, PlayerAgent>();
HashSet<ulong> hashSet = new HashSet<ulong>();
bool flag = TryReadSlottedLookups(hashSet);
try
{
List<PlayerAgent> playerAgentsInLevel = PlayerManager.PlayerAgentsInLevel;
if (playerAgentsInLevel != null)
{
Enumerator<PlayerAgent> enumerator = playerAgentsInLevel.GetEnumerator();
while (enumerator.MoveNext())
{
PlayerAgent current = enumerator.Current;
if (!((Object)(object)current == (Object)null))
{
SNet_Player owner = current.Owner;
if ((Object)(object)owner != (Object)null && owner.IsInSlot && owner.Lookup != 0L)
{
dictionary[owner.Lookup] = current;
}
}
}
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not enumerate in-level player agents: " + ex.Message));
}
}
bool flag2 = false;
if (flag && hashSet.Count > 0)
{
List<ulong> list = new List<ulong>();
foreach (ulong key in PlayerSnapshots.Keys)
{
if (!hashSet.Contains(key))
{
list.Add(key);
}
}
foreach (ulong item in list)
{
PlayerSnapshots.Remove(item);
NextRemoteControlEnforcementAt.Remove(item);
flag2 = true;
}
}
foreach (KeyValuePair<ulong, PlayerAgent> item2 in dictionary)
{
SNet_Player owner2;
if (PlayerSnapshots.TryGetValue(item2.Key, out var value) && IsSameAgent(value, item2.Value))
{
TryApplyPlayerPause(value);
}
else if (TryGetReadySlottedOwner(item2.Value, out owner2) && CanCapturePlayer(item2.Value, owner2, pauseEdge))
{
if (PlayerSnapshots.TryGetValue(item2.Key, out value))
{
PlayerSnapshots.Remove(item2.Key);
NextRemoteControlEnforcementAt.Remove(item2.Key);
flag2 = true;
}
if (CaptureAndDisableAgent(item2.Value, owner2))
{
flag2 = true;
}
}
}
if (!pauseEdge)
{
HashSet<ulong> members = new HashSet<ulong>(PlayerSnapshots.Keys);
HashSet<ulong> added;
HashSet<ulong> removed;
bool flag3 = PauseRoster.Reconcile(members, out added, out removed);
if (flag2 || flag3)
{
QueueMigrationSnapshotRefresh("player roster changed during pause");
}
}
}
private static bool CanCapturePlayer(PlayerAgent agent, SNet_Player owner, bool pauseEdge)
{
try
{
PlayerCharacterController val = ((agent != null) ? agent.PlayerCharacterController : null);
return (Object)(object)val != (Object)null && PausePlayerGate.ShouldCapture(pauseEdge, (Object)(object)owner != (Object)null && owner.IsInDropIn, val.m_isSetup, val.IsGrounded);
}
catch
{
return false;
}
}
private static bool TryReadSlottedLookups(HashSet<ulong> lookups)
{
try
{
SNet_PlayerSlotManager slots = SNet.Slots;
if ((Object)(object)slots == (Object)null || slots.SlottedPlayers == null)
{
return false;
}
Enumerator<SNet_Player> enumerator = slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if ((Object)(object)current != (Object)null && current.IsInSlot && current.Lookup != 0L)
{
lookups.Add(current.Lookup);
}
}
return true;
}
catch (Exception ex)
{
if (_rosterReadWarnings++ < 3)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not read SNet.Slots.SlottedPlayers; retaining existing player snapshots: " + ex.Message));
}
}
return false;
}
}
private static bool TryGetReadySlottedOwner(PlayerAgent agent, out SNet_Player owner)
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
owner = null;
if ((Object)(object)agent == (Object)null)
{
return false;
}
try
{
owner = agent.Owner;
if ((Object)(object)owner == (Object)null || !owner.IsInSlot || owner.IsOutOfSync || owner.Lookup == 0L)
{
return false;
}
return owner.Load<pReady>().isReady;
}
catch
{
owner = null;
return false;
}
}
private static bool HasLanded(PlayerAgent agent)
{
try
{
PlayerCharacterController val = ((agent != null) ? agent.PlayerCharacterController : null);
return (Object)(object)val != (Object)null && val.m_isSetup && val.IsGrounded;
}
catch
{
return false;
}
}
private static bool IsSameAgent(PlayerSnapshot snapshot, PlayerAgent agent)
{
try
{
PlayerCharacterController val = ((agent != null) ? agent.PlayerCharacterController : null);
return (Object)(object)snapshot.Agent != (Object)null && (Object)(object)agent != (Object)null && snapshot.AgentPointer == ((Il2CppObjectBase)agent).Pointer && (Object)(object)val != (Object)null && ((Il2CppObjectBase)val).Pointer == snapshot.ControllerPointer;
}
catch
{
return false;
}
}
private static bool CaptureAndDisableAgent(PlayerAgent agent, SNet_Player owner)
{
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
//IL_00de: Unknown result type (might be due to invalid IL or missing references)
//IL_00df: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)agent == (Object)null || (Object)(object)owner == (Object)null || owner.Lookup == 0L || PlayerSnapshots.ContainsKey(owner.Lookup))
{
return false;
}
IntPtr pointer;
IntPtr pointer2;
bool updateEnabled;
eFocusState inputFilter;
InventorySlot wieldedSlot;
try
{
PlayerCharacterController playerCharacterController = agent.PlayerCharacterController;
if ((Object)(object)playerCharacterController == (Object)null || !playerCharacterController.m_isSetup)
{
return false;
}
pointer = ((Il2CppObjectBase)agent).Pointer;
pointer2 = ((Il2CppObjectBase)playerCharacterController).Pointer;
if (pointer == IntPtr.Zero || pointer2 == IntPtr.Zero)
{
return false;
}
updateEnabled = playerCharacterController.m_updateEnabled;
inputFilter = agent.InputFilter;
wieldedSlot = (InventorySlot)(((Object)(object)agent.Inventory != (Object)null) ? ((int)agent.Inventory.WieldedSlot) : 0);
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not snapshot a player's initialized control state: " + ex.Message));
}
return false;
}
PlayerSnapshot playerSnapshot = new PlayerSnapshot(agent, pointer, pointer2, updateEnabled, inputFilter, wieldedSlot);
PlayerSnapshots.Add(owner.Lookup, playerSnapshot);
TryApplyPlayerPause(playerSnapshot);
return true;
}
private static bool TryApplyPlayerPause(PlayerSnapshot snapshot)
{
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
if (snapshot == null)
{
return false;
}
try
{
PlayerAgent agent = snapshot.Agent;
if ((Object)(object)agent == (Object)null || ((Il2CppObjectBase)agent).Pointer != snapshot.AgentPointer)
{
snapshot.PauseApplied = false;
return false;
}
PlayerCharacterController playerCharacterController = agent.PlayerCharacterController;
if ((Object)(object)playerCharacterController == (Object)null || ((Il2CppObjectBase)playerCharacterController).Pointer != snapshot.ControllerPointer || !playerCharacterController.m_isSetup)
{
snapshot.PauseApplied = false;
return false;
}
agent.RequestToggleControlsEnabled(false);
playerCharacterController.SetEnabled(false);
if ((Object)(object)agent.Inventory != (Object)null && (int)agent.Inventory.WieldedSlot != 0 && (Object)(object)agent.Sync != (Object)null)
{
agent.Sync.WantsToWieldSlot((InventorySlot)0, false);
}
snapshot.PauseApplied = true;
snapshot.PauseApplyWarningLogged = false;
return true;
}
catch (Exception ex)
{
snapshot.PauseApplied = false;
if (!snapshot.PauseApplyWarningLogged)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not apply a player's pause control lock; retrying on roster refresh: " + ex.Message));
}
snapshot.PauseApplyWarningLogged = true;
}
return false;
}
}
private static void CaptureAndDisableReactors()
{
try
{
foreach (LG_WardenObjective_Reactor item in Object.FindObjectsOfType<LG_WardenObjective_Reactor>())
{
if ((Object)(object)item == (Object)null)
{
continue;
}
IntPtr pointer = ((Il2CppObjectBase)item).Pointer;
if (pointer == IntPtr.Zero)
{
continue;
}
if (ReactorReferences.TryGetValue(pointer, out var value))
{
if ((Object)(object)value != (Object)null)
{
ReactorReferences[pointer] = item;
item.m_progressUpdateEnabled = false;
continue;
}
ReactorReferences.Remove(pointer);
ReactorProgress.Remove(pointer);
}
if (IsActiveProgressReactor(item) && ReactorProgress.TryCapture(pointer, item.m_progressUpdateEnabled))
{
ReactorReferences[pointer] = item;
item.m_progressUpdateEnabled = false;
}
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not enumerate Warden objective reactors for pause: " + ex.Message));
}
}
}
private static bool IsActiveProgressReactor(LG_WardenObjective_Reactor reactor)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0022: Invalid comparison between Unknown and I4
//IL_0024: 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_0028: Invalid comparison between Unknown and I4
if (!reactor.m_isWardenObjective || reactor.ObjectiveItemSolved)
{
return false;
}
eReactorStatus status = reactor.m_currentState.status;
if (status - 2 <= 2 || status - 6 <= 2)
{
return true;
}
return false;
}
private static void QueueMigrationSnapshotRefresh(string reason)
{
_migrationRefreshPending = true;
_migrationRefreshReason = reason;
}
private static void CaptureMigrationSnapshotNow(string reason)
{
_hasMigrationSnapshot = false;
PauseStateSnapshot snapshot;
bool flag = Session.TryGetSnapshot(out snapshot);
try
{
if (flag)
{
TemporarilyRestorePauseEdgeStateForCapture(snapshot);
}
if (SNet.IsMaster && !((Object)(object)SNet.Capture == (Object)null))
{
SNet.Capture.CaptureGameState((eBufferType)4);
_hasMigrationSnapshot = true;
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogInfo((object)("Migration_A pause-edge snapshot captured (" + reason + ")."));
}
}
}
catch (Exception ex)
{
_hasMigrationSnapshot = false;
ManualLogSource pluginLog2 = Plugin.PluginLog;
if (pluginLog2 != null)
{
pluginLog2.LogWarning((object)("Migration_A pause snapshot failed (" + reason + "): " + ex.Message));
}
}
finally
{
if (flag)
{
ReapplyPausedState(snapshot);
}
}
}
private static void TemporarilyRestorePauseEdgeStateForCapture(PauseStateSnapshot snapshot)
{
foreach (KeyValuePair<IntPtr, LG_WardenObjective_Reactor> reactorReference in ReactorReferences)
{
if ((Object)(object)reactorReference.Value != (Object)null && ReactorProgress.TryGet(reactorReference.Key, out var wasRunning))
{
reactorReference.Value.m_progressUpdateEnabled = wasRunning;
}
}
TemporarilyRestorePlayersForCapture();
Clock.SetPaused(snapshot.ClockWasPaused);
Clock.ExpeditionProgressionTime = snapshot.ExpeditionProgressionTime;
}
private static void TemporarilyRestorePlayersForCapture()
{
//IL_007d: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
foreach (PlayerSnapshot value in PlayerSnapshots.Values)
{
PlayerAgent agent = value.Agent;
if (!((Object)(object)agent == (Object)null) && !(((Il2CppObjectBase)agent).Pointer != value.AgentPointer))
{
agent.RequestToggleControlsEnabled(value.ControlsWereEnabled);
PlayerCharacterController playerCharacterController = agent.PlayerCharacterController;
if ((Object)(object)playerCharacterController != (Object)null && ((Il2CppObjectBase)playerCharacterController).Pointer == value.ControllerPointer)
{
playerCharacterController.SetEnabled(value.ControlsWereEnabled);
}
agent.InputFilter = value.InputFilter;
if ((Object)(object)agent.Sync != (Object)null)
{
agent.Sync.WantsToWieldSlot(value.WieldedSlot, false);
}
}
}
}
private static void ReapplyPausedState(PauseStateSnapshot snapshot)
{
try
{
Clock.SetPaused(true);
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not reapply Clock pause after snapshot capture: " + ex.Message));
}
}
try
{
Clock.ExpeditionProgressionTime = snapshot.ExpeditionProgressionTime;
}
catch (Exception ex2)
{
ManualLogSource pluginLog2 = Plugin.PluginLog;
if (pluginLog2 != null)
{
pluginLog2.LogWarning((object)("Could not reapply expedition time after snapshot capture: " + ex2.Message));
}
}
foreach (KeyValuePair<IntPtr, LG_WardenObjective_Reactor> reactorReference in ReactorReferences)
{
if (!((Object)(object)reactorReference.Value != (Object)null))
{
continue;
}
try
{
reactorReference.Value.m_progressUpdateEnabled = false;
}
catch (Exception ex3)
{
ManualLogSource pluginLog3 = Plugin.PluginLog;
if (pluginLog3 != null)
{
pluginLog3.LogWarning((object)("Could not re-freeze reactor progress: " + ex3.Message));
}
}
}
foreach (PlayerSnapshot value in PlayerSnapshots.Values)
{
TryApplyPlayerPause(value);
}
ApplyPauseTimeScale(snapshot);
}
private static void RestorePlayers()
{
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
foreach (PlayerSnapshot value in PlayerSnapshots.Values)
{
PlayerAgent agent = value.Agent;
if ((Object)(object)agent == (Object)null || ((Il2CppObjectBase)agent).Pointer != value.AgentPointer)
{
continue;
}
try
{
agent.RequestToggleControlsEnabled(value.ControlsWereEnabled);
PlayerCharacterController playerCharacterController = agent.PlayerCharacterController;
if ((Object)(object)playerCharacterController != (Object)null && ((Il2CppObjectBase)playerCharacterController).Pointer == value.ControllerPointer)
{
playerCharacterController.SetEnabled(value.ControlsWereEnabled);
}
agent.InputFilter = value.InputFilter;
if ((Object)(object)agent.Sync != (Object)null)
{
agent.Sync.WantsToWieldSlot(value.WieldedSlot, false);
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not restore a player's pre-pause control/equipment state: " + ex.Message));
}
}
}
PlayerSnapshots.Clear();
}
private static void RestoreReactors()
{
foreach (KeyValuePair<IntPtr, LG_WardenObjective_Reactor> reactorReference in ReactorReferences)
{
if (!ReactorProgress.TryTake(reactorReference.Key, out var wasRunning) || !((Object)(object)reactorReference.Value != (Object)null))
{
continue;
}
try
{
reactorReference.Value.m_progressUpdateEnabled = wasRunning;
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Could not restore reactor progress state: " + ex.Message));
}
}
}
ReactorReferences.Clear();
ReactorProgress.Clear();
}
private static void ClearSessionData()
{
Session.Reset();
_hasMigrationSnapshot = false;
_migrationRefreshPending = false;
_migrationRefreshReason = null;
_nextPlayerRefreshAt = 0f;
_pendingPlayerSince = -1f;
_pendingPlayerWarningLogged = false;
_nextReactorRefreshAt = 0f;
PlayerSnapshots.Clear();
PauseRoster.Clear();
ReactorReferences.Clear();
ReactorProgress.Clear();
NextRemoteControlEnforcementAt.Clear();
}
}
[HarmonyPatch(typeof(GameStateManager), "DoChangeState", new Type[] { typeof(eGameStateName) })]
internal static class PauseGameStatePatch
{
[HarmonyPrefix]
private static void Prefix(eGameStateName __0)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0003: Invalid comparison between Unknown and I4
if ((int)__0 != 10)
{
PauseCoordinator.ForceResume("leaving InLevel", requestNetworkRecall: false);
}
}
}
[HarmonyPatch(typeof(GS_AfterLevel), "CleanupAfterExpedition", new Type[] { })]
internal static class PauseLevelCleanupPatch
{
[HarmonyPrefix]
private static void Prefix()
{
PauseCoordinator.ForceResume("expedition cleanup", requestNetworkRecall: false);
}
}
[HarmonyPatch(typeof(PlayerSync), "IncomingLocomotion", new Type[] { typeof(pPlayerLocomotion) })]
internal static class PauseRemoteLocomotionPatch
{
[HarmonyPostfix]
private static void Postfix(PlayerSync __instance, pPlayerLocomotion __0)
{
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.m_agent == (Object)null))
{
PauseCoordinator.HandleRemoteLocomotion(__instance.m_agent, __0.Pos);
}
}
}
internal sealed class PauseInputGate
{
internal const float DefaultHoldSeconds = 0.5f;
internal const float MinimumHoldSeconds = 0.1f;
internal const float MaximumHoldSeconds = 3f;
private bool _holding;
private bool _triggered;
private double _holdStartedAt;
internal bool Tick(bool eligible, bool keyHeld, double unscaledNow, float holdSeconds)
{
if (!eligible || !keyHeld || double.IsNaN(unscaledNow) || double.IsInfinity(unscaledNow))
{
Reset();
return false;
}
if (!_holding)
{
_holding = true;
_triggered = false;
_holdStartedAt = unscaledNow;
return false;
}
if (_triggered)
{
return false;
}
if (unscaledNow < _holdStartedAt)
{
_holdStartedAt = unscaledNow;
return false;
}
double num = NormalizeHoldSeconds(holdSeconds);
if (unscaledNow - _holdStartedAt < num)
{
return false;
}
_triggered = true;
return true;
}
internal void Reset()
{
_holding = false;
_triggered = false;
_holdStartedAt = 0.0;
}
internal static float NormalizeHoldSeconds(float value)
{
if (float.IsNaN(value) || float.IsInfinity(value))
{
return 0.5f;
}
return Math.Clamp(value, 0.1f, 3f);
}
}
internal static class PausePlayerGate
{
internal static bool ShouldCapture(bool pauseEdge, bool isDropIn, bool controllerIsSetup, bool isGrounded)
{
if (controllerIsSetup && !isDropIn)
{
return pauseEdge || isGrounded;
}
return false;
}
}
internal readonly struct PauseRosterMemberState
{
internal bool IsSlotted { get; }
internal bool HasLookup { get; }
internal bool IsInDropIn { get; }
internal bool HasPauseSnapshot { get; }
internal bool PauseApplied { get; }
internal PauseRosterMemberState(bool isSlotted, bool hasLookup, bool isInDropIn, bool hasPauseSnapshot, bool pauseApplied)
{
IsSlotted = isSlotted;
HasLookup = hasLookup;
IsInDropIn = isInDropIn;
HasPauseSnapshot = hasPauseSnapshot;
PauseApplied = pauseApplied;
}
}
internal static class PauseTimeScalePolicy
{
internal static bool HasPendingPlayer(IEnumerable<PauseRosterMemberState> roster, bool rosterReadable)
{
if (!rosterReadable || roster == null)
{
return true;
}
foreach (PauseRosterMemberState item in roster)
{
if (item.IsSlotted && (!item.HasLookup || item.IsInDropIn || !item.HasPauseSnapshot || !item.PauseApplied))
{
return true;
}
}
return false;
}
internal static float SelectTarget(float pauseEdgeTimeScale, bool hasPendingPlayer)
{
if (!hasPendingPlayer)
{
return 0f;
}
if (!float.IsNaN(pauseEdgeTimeScale) && !float.IsInfinity(pauseEdgeTimeScale) && !(pauseEdgeTimeScale <= 0f))
{
return pauseEdgeTimeScale;
}
return 1f;
}
}
internal static class PauseResumeMigrationPolicy
{
internal static bool ShouldCaptureFreshSnapshot(bool recallRequested, bool pauseEdgeSnapshotAvailable, bool isHostInLevel)
{
return recallRequested && pauseEdgeSnapshotAvailable && isHostInLevel;
}
internal static bool ShouldRecall(bool recallRequested, bool freshSnapshotCaptured, bool isHostInLevel)
{
return recallRequested && freshSnapshotCaptured && isHostInLevel;
}
}
internal readonly struct PauseStateSnapshot
{
internal float TimeScale { get; }
internal bool ClockWasPaused { get; }
internal float ExpeditionProgressionTime { get; }
internal bool IsValid
{
get
{
if (!float.IsNaN(TimeScale) && !float.IsInfinity(TimeScale) && !float.IsNaN(ExpeditionProgressionTime))
{
return !float.IsInfinity(ExpeditionProgressionTime);
}
return false;
}
}
internal PauseStateSnapshot(float timeScale, bool clockWasPaused, float expeditionProgressionTime)
{
TimeScale = timeScale;
ClockWasPaused = clockWasPaused;
ExpeditionProgressionTime = expeditionProgressionTime;
}
}
internal sealed class PauseSessionState
{
private PauseStateSnapshot _snapshot;
internal bool IsPaused { get; private set; }
internal bool TryBegin(PauseStateSnapshot snapshot)
{
if (IsPaused || !snapshot.IsValid)
{
return false;
}
_snapshot = snapshot;
IsPaused = true;
return true;
}
internal bool TryGetSnapshot(out PauseStateSnapshot snapshot)
{
snapshot = _snapshot;
return IsPaused;
}
internal bool TryEnd(out PauseStateSnapshot snapshot)
{
if (!IsPaused)
{
snapshot = default(PauseStateSnapshot);
return false;
}
snapshot = _snapshot;
_snapshot = default(PauseStateSnapshot);
IsPaused = false;
return true;
}
internal void Reset()
{
_snapshot = default(PauseStateSnapshot);
IsPaused = false;
}
}
internal sealed class BooleanStateLedger<TKey> where TKey : notnull
{
private readonly Dictionary<TKey, bool> _states = new Dictionary<TKey, bool>();
internal int Count => _states.Count;
internal bool TryCapture(TKey key, bool wasRunning)
{
if (_states.ContainsKey(key))
{
return false;
}
_states.Add(key, wasRunning);
return true;
}
internal bool TryGet(TKey key, out bool wasRunning)
{
return _states.TryGetValue(key, out wasRunning);
}
internal bool TryTake(TKey key, out bool wasRunning)
{
if (!_states.TryGetValue(key, out wasRunning))
{
return false;
}
_states.Remove(key);
return true;
}
internal bool Remove(TKey key)
{
return _states.Remove(key);
}
internal void Clear()
{
_states.Clear();
}
}
internal sealed class RosterSnapshot<TKey> where TKey : notnull
{
private readonly HashSet<TKey> _members = new HashSet<TKey>();
internal int Count => _members.Count;
internal void Reset(IEnumerable<TKey> members)
{
_members.Clear();
foreach (TKey member in members)
{
_members.Add(member);
}
}
internal bool Reconcile(IEnumerable<TKey> members, out HashSet<TKey> added, out HashSet<TKey> removed)
{
HashSet<TKey> hashSet = new HashSet<TKey>(members);
added = new HashSet<TKey>();
removed = new HashSet<TKey>();
if (hashSet.Count == 0 && _members.Count != 0)
{
return false;
}
added.UnionWith(hashSet);
added.ExceptWith(_members);
removed.UnionWith(_members);
removed.ExceptWith(hashSet);
if (added.Count == 0 && removed.Count == 0)
{
return false;
}
_members.Clear();
_members.UnionWith(hashSet);
return true;
}
internal void Clear()
{
_members.Clear();
}
}
internal sealed class PauseRuntime : MonoBehaviour
{
private readonly PauseInputGate _inputGate = new PauseInputGate();
public PauseRuntime(IntPtr pointer)
: base(pointer)
{
}
public void Update()
{
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0045: Unknown result type (might be due to invalid IL or missing references)
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
try
{
if (Plugin.Enabled == null || !Plugin.Enabled.Value)
{
_inputGate.Reset();
if (PauseCoordinator.IsPaused)
{
PauseCoordinator.ForceResume("plugin disabled", requestNetworkRecall: true);
}
return;
}
bool isHostInLevel = PauseCoordinator.IsHostInLevel;
KeyCode value = Plugin.PauseKey.Value;
bool keyHeld = (int)value != 0 && Input.GetKey(value);
if (_inputGate.Tick(isHostInLevel, keyHeld, Time.unscaledTime, Plugin.HoldSeconds.Value))
{
PauseCoordinator.Toggle();
}
if (PauseCoordinator.IsPaused)
{
PauseCoordinator.EnforcePause(Time.unscaledTime);
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogError((object)("Pause runtime update failed: " + ex));
}
_inputGate.Reset();
if (PauseCoordinator.IsPaused)
{
PauseCoordinator.ForceResume("runtime update exception", requestNetworkRecall: true);
}
}
}
public void LateUpdate()
{
try
{
if (PauseCoordinator.IsPaused)
{
PauseCoordinator.FlushPendingMigrationSnapshot();
}
}
catch (Exception ex)
{
ManualLogSource pluginLog = Plugin.PluginLog;
if (pluginLog != null)
{
pluginLog.LogWarning((object)("Deferred pause snapshot refresh failed: " + ex.Message));
}
}
}
public void OnDestroy()
{
_inputGate.Reset();
PauseCoordinator.ForceResume("runtime component destroyed", requestNetworkRecall: true);
}
}
[BepInPlugin("HawChow.GTFOPause", "GTFOPause", "1.0.1")]
public sealed class Plugin : BasePlugin
{
internal static ManualLogSource PluginLog { get; private set; }
internal static ConfigEntry<bool> Enabled { get; private set; }
internal static ConfigEntry<KeyCode> PauseKey { get; private set; }
internal static ConfigEntry<float> HoldSeconds { get; private set; }
public override void Load()
{
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0084: Expected O, but got Unknown
//IL_0093: Unknown result type (might be due to invalid IL or missing references)
//IL_0099: Expected O, but got Unknown
//IL_010d: Unknown result type (might be due to invalid IL or missing references)
//IL_0113: Expected O, but got Unknown
//IL_0148: Unknown result type (might be due to invalid IL or missing references)
PluginLog = ((BasePlugin)this).Log;
Enabled = ((BasePlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable host pause and resume hotkey handling.");
PauseKey = ((BasePlugin)this).Config.Bind<KeyCode>("Input", "PauseKey", (KeyCode)112, "Host-only pause key. None disables the hotkey.");
HoldSeconds = ((BasePlugin)this).Config.Bind<float>("Input", "HoldSeconds", 0.5f, new ConfigDescription("Continuous hold time before a single pause/resume toggle.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 3f), Array.Empty<object>()));
ClassInjector.RegisterTypeInIl2Cpp<PauseRuntime>();
Harmony val = new Harmony("HawChow.GTFOPause");
try
{
val.PatchAll(typeof(Plugin).Assembly);
RequirePatch(val, typeof(GameStateManager), "DoChangeState");
RequirePatch(val, typeof(GS_AfterLevel), "CleanupAfterExpedition");
RequirePatch(val, typeof(PlayerSync), "IncomingLocomotion");
((BasePlugin)this).AddComponent<PauseRuntime>();
}
catch
{
val.UnpatchSelf();
throw;
}
ManualLogSource log = ((BasePlugin)this).Log;
bool flag = default(bool);
BepInExInfoLogInterpolatedStringHandler val2 = new BepInExInfoLogInterpolatedStringHandler(129, 4, ref flag);
if (flag)
{
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("GTFOPause");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" ");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("1.0.1");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" loaded; host-only hold-P toggle=");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<KeyCode>(PauseKey.Value);
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(", hold=");
((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>(HoldSeconds.Value, "0.##");
((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("s; GTFO Clock and controlled players pause while Unity physics remains live for drop-in.");
}
log.LogInfo(val2);
}
private static void RequirePatch(Harmony harmony, Type targetType, string methodName)
{
foreach (MethodBase patchedMethod in harmony.GetPatchedMethods())
{
if (patchedMethod.DeclaringType == targetType && patchedMethod.Name == methodName)
{
return;
}
}
throw new InvalidOperationException("Required Harmony patch was not installed: " + targetType.FullName + "." + methodName);
}
}
internal static class PluginInfo
{
internal const string Guid = "HawChow.GTFOPause";
internal const string Name = "GTFOPause";
internal const string Version = "1.0.1";
}
}