Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of QuickRunReload v1.2.0
BepInEx/plugins/QuickRunReload/QuickRunReload.dll
Decompiled 2 weeks agousing System; using System.Collections; 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.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: AssemblyProduct("QuickRunReload")] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyCompany("qup0ix")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyTitle("QuickRunReload")] [assembly: AssemblyDescription("Pre-landing checkpoint and host-only wipe reload for Lethal Company.")] [assembly: AssemblyVersion("1.2.0.0")] namespace QuickRunReload; [BepInPlugin("qup0ix.QuickRunReload", "QuickRunReload", "1.2.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "qup0ix.QuickRunReload"; public const string PluginName = "QuickRunReload"; public const string PluginVersion = "1.2.0"; private Harmony _harmony; private ConfigEntry<KeyboardShortcut> _reloadHotkey; private ConfigEntry<bool> _requireFullWipe; private ChatCommandHandler _chatCommandHandler; private AutoRestartMonitor _autoRestartMonitor; private CheckpointManager _checkpoints; private ReloadController _reloadController; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0187: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; _reloadHotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("General", "ReloadHotkey", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[0]), "Host-only hotkey that restores the pre-landing checkpoint and reloads the same save."); _requireFullWipe = ((BaseUnityPlugin)this).Config.Bind<bool>("Safety", "RequireAllPlayersDead", true, "When true, reload requests are accepted only after the whole crew is dead."); ConfigEntry<bool> enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "EnableChatCommand", true, "Enables the host-only chat command that requests the same run reload as the hotkey."); ConfigEntry<string> command = ((BaseUnityPlugin)this).Config.Bind<string>("Chat", "ChatCommand", "/restart", "Exact chat message used to request a run reload. Comparison ignores case and surrounding whitespace."); ConfigEntry<bool> hideCommandMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("Chat", "HideCommandMessage", true, "When true, the matched command is consumed locally instead of being sent as a normal chat message."); ConfigEntry<bool> enabled2 = ((BaseUnityPlugin)this).Config.Bind<bool>("AutoRestart", "EnableAutoRestart", false, "Automatically reload the pre-landing checkpoint when all active players are dead."); ConfigEntry<float> delay = ((BaseUnityPlugin)this).Config.Bind<float>("AutoRestart", "AutoRestartDelay", 3f, "Seconds to wait after all active players are confirmed dead before automatically reloading the checkpoint."); _checkpoints = new CheckpointManager(); _reloadController = new ReloadController(this, _checkpoints, _requireFullWipe); _chatCommandHandler = new ChatCommandHandler(enabled, command, hideCommandMessage); _autoRestartMonitor = new AutoRestartMonitor(this, _reloadController, _checkpoints, enabled2, delay); _harmony = new Harmony("qup0ix.QuickRunReload"); _harmony.PatchAll(typeof(Plugin).Assembly); Log.LogInfo((object)string.Format("{0} {1} loaded. Reload hotkey: {2}", "QuickRunReload", "1.2.0", _reloadHotkey.Value)); _chatCommandHandler.ValidateConfiguration(); LogCompatibilityInformation(); } private void Update() { //IL_000e: 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) if (_reloadHotkey != null) { KeyboardShortcut value = _reloadHotkey.Value; if (((KeyboardShortcut)(ref value)).IsDown()) { RequestRestart("hotkey"); } } if (_autoRestartMonitor != null) { _autoRestartMonitor.Tick(); } } private void OnDestroy() { if (_harmony != null) { _harmony.UnpatchSelf(); } } internal void CaptureCheckpoint(StartOfRound round) { if (_reloadController.IsReloadInProgress) { return; } GameNetworkManager instance = GameNetworkManager.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)round == (Object)null || !instance.isHostingGame || !round.inShipPhase || round.beganLoadingNewLevel) { return; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsHost) { return; } if (string.Equals(instance.currentSaveFileName, "LCChallengeFile", StringComparison.Ordinal)) { Log.LogWarning((object)"Challenge saves are not checkpointed."); return; } try { SavePreLandingState(instance); _checkpoints.Create(instance.currentSaveFileName, instance.saveFileNum); } catch (Exception ex) { Log.LogError((object)"Failed to create the pre-landing checkpoint. Reload requests are disabled for this landing."); Log.LogError((object)ex); _checkpoints.Invalidate(); } } internal bool ShouldSuppressSaveGame() { if (_reloadController != null) { return _reloadController.SuppressSaveGame; } return false; } internal bool HandleChatSubmit(HUDManager hudManager, CallbackContext context) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (_chatCommandHandler != null) { return _chatCommandHandler.RunOriginalAfterSubmit(hudManager, context); } return true; } internal bool HandleChatEnable(HUDManager hudManager, CallbackContext context) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (_chatCommandHandler != null) { return _chatCommandHandler.RunOriginalAfterEnable(hudManager, context); } return true; } internal bool RequestRestart(string source, bool activePlayersVerifiedDead = false) { if (_reloadController != null) { return _reloadController.TryBeginReload(source, activePlayersVerifiedDead); } return false; } private static void SavePreLandingState(GameNetworkManager network) { FieldInfo fieldInfo = null; bool flag = true; try { Type type = Type.GetType("LobbyControl.LobbyControl, LobbyControl", throwOnError: false); if (type != null) { fieldInfo = type.GetField("CanSave", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo != null && fieldInfo.FieldType == typeof(bool)) { flag = (bool)fieldInfo.GetValue(null); if (!flag) { Log.LogInfo((object)"Temporarily allowing LobbyControl save for the pre-landing checkpoint."); fieldInfo.SetValue(null, true); } } } network.SaveGame(); } finally { if (fieldInfo != null && !flag) { fieldInfo.SetValue(null, false); } } } private static void LogCompatibilityInformation() { foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Value.Metadata.Name ?? string.Empty; if (text.IndexOf("LobbyControl", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Save Before Landing", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("SaveBeforeLanding", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("MoreCompany", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("LateCompany", StringComparison.OrdinalIgnoreCase) >= 0) { Log.LogInfo((object)$"Detected compatibility mod: {text} {pluginInfo.Value.Metadata.Version}"); } } } } internal sealed class AutoRestartMonitor { private enum IgnoreReason { None, NotHost, ReloadInProgress, NotInActiveLanding } private const float PollInterval = 0.25f; private readonly Plugin _plugin; private readonly ReloadController _reloadController; private readonly CheckpointManager _checkpoints; private readonly ConfigEntry<bool> _enabled; private readonly ConfigEntry<float> _delay; private float _nextPollTime; private float _restartAt; private bool _restartScheduled; private IgnoreReason _lastIgnoreReason; internal AutoRestartMonitor(Plugin plugin, ReloadController reloadController, CheckpointManager checkpoints, ConfigEntry<bool> enabled, ConfigEntry<float> delay) { _plugin = plugin; _reloadController = reloadController; _checkpoints = checkpoints; _enabled = enabled; _delay = delay; } internal void Tick() { if (!_enabled.Value) { ResetTracking(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextPollTime) { return; } _nextPollTime = realtimeSinceStartup + 0.25f; GameNetworkManager instance = GameNetworkManager.Instance; NetworkManager singleton = NetworkManager.Singleton; StartOfRound instance2 = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)singleton == (Object)null || (Object)(object)instance2 == (Object)null) { Block(IgnoreReason.NotInActiveLanding); return; } if (!instance.isHostingGame || !singleton.IsHost) { Block(IgnoreReason.NotHost); return; } if (_reloadController.IsReloadInProgress) { Block(IgnoreReason.ReloadInProgress); return; } if (!IsActiveLanding(instance, singleton, instance2)) { Block(IgnoreReason.NotInActiveLanding); return; } CountActivePlayers(instance2, singleton, out var activePlayers, out var livingPlayers); if (activePlayers == 0) { Block(IgnoreReason.NotInActiveLanding); return; } _lastIgnoreReason = IgnoreReason.None; if (livingPlayers > 0) { if (_restartScheduled) { Plugin.Log.LogInfo((object)"Auto restart cancelled because a player is alive"); } _restartScheduled = false; return; } if (!_restartScheduled) { float num = _delay.Value; if (num < 0f || float.IsNaN(num) || float.IsInfinity(num)) { num = 0f; } _restartScheduled = true; _restartAt = realtimeSinceStartup + num; Plugin.Log.LogInfo((object)"All active players are dead"); Plugin.Log.LogInfo((object)string.Format(CultureInfo.InvariantCulture, "Auto restart scheduled in {0:0.###} seconds", num)); } if (!(realtimeSinceStartup < _restartAt)) { _restartScheduled = false; Plugin.Log.LogInfo((object)"Auto restart triggered"); _plugin.RequestRestart("auto restart", activePlayersVerifiedDead: true); } } private bool IsActiveLanding(GameNetworkManager gameNetwork, NetworkManager netcode, StartOfRound round) { if (netcode.IsListening && ((NetworkBehaviour)round).IsSpawned && gameNetwork.gameHasStarted && !round.inShipPhase && round.shipHasLanded && !round.shipIsLeaving) { return _checkpoints.Matches(gameNetwork.currentSaveFileName); } return false; } private static void CountActivePlayers(StartOfRound round, NetworkManager netcode, out int activePlayers, out int livingPlayers) { activePlayers = 0; livingPlayers = 0; PlayerControllerB[] allPlayerScripts = round.allPlayerScripts; if (allPlayerScripts == null) { return; } foreach (PlayerControllerB val in allPlayerScripts) { if (!((Object)(object)val == (Object)null) && ((NetworkBehaviour)val).IsSpawned && !val.disconnectedMidGame && (val.isPlayerControlled || val.isPlayerDead) && netcode.ConnectedClients.ContainsKey(val.actualClientId)) { activePlayers++; if (!val.isPlayerDead) { livingPlayers++; } } } } private void Block(IgnoreReason reason) { _restartScheduled = false; if (_lastIgnoreReason != reason) { _lastIgnoreReason = reason; switch (reason) { case IgnoreReason.NotHost: Plugin.Log.LogInfo((object)"Auto restart ignored: local player is not host"); break; case IgnoreReason.ReloadInProgress: Plugin.Log.LogInfo((object)"Auto restart ignored: reload already in progress"); break; case IgnoreReason.NotInActiveLanding: Plugin.Log.LogInfo((object)"Auto restart ignored: not in active landing"); break; } } } private void ResetTracking() { _restartScheduled = false; _lastIgnoreReason = IgnoreReason.None; _nextPollTime = 0f; } } internal sealed class ChatCommandHandler { private readonly ConfigEntry<bool> _enabled; private readonly ConfigEntry<string> _command; private readonly ConfigEntry<bool> _hideCommandMessage; private bool _warnedAboutEmptyCommand; internal ChatCommandHandler(ConfigEntry<bool> enabled, ConfigEntry<string> command, ConfigEntry<bool> hideCommandMessage) { _enabled = enabled; _command = command; _hideCommandMessage = hideCommandMessage; } internal void ValidateConfiguration() { GetConfiguredCommand(); } internal bool RunOriginalAfterEnable(HUDManager hudManager, CallbackContext context) { if (!_enabled.Value || !((CallbackContext)(ref context)).performed || (Object)(object)hudManager == (Object)null || GetConfiguredCommand() == null) { return true; } GameNetworkManager instance = GameNetworkManager.Instance; StartOfRound instance2 = StartOfRound.Instance; if (!IsActiveHost() || (Object)(object)instance == (Object)null || (Object)(object)instance.localPlayerController == (Object)null || (Object)(object)instance2 == (Object)null || instance2.inShipPhase || !instance.localPlayerController.isPlayerDead) { return true; } hudManager.localPlayer = instance.localPlayerController; if (hudManager.localPlayer.inTerminalMenu || (Object)(object)hudManager.chatTextField == (Object)null) { return true; } hudManager.localPlayer.isTypingChat = true; ((Selectable)hudManager.chatTextField).Select(); hudManager.chatTextField.ActivateInputField(); hudManager.PingHUDElement(hudManager.Chat, 0.1f, 1f, 1f); if ((Object)(object)hudManager.typingIndicator != (Object)null) { ((Behaviour)hudManager.typingIndicator).enabled = true; } return false; } internal bool RunOriginalAfterSubmit(HUDManager hudManager, CallbackContext context) { if (!_enabled.Value || !((CallbackContext)(ref context)).performed || (Object)(object)hudManager == (Object)null || (Object)(object)hudManager.chatTextField == (Object)null) { return true; } string configuredCommand = GetConfiguredCommand(); if (configuredCommand == null) { return true; } string text = hudManager.chatTextField.text; if (text == null || !string.Equals(text.Trim(), configuredCommand, StringComparison.OrdinalIgnoreCase)) { if (IsDeadActiveHost()) { CloseChatInput(hudManager); return false; } return true; } Plugin.Log.LogInfo((object)"Chat command detected"); if (!IsActiveHost()) { Plugin.Log.LogWarning((object)"Chat command ignored: local player is not host"); if (_hideCommandMessage.Value) { CloseChatInput(hudManager); return false; } return true; } Plugin.Log.LogInfo((object)"Restart requested by chat command"); Plugin.Instance.RequestRestart("chat command"); if (!_hideCommandMessage.Value && !IsDeadActiveHost()) { return true; } CloseChatInput(hudManager); return false; } private string GetConfiguredCommand() { string value = _command.Value; if (string.IsNullOrWhiteSpace(value)) { if (!_warnedAboutEmptyCommand) { Plugin.Log.LogWarning((object)"ChatCommand is empty or whitespace; the chat command is disabled until a non-empty value is configured."); _warnedAboutEmptyCommand = true; } return null; } return value.Trim(); } private static bool IsActiveHost() { NetworkManager singleton = NetworkManager.Singleton; GameNetworkManager instance = GameNetworkManager.Instance; if ((Object)(object)singleton != (Object)null && singleton.IsHost && (Object)(object)instance != (Object)null) { return instance.isHostingGame; } return false; } private static bool IsDeadActiveHost() { GameNetworkManager instance = GameNetworkManager.Instance; if (IsActiveHost() && (Object)(object)instance != (Object)null && (Object)(object)instance.localPlayerController != (Object)null) { return instance.localPlayerController.isPlayerDead; } return false; } private static void CloseChatInput(HUDManager hudManager) { GameNetworkManager instance = GameNetworkManager.Instance; if ((Object)(object)instance != (Object)null && (Object)(object)instance.localPlayerController != (Object)null) { instance.localPlayerController.isTypingChat = false; } if ((Object)(object)hudManager.chatTextField != (Object)null) { hudManager.chatTextField.text = string.Empty; hudManager.chatTextField.DeactivateInputField(false); } if ((Object)(object)hudManager.typingIndicator != (Object)null) { ((Behaviour)hudManager.typingIndicator).enabled = false; } } } internal sealed class CheckpointManager { private sealed class CheckpointFile { internal string OriginalPath; internal string BackupPath; } private readonly string _rootDirectory; private readonly List<CheckpointFile> _files = new List<CheckpointFile>(); internal string SaveFileName { get; private set; } internal int SaveFileNumber { get; private set; } internal DateTime CreatedUtc { get; private set; } internal bool IsValid { get; private set; } internal CheckpointManager() { _rootDirectory = Path.Combine(Paths.ConfigPath, "QuickRunReload", "checkpoint"); } internal bool Matches(string saveFileName) { if (IsValid) { return string.Equals(SaveFileName, saveFileName, StringComparison.Ordinal); } return false; } internal void Invalidate() { IsValid = false; _files.Clear(); SaveFileName = null; } internal void Create(string saveFileName, int saveFileNumber) { if (string.IsNullOrEmpty(saveFileName)) { throw new InvalidOperationException("GameNetworkManager.currentSaveFileName is empty."); } string persistentDataPath = Application.persistentDataPath; string text = Path.Combine(persistentDataPath, saveFileName); if (!File.Exists(text)) { throw new FileNotFoundException("The active save file does not exist after SaveGame().", text); } string text2 = _rootDirectory + ".staging"; RecreateOwnedDirectory(text2); List<string> list = new List<string>(); list.Add(text); string[] files = Directory.GetFiles(persistentDataPath, saveFileName + ".*"); foreach (string text3 in files) { if (!list.Contains<string>(text3, StringComparer.OrdinalIgnoreCase) && !text3.EndsWith(".qrr.restore.tmp", StringComparison.OrdinalIgnoreCase)) { list.Add(text3); } } foreach (string item in list) { string destFileName = Path.Combine(text2, Path.GetFileName(item)); File.Copy(item, destFileName, overwrite: true); } string contents = string.Format("version=1{0}saveBase64={1}{0}saveFileNumber={2}{0}createdUtc={3:o}{0}files={4}{0}", Environment.NewLine, Convert.ToBase64String(Encoding.UTF8.GetBytes(saveFileName)), saveFileNumber, DateTime.UtcNow, string.Join("|", list.Select(Path.GetFileName).ToArray())); File.WriteAllText(Path.Combine(text2, "checkpoint.meta"), contents, Encoding.UTF8); RecreateOwnedDirectory(_rootDirectory); string[] files2 = Directory.GetFiles(text2); foreach (string text4 in files2) { File.Move(text4, Path.Combine(_rootDirectory, Path.GetFileName(text4))); } Directory.Delete(text2, recursive: false); _files.Clear(); foreach (string item2 in list) { _files.Add(new CheckpointFile { OriginalPath = item2, BackupPath = Path.Combine(_rootDirectory, Path.GetFileName(item2)) }); } SaveFileName = saveFileName; SaveFileNumber = saveFileNumber; CreatedUtc = DateTime.UtcNow; IsValid = true; Plugin.Log.LogInfo((object)$"Created pre-landing checkpoint for '{saveFileName}' ({_files.Count} file(s)) at {_rootDirectory}."); } internal void Restore() { if (!IsValid || _files.Count == 0) { throw new InvalidOperationException("No valid pre-landing checkpoint is available."); } foreach (CheckpointFile file in _files) { if (!File.Exists(file.BackupPath)) { throw new FileNotFoundException("Checkpoint file is missing.", file.BackupPath); } } foreach (CheckpointFile file2 in _files) { AtomicCopy(file2.BackupPath, file2.OriginalPath); } Plugin.Log.LogInfo((object)$"Restored checkpoint for '{SaveFileName}'."); } private static void AtomicCopy(string source, string destination) { string text = destination + ".qrr.restore.tmp"; File.Copy(source, text, overwrite: true); try { if (File.Exists(destination)) { File.Replace(text, destination, null, ignoreMetadataErrors: true); } else { File.Move(text, destination); } } catch (PlatformNotSupportedException) { File.Copy(text, destination, overwrite: true); File.Delete(text); } catch (IOException) { File.Copy(text, destination, overwrite: true); File.Delete(text); } } private static void RecreateOwnedDirectory(string path) { if (Directory.Exists(path)) { Directory.Delete(path, recursive: true); } Directory.CreateDirectory(path); } } internal sealed class ReloadController { private sealed class LobbySettingsSnapshot { internal string Name; internal bool IsPublic; internal string Tag; } private readonly Plugin _plugin; private readonly CheckpointManager _checkpoints; private readonly ConfigEntry<bool> _requireFullWipe; private float _lastRequestTime = -100f; internal bool IsReloadInProgress { get; private set; } internal bool SuppressSaveGame { get; private set; } internal ReloadController(Plugin plugin, CheckpointManager checkpoints, ConfigEntry<bool> requireFullWipe) { _plugin = plugin; _checkpoints = checkpoints; _requireFullWipe = requireFullWipe; } internal bool TryBeginReload(string source, bool activePlayersVerifiedDead = false) { if (IsReloadInProgress || Time.realtimeSinceStartup - _lastRequestTime < 1f) { Plugin.Log.LogWarning((object)$"Reload request ignored ({source}): a reload is already running or another request was just received."); return false; } _lastRequestTime = Time.realtimeSinceStartup; GameNetworkManager instance = GameNetworkManager.Instance; NetworkManager singleton = NetworkManager.Singleton; StartOfRound instance2 = StartOfRound.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)singleton == (Object)null || (Object)(object)instance2 == (Object)null || !instance.isHostingGame || !singleton.IsHost) { Plugin.Log.LogWarning((object)$"Reload request ignored ({source}): only the active host can reload a run."); return false; } if (instance2.inShipPhase) { Plugin.Log.LogWarning((object)$"Reload request ignored ({source}): the ship is not currently landed."); return false; } if (_requireFullWipe.Value && !activePlayersVerifiedDead && !instance2.allPlayersDead && instance2.livingPlayers > 0) { Plugin.Log.LogWarning((object)$"Reload request ignored ({source}): at least one player is still alive."); return false; } if (!_checkpoints.Matches(instance.currentSaveFileName)) { Plugin.Log.LogError((object)$"Reload request ignored ({source}): there is no checkpoint for the active save and landing."); return false; } string currentSaveFileName = instance.currentSaveFileName; int saveFileNum = instance.saveFileNum; LobbySettingsSnapshot lobby = CaptureLobbySettings(instance); IsReloadInProgress = true; SuppressSaveGame = true; Plugin.Log.LogWarning((object)$"Wipe reload requested by {source}. Restoring '{currentSaveFileName}' and restarting the host session."); ((MonoBehaviour)_plugin).StartCoroutine(ReloadRoutine(instance, currentSaveFileName, saveFileNum, lobby)); return true; } private IEnumerator ReloadRoutine(GameNetworkManager oldNetwork, string saveFileName, int saveFileNumber, LobbySettingsSnapshot lobby) { try { _checkpoints.Restore(); oldNetwork.Disconnect(); _checkpoints.Restore(); } catch (Exception ex) { Plugin.Log.LogError((object)"Could not restore the checkpoint; the session was not re-hosted."); Plugin.Log.LogError((object)ex); Finish(success: false); yield break; } float menuDeadline = Time.realtimeSinceStartup + 30f; MenuManager menu = null; while (Time.realtimeSinceStartup < menuDeadline) { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == "MainMenu") { menu = Object.FindObjectOfType<MenuManager>(); } if ((Object)(object)menu != (Object)null && (Object)(object)GameNetworkManager.Instance != (Object)null) { break; } yield return null; } if ((Object)(object)menu == (Object)null || (Object)(object)GameNetworkManager.Instance == (Object)null) { Plugin.Log.LogError((object)"Timed out waiting for MainMenu after disconnect. The checkpoint is safe on disk; host the same save manually."); Finish(success: false); yield break; } yield return null; GameNetworkManager newNetwork = GameNetworkManager.Instance; try { _checkpoints.Restore(); newNetwork.currentSaveFileName = saveFileName; newNetwork.saveFileNum = saveFileNumber; newNetwork.lobbyHostSettings = new HostSettings(lobby.Name, lobby.IsPublic, lobby.Tag); Plugin.Log.LogInfo((object)$"Re-hosting save '{saveFileName}' through GameNetworkManager.StartHost()."); newNetwork.StartHost(); } catch (Exception ex2) { Plugin.Log.LogError((object)"Automatic re-host failed. The checkpoint is restored; host the same save manually."); Plugin.Log.LogError((object)ex2); Finish(success: false); yield break; } float hostDeadline = Time.realtimeSinceStartup + 60f; while (Time.realtimeSinceStartup < hostDeadline) { NetworkManager manager = NetworkManager.Singleton; StartOfRound newRound = StartOfRound.Instance; if ((Object)(object)manager != (Object)null && manager.IsHost && (Object)(object)newRound != (Object)null && newRound.inShipPhase) { Plugin.Log.LogInfo((object)$"Reload complete. Save '{saveFileName}' is back in the pre-landing ship state."); Finish(success: true); yield break; } yield return null; } Plugin.Log.LogError((object)"Timed out waiting for the restored ship scene. The checkpoint remains available on disk."); Finish(success: false); } private static LobbySettingsSnapshot CaptureLobbySettings(GameNetworkManager network) { HostSettings lobbyHostSettings = network.lobbyHostSettings; LobbySettingsSnapshot lobbySettingsSnapshot = new LobbySettingsSnapshot(); lobbySettingsSnapshot.Name = ((lobbyHostSettings != null && !string.IsNullOrEmpty(lobbyHostSettings.lobbyName)) ? lobbyHostSettings.lobbyName : ((!string.IsNullOrEmpty(network.steamLobbyName)) ? network.steamLobbyName : "QuickRunReload")); lobbySettingsSnapshot.IsPublic = lobbyHostSettings?.isLobbyPublic ?? false; lobbySettingsSnapshot.Tag = ((lobbyHostSettings != null) ? lobbyHostSettings.serverTag : string.Empty); return lobbySettingsSnapshot; } private void Finish(bool success) { SuppressSaveGame = false; IsReloadInProgress = false; if (!success) { Plugin.Log.LogWarning((object)"Quick reload stopped safely. Save suppression has been released."); } } } [HarmonyPatch(typeof(StartOfRound), "StartGame")] internal static class StartOfRoundStartGamePatch { [HarmonyPrefix] [HarmonyPriority(0)] private static void CaptureCheckpointPrefix(StartOfRound __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.CaptureCheckpoint(__instance); } } } [HarmonyPatch(typeof(GameNetworkManager), "SaveGame")] internal static class GameNetworkManagerSaveGamePatch { [HarmonyPriority(800)] [HarmonyPrefix] private static bool SuppressDeathSaveDuringReloadPrefix() { bool flag = (Object)(object)Plugin.Instance != (Object)null && Plugin.Instance.ShouldSuppressSaveGame(); if (flag) { Plugin.Log.LogWarning((object)"Suppressed GameNetworkManager.SaveGame() while restoring the pre-landing checkpoint."); } return !flag; } } [HarmonyPatch(typeof(HUDManager), "SubmitChat_performed")] internal static class HUDManagerSubmitChatPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool DetectRestartCommandPrefix(HUDManager __instance, CallbackContext context) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Plugin.Instance == (Object)null)) { return Plugin.Instance.HandleChatSubmit(__instance, context); } return true; } } [HarmonyPatch(typeof(HUDManager), "EnableChat_performed")] internal static class HUDManagerEnableChatPatch { [HarmonyPrefix] [HarmonyPriority(800)] private static bool AllowDeadHostCommandInputPrefix(HUDManager __instance, CallbackContext context) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Plugin.Instance == (Object)null)) { return Plugin.Instance.HandleChatEnable(__instance, context); } return true; } }