using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Agents;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using CellMenu;
using GTFO.PersistentCheckpoint.Core;
using GTFO.PersistentCheckpoint.Persistence;
using GameData;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Runtime.InteropServices;
using LevelGeneration;
using Localization;
using Microsoft.CodeAnalysis;
using Player;
using SNetwork;
using TMPro;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("GTFO Persistent Checkpoint")]
[assembly: AssemblyDescription("Persists GTFO checkpoint capture buffers between game sessions.")]
[assembly: AssemblyCompany("TheRoughy")]
[assembly: AssemblyProduct("GTFO Persistent Checkpoint")]
[assembly: ComVisible(false)]
[assembly: Guid("a11a2774-4115-4e28-a803-8f3fc4848f01")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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;
}
}
[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;
}
}
}
namespace GTFO.PersistentCheckpoint.Persistence
{
internal readonly record struct CheckpointIdentity(string ExpeditionKey, string RundownName, string ExpeditionName)
{
internal bool IsValid
{
get
{
if (!string.IsNullOrWhiteSpace(ExpeditionKey) && !string.IsNullOrWhiteSpace(RundownName))
{
return !string.IsNullOrWhiteSpace(ExpeditionName);
}
return false;
}
}
internal string GetFilename()
{
if (!IsValid)
{
throw new InvalidDataException("Checkpoint mission names are unavailable.");
}
using MemoryStream memoryStream = new MemoryStream();
using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
{
binaryWriter.Write("GTFO checkpoint identity v1");
binaryWriter.Write(ExpeditionKey);
binaryWriter.Write(RundownName);
binaryWriter.Write(ExpeditionName);
}
return Convert.ToHexString(SHA256.HashData(memoryStream.ToArray())) + ".pcp";
}
[CompilerGenerated]
private bool PrintMembers(StringBuilder builder)
{
builder.Append("ExpeditionKey = ");
builder.Append((object?)ExpeditionKey);
builder.Append(", RundownName = ");
builder.Append((object?)RundownName);
builder.Append(", ExpeditionName = ");
builder.Append((object?)ExpeditionName);
return true;
}
}
internal sealed record CheckpointSnapshot
{
internal int BuildRevision { get; init; }
internal string ExpeditionKey { get; init; } = string.Empty;
internal string RundownKey { get; init; } = string.Empty;
internal string RundownName { get; init; } = string.Empty;
internal string ExpeditionName { get; init; } = string.Empty;
internal CheckpointIdentity Identity => new CheckpointIdentity(ExpeditionKey, RundownName, ExpeditionName);
internal int Tier { get; init; }
internal int ExpeditionIndex { get; init; }
internal int HostSeed { get; init; }
internal int SessionSeed { get; init; }
internal ulong LevelChecksum { get; init; }
internal float ProgressionTime { get; init; }
internal ulong RecallCount { get; init; }
internal long SavedAtUtcTicks { get; init; }
internal int RequiredSlotCapacity { get; init; } = 4;
internal bool LobbyExpansionPresent { get; init; }
internal string LobbyExpansionVersion { get; init; } = string.Empty;
internal List<CapturedPlayer> Players { get; init; } = new List<CapturedPlayer>();
internal List<List<byte[]>> Passes { get; init; } = new List<List<byte[]>>();
[CompilerGenerated]
private bool PrintMembers(StringBuilder builder)
{
return false;
}
}
internal readonly record struct CapturedPlayer(ulong Lookup, int CharacterIndex, bool IsBot, bool HasResources = false, float HealthRel = 0f, float Infection = 0f, float StandardAmmo = 0f, float SpecialAmmo = 0f, float ClassAmmo = 0f, float ResourcePackAmmo = 0f, float ConsumableAmmo = 0f);
internal readonly record struct SnapshotLoadResult(CheckpointSnapshot? Snapshot, string? Error)
{
internal bool Success => Snapshot != null;
[CompilerGenerated]
private bool PrintMembers(StringBuilder builder)
{
builder.Append("Snapshot = ");
builder.Append(Snapshot);
builder.Append(", Error = ");
builder.Append((object?)Error);
return true;
}
}
internal static class CheckpointStore
{
private static readonly byte[] Magic = Encoding.ASCII.GetBytes("GTFOCP01");
private const int FormatVersion = 3;
private const int HashLength = 32;
private const int MaxStringBytes = 2048;
private const int MaxPasses = 32;
private const int MaxPacketsPerPass = 1000000;
private const int MaxPacketBytes = 16777216;
private const long MaxPayloadBytes = 536870912L;
private static readonly object QueueLock = new object();
private static Task _pending = Task.CompletedTask;
private static string _directory = string.Empty;
internal static void Initialize()
{
_directory = Path.Combine(Paths.ConfigPath, "GTFO_PersistentCheckpoint", "checkpoints");
Directory.CreateDirectory(_directory);
}
internal static SnapshotLoadResult TryLoad(CheckpointIdentity identity)
{
try
{
string path = GetPath(identity);
if (!File.Exists(path))
{
return new SnapshotLoadResult(null, null);
}
byte[] array = File.ReadAllBytes(path);
if (array.LongLength < Magic.Length + 4 + 32 || array.LongLength > 536870912)
{
return new SnapshotLoadResult(null, "checkpoint file has an invalid size");
}
int num = array.Length - 32;
if (!CryptographicOperations.FixedTimeEquals(SHA256.HashData(array.AsSpan(0, num)), array.AsSpan(num, 32)))
{
return new SnapshotLoadResult(null, "checkpoint integrity check failed");
}
using MemoryStream memoryStream = new MemoryStream(array, 0, num, writable: false);
using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: false);
if (!binaryReader.ReadBytes(Magic.Length).SequenceEqual(Magic))
{
return new SnapshotLoadResult(null, "checkpoint magic is invalid");
}
if (binaryReader.ReadInt32() != 3)
{
return new SnapshotLoadResult(null, "checkpoint format is unsupported");
}
CheckpointSnapshot checkpointSnapshot = ReadSnapshot(binaryReader);
if (memoryStream.Position != memoryStream.Length)
{
return new SnapshotLoadResult(null, "checkpoint contains trailing payload data");
}
if (!string.Equals(checkpointSnapshot.ExpeditionKey, identity.ExpeditionKey, StringComparison.Ordinal))
{
return new SnapshotLoadResult(null, "checkpoint expedition identity does not match its file");
}
if (checkpointSnapshot.Identity != identity)
{
return new SnapshotLoadResult(null, "checkpoint mission names do not match the selected mission");
}
return new SnapshotLoadResult(checkpointSnapshot, null);
}
catch (Exception ex)
{
Log.Warning("Failed to load checkpoint for " + identity.ExpeditionKey + ": " + ex.Message);
return new SnapshotLoadResult(null, ex.Message);
}
}
internal static void EnqueueSave(CheckpointSnapshot snapshot)
{
Enqueue(delegate
{
WriteAtomic(snapshot);
});
}
internal static void EnqueueDelete(CheckpointIdentity identity)
{
Enqueue(delegate
{
string path = GetPath(identity);
if (File.Exists(path))
{
File.Delete(path);
}
});
}
internal static void Flush(TimeSpan timeout)
{
Task pending;
lock (QueueLock)
{
pending = _pending;
}
try
{
if (!pending.Wait(timeout))
{
Log.Warning("Timed out while flushing checkpoint writes.");
}
}
catch (Exception exception)
{
Log.Error(exception);
}
}
private static void Enqueue(Action operation)
{
lock (QueueLock)
{
_pending = _pending.ContinueWith(delegate
{
try
{
operation();
}
catch (Exception exception)
{
Log.Error(exception);
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
}
}
private static void WriteAtomic(CheckpointSnapshot snapshot)
{
Directory.CreateDirectory(_directory);
using MemoryStream memoryStream = new MemoryStream();
using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
{
binaryWriter.Write(Magic);
binaryWriter.Write(3);
WriteSnapshot(binaryWriter, snapshot);
}
if (memoryStream.Length > 536870912)
{
throw new InvalidDataException($"Checkpoint payload is too large ({memoryStream.Length} bytes).");
}
byte[] array = memoryStream.ToArray();
byte[] array2 = SHA256.HashData(array);
string path = GetPath(snapshot.Identity);
string text = path + ".tmp";
using (FileStream fileStream = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
{
fileStream.Write(array);
fileStream.Write(array2);
fileStream.Flush(flushToDisk: true);
}
File.Move(text, path, overwrite: true);
Log.Info($"Saved checkpoint for {snapshot.ExpeditionKey} ({array.Length + array2.Length:N0} bytes).");
}
private static void WriteSnapshot(BinaryWriter writer, CheckpointSnapshot snapshot)
{
writer.Write(snapshot.BuildRevision);
WriteString(writer, snapshot.ExpeditionKey);
WriteString(writer, snapshot.RundownKey);
WriteString(writer, snapshot.RundownName);
WriteString(writer, snapshot.ExpeditionName);
writer.Write(snapshot.Tier);
writer.Write(snapshot.ExpeditionIndex);
writer.Write(snapshot.HostSeed);
writer.Write(snapshot.SessionSeed);
writer.Write(snapshot.LevelChecksum);
writer.Write(snapshot.ProgressionTime);
writer.Write(snapshot.RecallCount);
writer.Write(snapshot.SavedAtUtcTicks);
writer.Write(snapshot.RequiredSlotCapacity);
writer.Write(snapshot.LobbyExpansionPresent);
WriteString(writer, snapshot.LobbyExpansionVersion);
writer.Write(snapshot.Players.Count);
foreach (CapturedPlayer player in snapshot.Players)
{
writer.Write(player.Lookup);
writer.Write(player.CharacterIndex);
writer.Write(player.IsBot);
writer.Write(player.HasResources);
writer.Write(player.HealthRel);
writer.Write(player.Infection);
writer.Write(player.StandardAmmo);
writer.Write(player.SpecialAmmo);
writer.Write(player.ClassAmmo);
writer.Write(player.ResourcePackAmmo);
writer.Write(player.ConsumableAmmo);
}
writer.Write(snapshot.Passes.Count);
foreach (List<byte[]> pass in snapshot.Passes)
{
writer.Write(pass.Count);
foreach (byte[] item in pass)
{
if (item.Length > 16777216)
{
throw new InvalidDataException($"Capture packet is too large ({item.Length} bytes).");
}
writer.Write(item.Length);
writer.Write(item);
}
}
}
private static CheckpointSnapshot ReadSnapshot(BinaryReader reader)
{
int buildRevision = reader.ReadInt32();
string expeditionKey = ReadString(reader);
string rundownKey = ReadString(reader);
string rundownName = ReadString(reader);
string expeditionName = ReadString(reader);
int tier = reader.ReadInt32();
int expeditionIndex = reader.ReadInt32();
int hostSeed = reader.ReadInt32();
int sessionSeed = reader.ReadInt32();
ulong levelChecksum = reader.ReadUInt64();
float progressionTime = reader.ReadSingle();
ulong recallCount = reader.ReadUInt64();
long savedAtUtcTicks = reader.ReadInt64();
int val = reader.ReadInt32();
bool lobbyExpansionPresent = reader.ReadBoolean();
string lobbyExpansionVersion = ReadString(reader);
int num = ReadBoundedCount(reader, 256, "player");
List<CapturedPlayer> list = new List<CapturedPlayer>(num);
for (int i = 0; i < num; i++)
{
ulong lookup = reader.ReadUInt64();
int characterIndex = reader.ReadInt32();
bool isBot = reader.ReadBoolean();
list.Add(new CapturedPlayer(lookup, characterIndex, isBot, reader.ReadBoolean(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()));
}
int num2 = ReadBoundedCount(reader, 32, "capture pass");
List<List<byte[]>> list2 = new List<List<byte[]>>(num2);
long num3 = 0L;
for (int j = 0; j < num2; j++)
{
int num4 = ReadBoundedCount(reader, 1000000, "packet");
List<byte[]> list3 = new List<byte[]>(num4);
for (int k = 0; k < num4; k++)
{
int num5 = ReadBoundedCount(reader, 16777216, "packet byte");
num3 += num5;
if (num3 > 536870912)
{
throw new InvalidDataException("Checkpoint packet data exceeds the allowed size.");
}
byte[] array = reader.ReadBytes(num5);
if (array.Length != num5)
{
throw new EndOfStreamException("Checkpoint packet was truncated.");
}
list3.Add(array);
}
list2.Add(list3);
}
return new CheckpointSnapshot
{
BuildRevision = buildRevision,
ExpeditionKey = expeditionKey,
RundownKey = rundownKey,
RundownName = rundownName,
ExpeditionName = expeditionName,
Tier = tier,
ExpeditionIndex = expeditionIndex,
HostSeed = hostSeed,
SessionSeed = sessionSeed,
LevelChecksum = levelChecksum,
ProgressionTime = progressionTime,
RecallCount = recallCount,
SavedAtUtcTicks = savedAtUtcTicks,
RequiredSlotCapacity = Math.Max(4, val),
LobbyExpansionPresent = lobbyExpansionPresent,
LobbyExpansionVersion = lobbyExpansionVersion,
Players = list,
Passes = list2
};
}
private static int ReadBoundedCount(BinaryReader reader, int maximum, string name)
{
int num = reader.ReadInt32();
if (num < 0 || num > maximum)
{
throw new InvalidDataException($"Invalid {name} count: {num}.");
}
return num;
}
private static void WriteString(BinaryWriter writer, string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);
if (bytes.Length > 2048)
{
throw new InvalidDataException("Checkpoint string is too long.");
}
writer.Write(bytes.Length);
writer.Write(bytes);
}
private static string ReadString(BinaryReader reader)
{
int num = ReadBoundedCount(reader, 2048, "string byte");
byte[] array = reader.ReadBytes(num);
if (array.Length != num)
{
throw new EndOfStreamException("Checkpoint string was truncated.");
}
return Encoding.UTF8.GetString(array);
}
private static string GetPath(CheckpointIdentity identity)
{
return Path.Combine(_directory, identity.GetFilename());
}
}
internal static class DestroyedDoorPreparation
{
internal static int Prepare(CheckpointSnapshot snapshot)
{
//IL_0245: Unknown result type (might be due to invalid IL or missing references)
//IL_0182: Unknown result type (might be due to invalid IL or missing references)
//IL_0187: Unknown result type (might be due to invalid IL or missing references)
//IL_0189: Unknown result type (might be due to invalid IL or missing references)
//IL_018b: Unknown result type (might be due to invalid IL or missing references)
//IL_0192: Invalid comparison between Unknown and I4
//IL_0196: Unknown result type (might be due to invalid IL or missing references)
//IL_019b: Unknown result type (might be due to invalid IL or missing references)
//IL_01a2: Invalid comparison between Unknown and I4
//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
if (!SNet.IsMaster)
{
return 0;
}
Dictionary<(ushort, byte), byte[]> dictionary = new Dictionary<(ushort, byte), byte[]>();
foreach (List<byte[]> pass in snapshot.Passes)
{
foreach (byte[] item in pass)
{
if (item.Length >= 3)
{
dictionary[(BinaryPrimitives.ReadUInt16LittleEndian(item), item[2])] = item;
}
}
}
List<(LG_Door_Sync, pDoorState, string)> list = new List<(LG_Door_Sync, pDoorState, string)>();
foreach (Object item2 in (Il2CppArrayBase<Object>)(object)Resources.FindObjectsOfTypeAll(Il2CppType.Of<LG_WeakDoor>()))
{
LG_WeakDoor val = ((Il2CppObjectBase)item2).TryCast<LG_WeakDoor>();
if ((Object)(object)val == (Object)null)
{
continue;
}
try
{
if ((Object)(object)val.Gate == (Object)null)
{
continue;
}
Component syncComp = val.m_syncComp;
LG_Door_Sync val2 = ((syncComp != null) ? ((Il2CppObjectBase)syncComp).TryCast<LG_Door_Sync>() : null);
if ((Object)(object)val2 == (Object)null || val2.m_stateReplicator == null)
{
continue;
}
SNet_Packet<pDoorState> statePacket_Recall = val2.m_stateReplicator.m_statePacket_Recall;
if (!dictionary.TryGetValue((((SNet_Packet)statePacket_Recall).Replicator.Key, ((SNet_Packet)statePacket_Recall).Index), out var value))
{
continue;
}
SNet_Marshaller<pDoorState> s_marshaller = SNet_Packet<pDoorState>.s_marshaller;
if (s_marshaller == null || value.Length != s_marshaller.SizeWithIDs)
{
Log.Warning("Skipping door preparation for " + ((Object)val).name + ": saved door packet size does not match the current game.");
continue;
}
pDoorState val3 = DoorStatePacketDecoder.Decode(value, s_marshaller.Size, ((SNet_Marshaller)s_marshaller).m_marshallingType);
if ((int)val3.status == 11 && (int)val2.GetCurrentSyncState().status != 11)
{
list.Add((val2, val3, ((Object)val).name));
}
}
catch (Exception value2)
{
Log.Warning($"Skipping door preparation for {((Object)val).name}: {value2}");
}
}
int num = 0;
foreach (var item3 in list)
{
try
{
item3.Item1.m_stateReplicator.OnStateChange_Master(item3.Item2);
num++;
Log.Info("Broadcast pre-recall destruction for " + item3.Item3 + ".");
}
catch (Exception value3)
{
Log.Warning($"Pre-recall destruction failed for {item3.Item3}; host recall repair remains available: {value3}");
}
}
return num;
}
}
internal static class DoorStatePacketDecoder
{
internal unsafe static pDoorState Decode(byte[] packet, int payloadSize, Type marshallingType)
{
//IL_006b: 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_0080: Unknown result type (might be due to invalid IL or missing references)
if (payloadSize <= 0 || packet.Length < 3 || packet.Length - 3 != payloadSize)
{
throw new InvalidDataException("Saved door packet does not match the current door marshaller's size.");
}
IntPtr intPtr = Marshal.AllocHGlobal(payloadSize);
try
{
Marshal.Copy(packet, 3, intPtr, payloadSize);
Object val = Marshal.PtrToStructure(intPtr, marshallingType);
if (val == null)
{
throw new InvalidDataException("The game's door marshaller returned no state.");
}
IntPtr intPtr2 = IL2CPP.il2cpp_object_unbox(((Il2CppObjectBase)val).Pointer);
if (intPtr2 == IntPtr.Zero)
{
throw new InvalidDataException("The game's door state could not be unboxed.");
}
pDoorState result = Unsafe.Read<pDoorState>((void*)intPtr2);
GC.KeepAlive(val);
return result;
}
finally
{
Marshal.FreeHGlobal(intPtr);
}
}
}
internal static class LobbyCompatibility
{
internal const string PluginGuid = "GTFO.LobbyExpansion";
internal static bool IsPresent => ((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.ContainsKey("GTFO.LobbyExpansion");
internal static string Version
{
get
{
if (!((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.TryGetValue("GTFO.LobbyExpansion", out var value))
{
return string.Empty;
}
return value.Instance?.GetType().Assembly.GetName().Version?.ToString() ?? "unknown";
}
}
internal static string Description
{
get
{
if (!IsPresent)
{
return "not present";
}
return "present (" + Version + ")";
}
}
internal static int CurrentSlotCapacity
{
get
{
try
{
SNet_PlayerSlotManager slots = SNet.Slots;
return ((slots == null) ? ((int?)null) : ((Il2CppArrayBase<SNet_Slot>)(object)slots.CharacterSlots)?.Length) ?? 4;
}
catch
{
return 4;
}
}
}
internal static bool CanRepresent(CheckpointSnapshot snapshot, out string reason)
{
int currentSlotCapacity = CurrentSlotCapacity;
if (currentSlotCapacity < snapshot.RequiredSlotCapacity)
{
reason = $"checkpoint needs {snapshot.RequiredSlotCapacity} character slots, current lobby has {currentSlotCapacity}";
return false;
}
reason = string.Empty;
return true;
}
}
internal static class ResumeCoordinator
{
private sealed record PlayerResync(ulong Lookup, string DisplayName);
private static MethodInfo? _setActiveExpedition;
private static MethodInfo? _startSinglePlayerRecall;
private static pActiveExpedition _freshPackage = null;
private static bool _hasFreshPackage;
private static bool _applyingPackage;
private static bool _suppressCheckpointSave;
private static int _requiredSlotCapacity = 4;
private static bool _pendingRecallDelayStarted;
private static float _pendingRecallNotBefore;
private static bool _pendingDoorPreparationStarted;
private static float _pendingDoorPreparationNotBefore;
private static readonly Queue<PlayerResync> PlayerResyncQueue = new Queue<PlayerResync>();
private static PlayerResync? _activePlayerResync;
private static CheckpointSnapshot? _playerResyncSnapshot;
private static float _activePlayerResyncDeadline;
private static float _nextPlayerResyncTime;
private static bool _activePlayerResyncSawNativeSync;
private static bool _activePlayerResyncTimedOut;
private static CheckpointSnapshot? _bridgeSnapshot;
private static float _bridgeNotBefore;
private static float _bridgeDeadline;
private static CheckpointIdentity? _activeIdentity;
private const float PendingRecallDelaySeconds = 3f;
private const float DoorPreparationDelaySeconds = 1f;
private const float InitialPlayerResyncDelaySeconds = 1.5f;
private const float PlayerResyncDelaySeconds = 0.25f;
private const float PlayerResyncTimeoutSeconds = 20f;
private const float BridgeStabilizationSeconds = 2f;
private const float BridgeReadyTimeoutSeconds = 20f;
internal static CheckpointSnapshot? ActiveSnapshot { get; private set; }
internal static CheckpointSnapshot? PendingRecall { get; private set; }
internal static string ActiveExpeditionKey { get; private set; } = string.Empty;
internal static bool ResumeSelected { get; private set; }
internal static string? LastSelectionError { get; private set; }
internal static bool HasResumeChoice
{
get
{
if (ActiveSnapshot != null)
{
return !string.IsNullOrEmpty(ActiveExpeditionKey);
}
return false;
}
}
internal static bool ShouldShowControl
{
get
{
if (!HasResumeChoice)
{
return LastSelectionError != null;
}
return true;
}
}
internal static bool ShouldShowCreateControl
{
get
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Invalid comparison between Unknown and I4
if (SNet.IsMaster && (int)GameStateManager.CurrentStateName == 10 && RundownManager.ActiveExpedition != null)
{
return !RundownManager.ActiveExpedition.SkipLobby;
}
return false;
}
}
internal static bool CanCreateManualCheckpoint
{
get
{
if (ShouldShowCreateControl && (Object)(object)CheckpointManager.Current != (Object)null && (Object)(object)SNet.Capture != (Object)null && !SNet.Capture.IsCapturing && !SNet.Capture.IsRecalling && !SNet.MasterManagement.IsMigrating && !_suppressCheckpointSave && !_pendingDoorPreparationStarted && PendingRecall == null && _bridgeSnapshot == null && _playerResyncSnapshot == null && _activePlayerResync == null)
{
return PlayerResyncQueue.Count == 0;
}
return false;
}
}
internal static void CreateManualCheckpoint()
{
//IL_005c: Unknown result type (might be due to invalid IL or missing references)
if (!CanCreateManualCheckpoint)
{
return;
}
try
{
if (GetUnreadyConnectedPlayers().Count > 0)
{
ShowHostWarning("CHECKPOINT NOT CREATED\nWAIT FOR PLAYERS TO FINISH SYNCING");
return;
}
pActiveExpedition activeExpeditionData = RundownManager.GetActiveExpeditionData();
if (!TryGetIdentity(activeExpeditionData, RundownManager.ActiveExpedition, out CheckpointIdentity _, out string reason))
{
throw new InvalidOperationException(reason);
}
PlayerAgent localPlayerAgent = PlayerManager.GetLocalPlayerAgent();
if ((Object)(object)localPlayerAgent == (Object)null)
{
throw new InvalidOperationException("local player agent is unavailable");
}
CheckpointManager.StoreCheckpoint(((Agent)localPlayerAgent).EyePosition);
SNet.Capture.CaptureGameState((eBufferType)3);
Log.Info("Manually created checkpoint for " + GetExpeditionKey(activeExpeditionData) + ".");
}
catch (Exception value)
{
Log.Error($"Manual checkpoint creation failed: {value}");
ShowHostWarning("CHECKPOINT CREATION FAILED\nSEE HOST LOG");
}
}
internal static void OnSelectingExpedition(ref pActiveExpedition package, ExpeditionInTierData expedition)
{
if (_applyingPackage || !SNet.IsMaster)
{
return;
}
ResetSelection();
if (expedition == null || expedition.SkipLobby)
{
if (expedition != null)
{
Log.Info("Persistent checkpoints disabled for " + GetExpeditionKey(package) + ": expedition skips the lobby.");
}
return;
}
string text = (ActiveExpeditionKey = GetExpeditionKey(package));
if (!TryGetIdentity(package, expedition, out CheckpointIdentity identity, out string reason))
{
LastSelectionError = reason;
Log.Warning("Checkpoint identity for " + text + " is unavailable: " + reason);
return;
}
_activeIdentity = identity;
_freshPackage = ClonePackage(package);
_hasFreshPackage = true;
SnapshotLoadResult snapshotLoadResult = CheckpointStore.TryLoad(identity);
if (!snapshotLoadResult.Success)
{
LastSelectionError = snapshotLoadResult.Error;
if (snapshotLoadResult.Error != null)
{
Log.Warning("Checkpoint for " + text + " is unavailable: " + snapshotLoadResult.Error);
}
return;
}
CheckpointSnapshot snapshot = snapshotLoadResult.Snapshot;
if (!ValidateBeforeGeneration(snapshot, package, expedition, out string reason2))
{
LastSelectionError = reason2;
Log.Warning("Checkpoint for " + text + " is incompatible: " + reason2);
return;
}
ActiveSnapshot = snapshot;
ResumeSelected = true;
ApplySeeds(ref package, snapshot);
Log.Info($"Selected persistent checkpoint for {text}; session seed {snapshot.SessionSeed}.");
}
internal static void ToggleResume()
{
if (SNet.IsMaster && !SkipUnsupportedActiveExpedition() && !(ActiveSnapshot == null) && _hasFreshPackage)
{
bool flag = !ResumeSelected;
pActiveExpedition package = (pActiveExpedition)(flag ? ((object)RundownManager.GetActiveExpeditionData()) : ((object)_freshPackage));
if (flag)
{
ApplySeeds(ref package, ActiveSnapshot);
}
if (ApplyActivePackage(package))
{
ResumeSelected = flag;
Log.Info($"Resume checkpoint for {ActiveExpeditionKey}: {(flag ? "enabled" : "disabled")}.");
}
}
}
internal static void OnStartLevelResult(bool started)
{
if (started && SNet.IsMaster && !SkipUnsupportedActiveExpedition() && !string.IsNullOrEmpty(ActiveExpeditionKey) && _activeIdentity.HasValue)
{
if (ResumeSelected && ActiveSnapshot != null)
{
ResetPendingRecallDelay();
PendingRecall = ActiveSnapshot;
Log.Info("Expedition launch will recall checkpoint for " + ActiveExpeditionKey + ".");
}
else
{
CheckpointStore.EnqueueDelete(_activeIdentity.Value);
ActiveSnapshot = null;
Log.Info("Fresh expedition selected; deleted the old checkpoint for " + ActiveExpeditionKey + ".");
}
}
}
internal static void CaptureCheckpoint()
{
//IL_017f: Unknown result type (might be due to invalid IL or missing references)
//IL_0189: Expected I4, but got Unknown
//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
//IL_01d4: Unknown result type (might be due to invalid IL or missing references)
if (_suppressCheckpointSave || _pendingDoorPreparationStarted || !SNet.IsMaster)
{
return;
}
try
{
if (SkipUnsupportedActiveExpedition())
{
return;
}
pActiveExpedition activeExpeditionData = RundownManager.GetActiveExpeditionData();
string expeditionKey = GetExpeditionKey(activeExpeditionData);
if (!TryGetIdentity(activeExpeditionData, RundownManager.ActiveExpedition, out CheckpointIdentity identity, out string reason))
{
throw new InvalidOperationException(reason);
}
SNet_CaptureBuffer val = ((Il2CppArrayBase<SNet_CaptureBuffer>)(object)SNet.Capture.m_buffers)[3];
if (val == null || !val.isValid)
{
return;
}
List<List<byte[]>> list = new List<List<byte[]>>(SNet_CaptureBuffer.PassCount);
for (int i = 0; i < SNet_CaptureBuffer.PassCount; i++)
{
List<Il2CppStructArray<byte>> val2 = ((Il2CppArrayBase<List<Il2CppStructArray<byte>>>)(object)val.m_passes)[i];
List<byte[]> list2 = new List<byte[]>(val2.Count);
for (int j = 0; j < val2.Count; j++)
{
list2.Add(CopyBytes(val2[j]));
}
list.Add(list2);
}
List<CapturedPlayer> list3 = new List<CapturedPlayer>();
int num = _requiredSlotCapacity;
Enumerator<SNet_Player> enumerator = SNet.Slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if (!((Object)(object)current == (Object)null))
{
list3.Add(CapturePlayerState(current));
num = Math.Max(num, current.CharacterIndex + 1);
}
}
CheckpointSnapshot obj = new CheckpointSnapshot
{
BuildRevision = CellBuildData.GetRevision(),
ExpeditionKey = expeditionKey,
RundownKey = activeExpeditionData.rundownKey.data,
RundownName = identity.RundownName,
ExpeditionName = identity.ExpeditionName,
Tier = (int)activeExpeditionData.tier,
ExpeditionIndex = activeExpeditionData.expeditionIndex,
HostSeed = activeExpeditionData.hostIDSeed,
SessionSeed = activeExpeditionData.sessionSeed,
LevelChecksum = val.data.levelChecksum,
ProgressionTime = val.data.progressionTime,
RecallCount = val.data.recallCount,
SavedAtUtcTicks = DateTime.UtcNow.Ticks,
RequiredSlotCapacity = num,
LobbyExpansionPresent = LobbyCompatibility.IsPresent,
LobbyExpansionVersion = LobbyCompatibility.Version,
Players = list3,
Passes = list
};
ActiveExpeditionKey = expeditionKey;
_activeIdentity = identity;
ActiveSnapshot = obj;
ResumeSelected = true;
CheckpointStore.EnqueueSave(obj);
}
catch (Exception exception)
{
Log.Error(exception);
}
}
internal static bool TryRecallPending()
{
//IL_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Invalid comparison between Unknown and I4
CheckpointSnapshot pendingRecall = PendingRecall;
if (pendingRecall == null || !SNet.IsMaster)
{
ResetPendingRecallDelay();
return false;
}
if ((int)GameStateManager.CurrentStateName != 10)
{
if (_pendingDoorPreparationStarted)
{
PendingRecall = null;
ResetPendingRecallDelay();
Log.Warning("Cancelled pending checkpoint recall after leaving InLevel during door preparation.");
}
return false;
}
if (SkipUnsupportedActiveExpedition())
{
return false;
}
if (SNet.Capture.IsCapturing || SNet.Capture.IsRecalling || (Object)(object)CheckpointManager.Current == (Object)null)
{
return false;
}
if (!_pendingRecallDelayStarted)
{
if (!GameStateManager.IsEveryoneInState((eGameStateName)10, true, true))
{
return false;
}
_pendingRecallDelayStarted = true;
_pendingRecallNotBefore = Time.realtimeSinceStartup + 3f;
ShowHostWarning("TEMPORAL INSTABILITY DETECTED\nRESTORING TIMELINE...\nEL PSY KONGROO");
Log.Info($"All players reached InLevel; persistent checkpoint recall will begin in {3f:F1} seconds.");
return false;
}
if (Time.realtimeSinceStartup < _pendingRecallNotBefore)
{
return false;
}
if (!_pendingDoorPreparationStarted)
{
try
{
ValidateGeneratedLevel(pendingRecall);
_pendingDoorPreparationStarted = true;
int num = DestroyedDoorPreparation.Prepare(pendingRecall);
_pendingDoorPreparationNotBefore = Time.realtimeSinceStartup + ((num > 0) ? 1f : 0f);
Log.Info($"Prepared {num} destroyed door(s) before persistent checkpoint recall; callback settling delay: {((num > 0) ? 1f : 0f):F1} seconds.");
if (num > 0)
{
return false;
}
}
catch (Exception value)
{
PendingRecall = null;
ResetPendingRecallDelay();
Log.Error($"Persistent checkpoint door preparation failed: {value}");
return false;
}
}
if (Time.realtimeSinceStartup < _pendingDoorPreparationNotBefore)
{
return false;
}
PendingRecall = null;
ResetPendingRecallDelay();
RestoreAndRecall(pendingRecall);
return true;
}
internal static void OnPlayerEvent(SNet_Player player, SNet_PlayerEvent playerEvent)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Invalid comparison between Unknown and I4
if ((int)playerEvent == 3 && !(_activePlayerResync == null) && !((Object)(object)player == (Object)null) && player.Lookup == _activePlayerResync.Lookup)
{
_activePlayerResyncSawNativeSync = true;
Log.Info($"GTFO reported PlayerIsSynced for checkpoint hard-sync target {player.NickName} ({player.Lookup}); {GetPlayerSyncSummary(player)}.");
}
}
internal static void UpdatePlayerResync()
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Invalid comparison between Unknown and I4
//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
//IL_00ef: Invalid comparison between Unknown and I4
if (!SNet.IsMaster || (int)GameStateManager.CurrentStateName != 10)
{
ClearPlayerResyncs();
}
else
{
if ((_activePlayerResync == null && _playerResyncSnapshot == null && PlayerResyncQueue.Count == 0) || SkipUnsupportedActiveExpedition())
{
return;
}
if (_activePlayerResync != null)
{
PlayerResync activePlayerResync = _activePlayerResync;
SNet_Player val = FindSlottedPlayer(activePlayerResync.Lookup);
if ((Object)(object)val == (Object)null || !val.IsInSessionHub)
{
Log.Warning($"Skipping checkpoint hard-sync for {activePlayerResync.DisplayName} ({activePlayerResync.Lookup}): player disconnected.");
FinishActivePlayerResync();
}
else if (IsPlayerPlayingWithAgent(val) && (_activePlayerResyncSawNativeSync || (int)val.SyncProcessState == 5))
{
Log.Info($"Checkpoint hard-sync completed for {val.NickName} ({val.Lookup}); {GetPlayerSyncSummary(val)}.");
FinishActivePlayerResync();
}
else if (!_activePlayerResyncTimedOut && Time.realtimeSinceStartup >= _activePlayerResyncDeadline)
{
if (IsPlayerPlayingWithAgent(val))
{
Log.Warning($"Checkpoint hard-sync completion signals were stale or missed for {val.NickName} ({val.Lookup}); accepting Playing with a valid agent after {20f:F0} seconds. {GetPlayerSyncSummary(val)}.");
FinishActivePlayerResync();
}
else
{
Log.Error($"Checkpoint hard-sync timed out for {val.NickName} ({val.Lookup}). Remaining queued players: {PlayerResyncQueue.Count}. {GetPlayerSyncSummary(val)}. " + "The canonical checkpoint will not be created; that player should leave and rejoin.");
ShowHostWarning("CHECKPOINT SYNC FAILED: " + val.NickName + "\nLEAVE AND REJOIN");
_activePlayerResyncTimedOut = true;
}
return;
}
}
if (_activePlayerResync != null || Time.realtimeSinceStartup < _nextPlayerResyncTime)
{
return;
}
while (PlayerResyncQueue.Count > 0)
{
PlayerResync playerResync = PlayerResyncQueue.Dequeue();
SNet_Player val2 = FindSlottedPlayer(playerResync.Lookup);
if ((Object)(object)val2 == (Object)null || !val2.IsInSessionHub)
{
Log.Warning($"Skipping checkpoint hard-sync for {playerResync.DisplayName} ({playerResync.Lookup}): player disconnected.");
continue;
}
try
{
_activePlayerResync = playerResync;
_activePlayerResyncDeadline = Time.realtimeSinceStartup + 20f;
_activePlayerResyncSawNativeSync = false;
_activePlayerResyncTimedOut = false;
StartPlayerHardSync(val2);
Log.Info($"Started checkpoint hard-sync for {val2.NickName} ({val2.Lookup}); {GetPlayerSyncSummary(val2)}.");
return;
}
catch (Exception value)
{
Log.Error($"Failed to start checkpoint hard-sync for {val2.NickName} ({val2.Lookup}): {value}");
ShowHostWarning("CHECKPOINT SYNC FAILED: " + val2.NickName + "\nLEAVE AND REJOIN");
ClearPlayerResyncs();
return;
}
}
if (_playerResyncSnapshot != null)
{
CheckpointSnapshot? playerResyncSnapshot = _playerResyncSnapshot;
ClearPlayerResyncs();
BeginBridgeRecall(playerResyncSnapshot);
}
}
}
internal static void UpdateBridgeRecall()
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_001e: Invalid comparison between Unknown and I4
CheckpointSnapshot bridgeSnapshot = _bridgeSnapshot;
if (bridgeSnapshot == null)
{
return;
}
if (!SNet.IsMaster || (int)GameStateManager.CurrentStateName != 10)
{
ClearBridgeRecall();
}
else
{
if (SkipUnsupportedActiveExpedition() || Time.realtimeSinceStartup < _bridgeNotBefore || SNet.Capture.IsCapturing || SNet.Capture.IsRecalling)
{
return;
}
List<SNet_Player> unreadyConnectedPlayers = GetUnreadyConnectedPlayers();
if (unreadyConnectedPlayers.Count > 0 && Time.realtimeSinceStartup < _bridgeDeadline)
{
return;
}
if (unreadyConnectedPlayers.Count <= 0)
{
try
{
ApplySavedPlayerResources(bridgeSnapshot);
LogCurrentPlayerResources("Checkpoint bridge resources");
bool suppressCheckpointSave = _suppressCheckpointSave;
try
{
_suppressCheckpointSave = true;
SNet.Capture.CaptureGameState((eBufferType)3);
}
finally
{
_suppressCheckpointSave = suppressCheckpointSave;
}
SNet_CaptureBuffer val = ((Il2CppArrayBase<SNet_CaptureBuffer>)(object)SNet.Capture.m_buffers)[3];
if (val == null || !val.isValid)
{
throw new InvalidOperationException("canonical checkpoint capture was invalid");
}
CheckpointManager.ReloadCheckpoint();
if (!SNet.Sync.SessionCommand((eSessionCommandType)8, 0))
{
Log.Warning("GTFO rejected ForceStartPlaying before canonical checkpoint recall; continuing with recall.");
}
StartCanonicalAllPlayerRecall();
Log.Info("Captured and recalled canonical checkpoint for " + bridgeSnapshot.ExpeditionKey + " with GTFO's explicit all-player checkpoint protocol.");
return;
}
catch (Exception value)
{
Log.Error($"Canonical checkpoint bridge failed: {value}");
ShowHostWarning("CHECKPOINT SYNC FAILED\nLEAVE AND REJOIN");
return;
}
finally
{
ClearBridgeRecall();
}
}
string text = string.Join(", ", unreadyConnectedPlayers.Select((SNet_Player player) => player.NickName));
Log.Error($"Timed out waiting for checkpoint bridge players after {20f:F0} seconds: {text}. " + "The canonical checkpoint recall was cancelled to avoid splitting the session again.");
ShowHostWarning("CHECKPOINT SYNC INCOMPLETE: " + text + "\nLEAVE AND REJOIN");
ClearBridgeRecall();
}
}
internal static void ClearOnSuccess()
{
if (SNet.IsMaster && !SkipUnsupportedActiveExpedition() && !string.IsNullOrEmpty(ActiveExpeditionKey))
{
PendingRecall = null;
ResetPendingRecallDelay();
ActiveSnapshot = null;
ResumeSelected = false;
ClearPlayerResyncs();
ClearBridgeRecall();
if (_activeIdentity.HasValue)
{
CheckpointStore.EnqueueDelete(_activeIdentity.Value);
}
Log.Info("Expedition succeeded; deleted checkpoint for " + ActiveExpeditionKey + ".");
}
}
internal static string GetButtonText()
{
if (ActiveSnapshot == null)
{
return "<color=#FF8050>CHECKPOINT UNAVAILABLE</color> <size=55%>" + LastSelectionError + "</size>";
}
DateTime value = new DateTime(ActiveSnapshot.SavedAtUtcTicks, DateTimeKind.Utc).ToLocalTime();
return $"[{(ResumeSelected ? "X" : " ")}] RESUME CHECKPOINT <size=65%>{value:g}</size>";
}
internal static void ObserveCurrentSlots()
{
if (!SNet.HasLocalPlayer)
{
return;
}
try
{
Enumerator<SNet_Player> enumerator = SNet.Slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if ((Object)(object)current != (Object)null)
{
_requiredSlotCapacity = Math.Max(_requiredSlotCapacity, current.CharacterIndex + 1);
}
}
}
catch
{
}
}
private static void RestoreAndRecall(CheckpointSnapshot snapshot)
{
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: 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)
//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
SNet_CaptureBuffer val = null;
try
{
ValidateGeneratedLevel(snapshot);
List<PlayerResync> players = CaptureConnectedHumans(snapshot);
_suppressCheckpointSave = true;
SNet.Capture.CaptureGameState((eBufferType)3);
val = ((Il2CppArrayBase<SNet_CaptureBuffer>)(object)SNet.Capture.m_buffers)[3];
ushort bufferID = val.data.bufferID;
for (int i = 0; i < snapshot.Passes.Count; i++)
{
List<Il2CppStructArray<byte>> val2 = ((Il2CppArrayBase<List<Il2CppStructArray<byte>>>)(object)val.m_passes)[i];
val2.Clear();
foreach (byte[] item in snapshot.Passes[i])
{
val2.Add(new Il2CppStructArray<byte>(item));
}
}
pBufferData data = val.data;
data.sendingPlayerLookup = SNet.LocalPlayer.Lookup;
data.levelChecksum = snapshot.LevelChecksum;
data.bufferID = bufferID;
data.progressionTime = snapshot.ProgressionTime;
data.recallCount = snapshot.RecallCount;
val.data = data;
val.isValid = true;
CheckpointManager.ReloadCheckpoint();
SNet.Capture.InitCheckpointRecall();
SNet.Capture.RecallGameState((eBufferType)3);
if (Math.Abs(Clock.ExpeditionProgressionTime - snapshot.ProgressionTime) > 0.01f)
{
throw new InvalidOperationException($"Checkpoint recall returned without restoring progression time {snapshot.ProgressionTime:F2} (current {Clock.ExpeditionProgressionTime:F2}).");
}
Log.Info($"Recalled persistent checkpoint for {snapshot.ExpeditionKey} at {Clock.ExpeditionProgressionTime:F2} seconds.");
QueuePlayerResyncs(players, snapshot);
}
catch (Exception value)
{
if (val != null)
{
val.Clear();
}
Log.Error($"Persistent checkpoint recall failed: {value}");
}
finally
{
_suppressCheckpointSave = false;
}
}
private static void ValidateGeneratedLevel(CheckpointSnapshot snapshot)
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
if (!TryGetIdentity(RundownManager.GetActiveExpeditionData(), RundownManager.ActiveExpedition, out CheckpointIdentity identity, out string reason))
{
throw new InvalidOperationException(reason);
}
if (snapshot.Identity != identity)
{
throw new InvalidOperationException("Current mission names do not match the saved checkpoint.");
}
ulong num = SNet.LocalPlayer.Session.levelChecksum;
if (num != snapshot.LevelChecksum)
{
throw new InvalidOperationException($"Generated level checksum {num} does not match checkpoint {snapshot.LevelChecksum}.");
}
if (!LobbyCompatibility.CanRepresent(snapshot, out string reason2))
{
throw new InvalidOperationException(reason2);
}
if (snapshot.Passes.Count != SNet_CaptureBuffer.PassCount)
{
throw new InvalidOperationException($"Checkpoint has {snapshot.Passes.Count} capture passes, game expects {SNet_CaptureBuffer.PassCount}.");
}
}
private static List<PlayerResync> CaptureConnectedHumans(CheckpointSnapshot snapshot)
{
HashSet<ulong> hashSet = (from player in snapshot.Players
where !player.IsBot
select player.Lookup).ToHashSet();
List<PlayerResync> list = new List<PlayerResync>();
Enumerator<SNet_Player> enumerator = SNet.Slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if (!((Object)(object)current == (Object)null) && !current.IsMaster && !current.IsBot && current.IsInSessionHub)
{
list.Add(new PlayerResync(current.Lookup, current.NickName));
Log.Info($"Checkpoint player mapping: {current.NickName} ({current.Lookup}), character {current.CharacterIndex}, saved state {(hashSet.Contains(current.Lookup) ? "found" : "not found; vanilla defaults will apply")}, fresh agent key {GetPlayerAgentKey(current)}.");
}
}
return list;
}
private static void QueuePlayerResyncs(IEnumerable<PlayerResync> players, CheckpointSnapshot snapshot)
{
ClearPlayerResyncs();
_playerResyncSnapshot = snapshot;
foreach (PlayerResync player in players)
{
PlayerResyncQueue.Enqueue(player);
}
if (PlayerResyncQueue.Count == 0)
{
CheckpointSnapshot? playerResyncSnapshot = _playerResyncSnapshot;
ClearPlayerResyncs();
BeginBridgeRecall(playerResyncSnapshot);
return;
}
_nextPlayerResyncTime = Time.realtimeSinceStartup + 1.5f;
Log.Info($"Queued {PlayerResyncQueue.Count} connected human player(s) for fresh DropIn hard-sync; the first sync will begin in {1.5f:F1} seconds.");
}
private static void StartPlayerHardSync(SNet_Player player)
{
if ((object)_startSinglePlayerRecall == null)
{
_startSinglePlayerRecall = typeof(SNet_SyncManager).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault(delegate(MethodInfo method)
{
ParameterInfo[] parameters = method.GetParameters();
return method.Name == "StartRecall" && parameters.Length == 1 && parameters[0].ParameterType == typeof(SNet_Player);
}) ?? throw new MissingMethodException(typeof(SNet_SyncManager).FullName, "StartRecall(SNet_Player)");
}
Log.Info($"DropIn capture resources for {player.NickName} ({player.Lookup}): {GetPlayerResourceSummary(player)}.");
player.SyncProcessState = (eSyncProcessState)4;
try
{
_startSinglePlayerRecall.Invoke(SNet.Sync, new object[1] { player });
}
catch
{
player.SyncProcessState = (eSyncProcessState)5;
throw;
}
}
private static void StartCanonicalAllPlayerRecall()
{
SNet.Capture.InitCheckpointRecall();
int num = 0;
Enumerator<SNet_Player> enumerator = SNet.Slots.PlayersSynchedWithGame.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if (!((Object)(object)current == (Object)null) && !current.IsMaster)
{
SNet.Sync.SendPlayerData_Session_SyncAllTo(current);
num++;
}
}
SNet.Capture.SendBuffer((eBufferType)3, (SNet_Player)null, false);
SNet.Capture.SendBufferCommand((eBufferType)3, (eBufferOperationType)2, (SNet_Player)null, (ushort)0);
SNet.Capture.RecallGameState((eBufferType)3);
Log.Info($"Explicit canonical checkpoint recall sent to {num} synced remote player(s).");
}
private static void BeginBridgeRecall(CheckpointSnapshot snapshot)
{
_bridgeSnapshot = snapshot;
_bridgeNotBefore = Time.realtimeSinceStartup + 2f;
_bridgeDeadline = Time.realtimeSinceStartup + 20f;
Log.Info($"All checkpoint hard-syncs completed; canonical all-player recall will begin after at least {2f:F1} seconds once current players are capture-ready.");
}
private static List<SNet_Player> GetUnreadyConnectedPlayers()
{
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0047: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Invalid comparison between Unknown and I4
List<SNet_Player> list = new List<SNet_Player>();
Enumerator<SNet_Player> enumerator = SNet.Slots.SlottedPlayers.GetEnumerator();
PlayerBackpack val = default(PlayerBackpack);
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if ((Object)(object)current == (Object)null || (!current.IsBot && !current.IsInSessionHub))
{
continue;
}
try
{
if ((!current.IsBot && (int)current.Session.mode != 8) || current.IsOutOfSync || !current.HasPlayerAgent || !PlayerBackpackManager.TryGetBackpack(current, ref val))
{
list.Add(current);
}
}
catch
{
list.Add(current);
}
}
return list;
}
private static void ApplySavedPlayerResources(CheckpointSnapshot snapshot)
{
//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
PlayerBackpack val2 = default(PlayerBackpack);
foreach (CapturedPlayer player in snapshot.Players)
{
if (!player.HasResources)
{
continue;
}
SNet_Player val = FindSlottedPlayer(player.Lookup);
if ((Object)(object)val == (Object)null || !val.HasPlayerAgent || !PlayerBackpackManager.TryGetBackpack(val, ref val2))
{
Log.Warning($"Could not apply saved resources for player {player.Lookup}: current agent or backpack unavailable.");
continue;
}
try
{
PlayerAgent val3 = ((Il2CppObjectBase)val.PlayerAgent).TryCast<PlayerAgent>() ?? throw new InvalidOperationException("player agent could not be cast to PlayerAgent");
((Dam_SyncedDamageBase)val3.Damage).Health = Mathf.Clamp01(player.HealthRel) * ((Dam_SyncedDamageBase)val3.Damage).HealthMax;
val3.Damage.Infection = Mathf.Clamp01(player.Infection);
pAmmoStorageData val4 = CreateAmmoDataForCapture(val, val2, player);
val2.AmmoStorage.SetStorageData(ref val4);
Log.Info($"Applied saved resources for {val.NickName} ({val.Lookup}): health={player.HealthRel:P0}, infection={player.Infection:P0}, {GetPlayerResourceSummary(val)}.");
}
catch (Exception ex)
{
Log.Warning($"Could not apply saved resources for {val.NickName} ({val.Lookup}): {ex.Message}");
}
}
}
private static pAmmoStorageData CreateAmmoDataForCapture(SNet_Player player, PlayerBackpack backpack, CapturedPlayer saved)
{
//IL_0069: Unknown result type (might be due to invalid IL or missing references)
//IL_0122: Unknown result type (might be due to invalid IL or missing references)
float num = saved.StandardAmmo;
float num2 = saved.SpecialAmmo;
if (backpack.IsLocal || (SNet.IsMaster && player.IsBot))
{
num -= backpack.AmmoStorage.GetClipAmmoFromSlot((InventorySlot)1) * backpack.AmmoStorage.StandardAmmo.CostOfBullet;
num2 -= backpack.AmmoStorage.GetClipAmmoFromSlot((InventorySlot)2) * backpack.AmmoStorage.SpecialAmmo.CostOfBullet;
}
pAmmoStorageData result = new pAmmoStorageData
{
PlayerLookup = player.Lookup,
hasSetAmmo = true
};
((DevFloat32)(ref result.standardAmmo)).Set(Mathf.Max(0f, num), 500f);
((DevFloat32)(ref result.specialAmmo)).Set(Mathf.Max(0f, num2), 500f);
((DevFloat32)(ref result.classAmmo)).Set(Mathf.Max(0f, saved.ClassAmmo), 500f);
((DevFloat32)(ref result.resourcePackAmmo)).Set(Mathf.Max(0f, saved.ResourcePackAmmo), 500f);
((DevFloat32)(ref result.consumableAmmo)).Set(Mathf.Max(0f, saved.ConsumableAmmo), 500f);
return result;
}
private static CapturedPlayer CapturePlayerState(SNet_Player player)
{
//IL_0074: Unknown result type (might be due to invalid IL or missing references)
//IL_0079: Unknown result type (might be due to invalid IL or missing references)
try
{
PlayerBackpack val = default(PlayerBackpack);
if (!player.HasPlayerAgent || !PlayerBackpackManager.TryGetBackpack(player, ref val))
{
return new CapturedPlayer(player.Lookup, player.CharacterIndex, player.IsBot);
}
PlayerAgent val2 = ((Il2CppObjectBase)player.PlayerAgent).TryCast<PlayerAgent>() ?? throw new InvalidOperationException("player agent could not be cast to PlayerAgent");
pAmmoStorageData storageDataForCapture = val.AmmoStorage.GetStorageDataForCapture();
return new CapturedPlayer(player.Lookup, player.CharacterIndex, player.IsBot, HasResources: true, ((Dam_SyncedDamageBase)val2.Damage).GetHealthRel(), val2.Damage.Infection, ((DevFloat32)(ref storageDataForCapture.standardAmmo)).Get(500f), ((DevFloat32)(ref storageDataForCapture.specialAmmo)).Get(500f), ((DevFloat32)(ref storageDataForCapture.classAmmo)).Get(500f), ((DevFloat32)(ref storageDataForCapture.resourcePackAmmo)).Get(500f), ((DevFloat32)(ref storageDataForCapture.consumableAmmo)).Get(500f));
}
catch (Exception ex)
{
Log.Warning($"Could not capture resources for {player.NickName} ({player.Lookup}): {ex.Message}");
return new CapturedPlayer(player.Lookup, player.CharacterIndex, player.IsBot);
}
}
private static SNet_Player? FindSlottedPlayer(ulong lookup)
{
Enumerator<SNet_Player> enumerator = SNet.Slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if ((Object)(object)current != (Object)null && current.Lookup == lookup)
{
return current;
}
}
return null;
}
private static string GetPlayerAgentKey(SNet_Player player)
{
try
{
if (player.HasPlayerAgent)
{
return player.PlayerAgent.GetReplicator().Key.ToString();
}
}
catch
{
}
return "none";
}
private static string GetPlayerSyncSummary(SNet_Player player)
{
//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_0036: Unknown result type (might be due to invalid IL or missing references)
try
{
return $"mode={player.Session.mode}, processState={player.SyncProcessState}, hasAgent={player.HasPlayerAgent}, agent key={GetPlayerAgentKey(player)}, native PlayerIsSynced={_activePlayerResyncSawNativeSync}";
}
catch (Exception ex)
{
return $"sync state unavailable ({ex.Message}), native PlayerIsSynced={_activePlayerResyncSawNativeSync}";
}
}
private static bool IsPlayerPlayingWithAgent(SNet_Player player)
{
//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_000c: Invalid comparison between Unknown and I4
try
{
return (int)player.Session.mode == 8 && player.HasPlayerAgent;
}
catch
{
return false;
}
}
private static void LogCurrentPlayerResources(string label)
{
Enumerator<SNet_Player> enumerator = SNet.Slots.SlottedPlayers.GetEnumerator();
while (enumerator.MoveNext())
{
SNet_Player current = enumerator.Current;
if ((Object)(object)current != (Object)null)
{
Log.Info($"{label} for {current.NickName} ({current.Lookup}): {GetPlayerResourceSummary(current)}.");
}
}
}
private static string GetPlayerResourceSummary(SNet_Player player)
{
//IL_001b: 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)
try
{
PlayerBackpack val = default(PlayerBackpack);
if (!PlayerBackpackManager.TryGetBackpack(player, ref val))
{
return "backpack unavailable";
}
pAmmoStorageData storageDataForCapture = val.AmmoStorage.GetStorageDataForCapture();
return $"standard={((DevFloat32)(ref storageDataForCapture.standardAmmo)).Get(500f):F1}, special={((DevFloat32)(ref storageDataForCapture.specialAmmo)).Get(500f):F1}, tool={((DevFloat32)(ref storageDataForCapture.classAmmo)).Get(500f):F1}, resourcePack={((DevFloat32)(ref storageDataForCapture.resourcePackAmmo)).Get(500f):F1}, consumable={((DevFloat32)(ref storageDataForCapture.consumableAmmo)).Get(500f):F1}";
}
catch (Exception ex)
{
return "unavailable (" + ex.Message + ")";
}
}
private static void ShowHostWarning(string message)
{
try
{
PlayerGuiLayer playerLayer = GuiManager.PlayerLayer;
if (playerLayer == null || (Object)(object)playerLayer.m_wardenIntel == (Object)null)
{
Log.Warning("Could not display checkpoint warning: the player Warden HUD is unavailable.");
}
else
{
playerLayer.ShowWardenIntel(message, 0f, 8f);
}
}
catch (Exception ex)
{
Log.Warning("Could not display checkpoint sync warning: " + ex.Message);
}
}
private static void ClearBridgeRecall()
{
_bridgeSnapshot = null;
_bridgeNotBefore = 0f;
_bridgeDeadline = 0f;
}
private static void ClearPlayerResyncs()
{
PlayerResyncQueue.Clear();
_activePlayerResync = null;
_playerResyncSnapshot = null;
_activePlayerResyncDeadline = 0f;
_nextPlayerResyncTime = 0f;
_activePlayerResyncSawNativeSync = false;
_activePlayerResyncTimedOut = false;
}
private static void FinishActivePlayerResync()
{
_activePlayerResync = null;
_activePlayerResyncDeadline = 0f;
_activePlayerResyncSawNativeSync = false;
_activePlayerResyncTimedOut = false;
_nextPlayerResyncTime = Time.realtimeSinceStartup + 0.25f;
}
private static bool ValidateBeforeGeneration(CheckpointSnapshot snapshot, pActiveExpedition package, ExpeditionInTierData expedition, out string reason)
{
if (snapshot.BuildRevision != CellBuildData.GetRevision())
{
reason = $"saved build {snapshot.BuildRevision}, current build {CellBuildData.GetRevision()}";
return false;
}
if (snapshot.HostSeed != package.hostIDSeed)
{
reason = "checkpoint belongs to a different host";
return false;
}
if (!LobbyCompatibility.CanRepresent(snapshot, out reason))
{
return false;
}
int num = default(int);
int num2 = default(int);
expedition.GetSeeds(ref num, ref num2);
try
{
expedition.SetSeeds(snapshot.HostSeed, snapshot.SessionSeed);
ulong num3 = expedition.GetChecksum();
if (num3 != snapshot.LevelChecksum)
{
reason = $"saved level checksum {snapshot.LevelChecksum}, current data produces {num3}";
return false;
}
}
finally
{
expedition.SetSeeds(num, num2);
}
reason = string.Empty;
return true;
}
private static void ApplySeeds(ref pActiveExpedition package, CheckpointSnapshot snapshot)
{
package.hostIDSeed = snapshot.HostSeed;
package.sessionSeed = snapshot.SessionSeed;
}
private static pActiveExpedition ClonePackage(pActiveExpedition source)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: 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_001d: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: 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_0035: 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)
//IL_004d: 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_0066: Expected O, but got Unknown
return new pActiveExpedition
{
PlayerData = source.PlayerData,
rundownType = source.rundownType,
rundownKey = source.rundownKey,
tier = source.tier,
expeditionIndex = source.expeditionIndex,
hostIDSeed = source.hostIDSeed,
sessionSeed = source.sessionSeed,
sessionGUID = source.sessionGUID
};
}
private static bool ApplyActivePackage(pActiveExpedition package)
{
try
{
if ((object)_setActiveExpedition == null)
{
_setActiveExpedition = (from method in (from method in typeof(RundownManager).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
where method.Name == "SetActiveExpedition"
select method).Where(delegate(MethodInfo method)
{
ParameterInfo[] parameters2 = method.GetParameters();
int num = parameters2.Length;
bool flag = (uint)(num - 2) <= 1u;
return flag && parameters2[0].ParameterType == typeof(pActiveExpedition) && parameters2[1].ParameterType == typeof(ExpeditionInTierData) && (parameters2.Length == 2 || parameters2[2].ParameterType == typeof(bool));
})
orderby method.GetParameters().Length descending
select method).FirstOrDefault() ?? throw new MissingMethodException(typeof(RundownManager).FullName, "SetActiveExpedition(pActiveExpedition, ExpeditionInTierData[, bool])");
}
object[] parameters = ((_setActiveExpedition.GetParameters().Length != 3) ? new object[2]
{
package,
RundownManager.ActiveExpedition
} : new object[3]
{
package,
RundownManager.ActiveExpedition,
true
});
_applyingPackage = true;
_setActiveExpedition.Invoke(RundownManager.Current, parameters);
return true;
}
catch (Exception value)
{
Log.Error($"Failed to switch expedition seed package: {value}");
return false;
}
finally
{
_applyingPackage = false;
}
}
private static string GetExpeditionKey(pActiveExpedition package)
{
//IL_002a: Unknown result type (might be due to invalid IL or missing references)
return $"{package.rundownKey.data}_{package.tier}_{package.expeditionIndex}";
}
private static bool TryGetIdentity(pActiveExpedition package, ExpeditionInTierData expedition, out CheckpointIdentity identity, out string reason)
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: Invalid comparison between Unknown and I4
//IL_0073: Unknown result type (might be due to invalid IL or missing references)
identity = default(CheckpointIdentity);
try
{
RundownDataBlock block = default(RundownDataBlock);
if ((int)package.rundownType == 5)
{
string data = package.rundownKey.data;
if (data == null || !data.StartsWith("Local_", StringComparison.Ordinal) || !uint.TryParse(data.AsSpan(6), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
{
throw new InvalidOperationException("local rundown key is invalid");
}
block = GameDataBlockBase<RundownDataBlock>.GetBlock(result);
if (block == null || RundownManager.GetLocalRundownKey(block) != data)
{
throw new InvalidOperationException("local rundown data is unavailable");
}
}
else if (!RundownManager.TryGetRundownData(package.rundownType, ref block) || block == null)
{
throw new InvalidOperationException("rundown data is unavailable");
}
LocalizedText title = block.StorytellingData.Title;
string rundownName = ((!string.IsNullOrWhiteSpace(title.UntranslatedText)) ? ("text:" + title.UntranslatedText) : ((title.Id != 0) ? ("loc:" + title.Id.ToString(CultureInfo.InvariantCulture)) : ((!string.IsNullOrWhiteSpace(((GameDataBlockBase<RundownDataBlock>)(object)block).name)) ? ("block:" + ((GameDataBlockBase<RundownDataBlock>)(object)block).name) : string.Empty)));
identity = new CheckpointIdentity(GetExpeditionKey(package), rundownName, expedition.Descriptive.PublicName);
if (!identity.IsValid)
{
throw new InvalidOperationException("rundown or expedition name is empty");
}
reason = string.Empty;
return true;
}
catch (Exception ex)
{
reason = ex.Message;
return false;
}
}
private static bool SkipUnsupportedActiveExpedition()
{
ExpeditionInTierData activeExpedition = RundownManager.ActiveExpedition;
if (activeExpedition != null && !activeExpedition.SkipLobby)
{
return false;
}
ResetSelection();
return true;
}
private static byte[] CopyBytes(Il2CppStructArray<byte> source)
{
byte[] array = new byte[((Il2CppArrayBase<byte>)(object)source).Length];
for (int i = 0; i < ((Il2CppArrayBase<byte>)(object)source).Length; i++)
{
array[i] = ((Il2CppArrayBase<byte>)(object)source)[i];
}
return array;
}
private static void ResetSelection()
{
ActiveSnapshot = null;
PendingRecall = null;
ResetPendingRecallDelay();
ActiveExpeditionKey = string.Empty;
_activeIdentity = null;
ResumeSelected = false;
LastSelectionError = null;
_hasFreshPackage = false;
_requiredSlotCapacity = 4;
ClearPlayerResyncs();
ClearBridgeRecall();
}
private static void ResetPendingRecallDelay()
{
_pendingRecallDelayStarted = false;
_pendingRecallNotBefore = 0f;
_pendingDoorPreparationStarted = false;
_pendingDoorPreparationNotBefore = 0f;
}
}
}
namespace GTFO.PersistentCheckpoint.Patches
{
[HarmonyPatch(typeof(RundownManager), "SetActiveExpedition")]
internal static class SetActiveExpeditionPatch
{
private static void Prefix(ref pActiveExpedition __0, ExpeditionInTierData __1)
{
ResumeCoordinator.OnSelectingExpedition(ref __0, __1);
}
}
[HarmonyPatch(typeof(SNet_Capture), "CaptureGameState")]
internal static class CaptureCheckpointPatch
{
private static void Postfix(eBufferType __0)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0002: Invalid comparison between Unknown and I4
if ((int)__0 == 3)
{
ResumeCoordinator.CaptureCheckpoint();
}
}
}
[HarmonyPatch(typeof(GS_Lobby), "TryStartLevelTrigger")]
internal static class StartLevelPatch
{
private static void Postfix(bool __result)
{
ResumeCoordinator.OnStartLevelResult(__result);
}
}
[HarmonyPatch(typeof(GS_ExpeditionSuccess), "Enter")]
internal static class ExpeditionSuccessPatch
{
private static void Prefix()
{
ResumeCoordinator.ClearOnSuccess();
}
}
[HarmonyPatch(typeof(SNet_GlobalManager), "OnPlayerEvent")]
internal static class PlayerSyncEventPatch
{
private static void Postfix(SNet_Player __0, SNet_PlayerEvent __1)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
ResumeCoordinator.OnPlayerEvent(__0, __1);
}
}
[HarmonyPatch(typeof(CM_PageLoadout), "Setup")]
[HarmonyAfter(new string[] { "GTFO.LobbyExpansion" })]
internal static class LoadoutUiPatch
{
private static void Postfix(CM_PageLoadout __instance)
{
PersistentCheckpointController.QueueLoadoutUi(__instance);
}
}
[HarmonyPatch(typeof(LG_WeakDoor), "OnSyncDoorStateChange")]
internal static class DestroyedDoorRecallPatch
{
private static void Postfix(LG_WeakDoor __instance, pDoorState __0, bool __1)
{
//IL_000a: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Invalid comparison between Unknown and I4
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
if (!SNet.IsMaster || !__1 || (int)__0.status != 11)
{
return;
}
try
{
if ((Object)(object)__instance.Gate != (Object)null)
{
__instance.Gate.HasBeenOpenedDuringPlay = true;
}
Component animComp = __instance.m_animComp;
LG_WeakDoor_Anim val = ((animComp != null) ? ((Il2CppObjectBase)animComp).TryCast<LG_WeakDoor_Anim>() : null);
if ((Object)(object)val != (Object)null)
{
val.OnDoorState(__0, false);
}
if ((Object)(object)__instance.Gate != (Object)null)
{
__instance.Gate.IsTraversable = true;
}
}
catch (Exception value)
{
Log.Error($"Failed to repair recalled destroyed-door navigation for {((Object)__instance).name}: {value}");
}
try
{
Component destructionComp = __instance.m_destructionComp;
LG_WeakDoor_Destruction val2 = ((destructionComp != null) ? ((Il2CppObjectBase)destructionComp).TryCast<LG_WeakDoor_Destruction>() : null);
if ((Object)(object)val2 == (Object)null || (Object)(object)val2.m_destructionController == (Object)null)
{
Log.Warning("Cannot repair destroyed-door geometry for " + ((Object)__instance).name + ": unsupported destruction component.");
return;
}
val2.EnableSkinnedDoor();
val2.m_destructionController.SnapToExploded();
val2.m_isDestroyed = true;
LG_DoorBladeCuller doorBladeCuller = __instance.m_doorBladeCuller;
if ((Object)(object)doorBladeCuller != (Object)null)
{
doorBladeCuller.SwitchToDestroyedRenderLists();
doorBladeCuller.ForceUpdate();
}
Log.Info($"Repaired recalled destroyed-door geometry for {((Object)__instance).name} on host; traversable: {(Object)(object)__instance.Gate != (Object)null && __instance.Gate.IsTraversable}.");
}
catch (Exception value2)
{
Log.Error($"Failed to repair recalled destroyed-door geometry for {((Object)__instance).name}: {value2}");
}
}
}
}
namespace GTFO.PersistentCheckpoint.Core
{
internal static class Log
{
private static ManualLogSource? _source;
internal static void Setup(ManualLogSource source)
{
_source = source;
}
internal static void Debug(string message)
{
ManualLogSource? source = _source;
if (source != null)
{
source.LogDebug((object)message);
}
}
internal static void Info(string message)
{
ManualLogSource? source = _source;
if (source != null)
{
source.LogInfo((object)message);
}
}
internal static void Warning(string message)
{
ManualLogSource? source = _source;
if (source != null)
{
source.LogWarning((object)message);
}
}
internal static void Error(string message)
{
ManualLogSource? source = _source;
if (source != null)
{
source.LogError((object)message);
}
}
internal static void Error(Exception exception)
{
ManualLogSource? source = _source;
if (source != null)
{
source.LogError((object)exception);
}
}
}
internal sealed class PersistentCheckpointController : MonoBehaviour
{
private static PersistentCheckpointController? _instance;
private CM_PageLoadout? _queuedPage;
private int _uiDelayFrames;
private CM_TimedButton? _resumeButton;
private CM_TimedButton? _createButton;
public PersistentCheckpointController(IntPtr pointer)
: base(pointer)
{
}
private void Awake()
{
if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this)
{
Object.Destroy((Object)(object)((Component)this).gameObject);
return;
}
_instance = this;
Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
}
private void Update()
{
UpdateQueuedUi();
ResumeCoordinator.ObserveCurrentSlots();
UpdateCheckpointButtons();
ResumeCoordinator.TryRecallPending();
ResumeCoordinator.UpdatePlayerResync();
ResumeCoordinator.UpdateBridgeRecall();
}
internal static void QueueLoadoutUi(CM_PageLoadout page)
{
if (!((Object)(object)_instance == (Object)null))
{
_instance._queuedPage = page;
_instance._uiDelayFrames = 2;
}
}
private void UpdateQueuedUi()
{
if ((Object)(object)_queuedPage == (Object)null || --_uiDelayFrames > 0)
{
return;
}
CM_PageLoadout queuedPage = _queuedPage;
_queuedPage = null;
try
{
_resumeButton = GetOrCreateButton(queuedPage, "PersistentCheckpointResumeButton", (eTimedButtonClickType)0, delegate
{
ResumeCoordinator.ToggleResume();
});
_createButton = GetOrCreateButton(queuedPage, "PersistentCheckpointCreateButton", (eTimedButtonClickType)3, delegate
{
ResumeCoordinator.CreateManualCheckpoint();
});
((CM_Item)_createButton).SetText("CREATE CHECKPOINT");
UpdateCheckpointButtons();
}
catch (Exception value)
{
Log.Error($"Failed to create checkpoint UI: {value}");
}
}
private static CM_TimedButton GetOrCreateButton(CM_PageLoadout page, string name, eTimedButtonClickType clickType, Action<int> onPress)
{
//IL_006f: Unknown result type (might be due to invalid IL or missing references)
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
CM_TimedButton val = ((IEnumerable<CM_TimedButton>)((Component)page).GetComponentsInChildren<CM_TimedButton>(true)).FirstOrDefault((Func<CM_TimedButton, bool>)((CM_TimedButton item) => ((Object)((Component)item).gameObject).name == name));
if ((Object)(object)val != (Object)null)
{
((CM_Item)val).OnBtnPressCallback = Action<int>.op_Implicit(onPress);
return val;
}
GameObject obj = Object.Instantiate<GameObject>(page.m_readyButtonPrefab, page.m_readyButtonAlign);
((Object)obj).name = name;
obj.transform.localPosition = new Vector3(430f, 100f, 0f);
obj.transform.localScale = new Vector3(0.75f, 0.75f, 1f);
CM_TimedButton component = obj.GetComponent<CM_TimedButton>();
((CM_Item)component).TextMeshRoot = ((Component)page).transform;
component.m_clickType = clickType;
((RectTransformComp)component).SetSize(new Vector2(520f, 80f));
((RectTransformComp)component).Setup();
((CM_Item)component).SetupCMItem();
ShiftButtonTextRight(component, 40f);
((CM_Item)component).OnBtnPressCallback = Action<int>.op_Implicit(onPress);
((RectTransformComp)component).SetVisible(false);
return component;
}
private static void ShiftButtonTextRight(CM_TimedButton button, float offset)
{
//IL_001b: 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_0030: Unknown result type (might be due to invalid IL or missing references)
foreach (TextMeshPro componentsInChild in ((Component)button).GetComponentsInChildren<TextMeshPro>(true))
{
Transform transform = componentsInChild.transform;
transform.localPosition += new Vector3(offset, 0f, 0f);
}
}
private void UpdateCheckpointButtons()
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Invalid comparison between Unknown and I4
if ((Object)(object)_resumeButton != (Object)null)
{
bool flag = SNet.IsMaster && (int)GameStateManager.CurrentStateName == 5 && ResumeCoordinator.ShouldShowControl;
if (((RectTransformComp)_resumeButton).IsVisible != flag)
{
((RectTransformComp)_resumeButton).SetVisible(flag);
}
if (flag)
{
((CM_Item)_resumeButton).SetButtonEnabled(ResumeCoordinator.HasResumeChoice);
string buttonText = ResumeCoordinator.GetButtonText();
if (((TMP_Text)_resumeButton.m_btnText).text != buttonText)
{
((CM_Item)_resumeButton).SetText(buttonText);
}
}
}
if ((Object)(object)_createButton != (Object)null)
{
bool shouldShowCreateControl = ResumeCoordinator.ShouldShowCreateControl;
if (((RectTransformComp)_createButton).IsVisible != shouldShowCreateControl)
{
((RectTransformComp)_createButton).SetVisible(shouldShowCreateControl);
}
if (shouldShowCreateControl)
{
((CM_Item)_createButton).SetButtonEnabled(ResumeCoordinator.CanCreateManualCheckpoint);
}
}
}
private void OnApplicationQuit()
{
CheckpointStore.Flush(TimeSpan.FromSeconds(3.0));
}
}
[BepInPlugin("com.Roughy.GTFO.PersistentCheckpoint", "GTFO Persistent Checkpoint", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class PersistentCheckpointPlugin : BasePlugin
{
public const string Guid = "com.Roughy.GTFO.PersistentCheckpoint";
public const string Name = "GTFO Persistent Checkpoint";
public const string Version = "1.0.0";
private Harmony? _harmony;
public override void Load()
{
//IL_001a: 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)
//IL_0025: Expected O, but got Unknown
//IL_0025: 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_0043: Expected O, but got Unknown
Log.Setup(((BasePlugin)this).Log);
CheckpointStore.Initialize();
ClassInjector.RegisterTypeInIl2Cpp<PersistentCheckpointController>();
GameObject val = new GameObject("GTFO_PersistentCheckpoint");
Object.DontDestroyOnLoad((Object)val);
((Object)val).hideFlags = (HideFlags)61;
val.AddComponent<PersistentCheckpointController>();
_harmony = new Harmony("com.Roughy.GTFO.PersistentCheckpoint");
_harmony.PatchAll();
Log.Info($"Loaded {"GTFO Persistent Checkpoint"} {"1.0.0"}. LobbyExpansion: {LobbyCompatibility.Description}");
}
public override bool Unload()
{
CheckpointStore.Flush(TimeSpan.FromSeconds(3.0));
Harmony? harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
return true;
}
}
}