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 ClientGhostWatchdog v1.1.0
ClientGhostWatchdog.dll
Decompiled 4 days agousing System; using System.Collections; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ClientGhostWatchdog")] [assembly: AssemblyDescription("Client-side watchdog mod for players connecting to Valheim servers that detects dropped connections and disconnects cleanly, preventing ghost desyncs. For more information, visit https://github.com/dreamwraith")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("dreamwraith (https://github.com/dreamwraith)")] [assembly: AssemblyProduct("ClientGhostWatchdog")] [assembly: AssemblyCopyright("Copyright © 2026 dreamwraith")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e6d8b2f0-1a74-4b52-9b2f-45b08ce74e99")] [assembly: AssemblyFileVersion("1.1.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ClientGhostWatchdog { [BepInPlugin("dreamwraith.ClientGhostWatchdog", "ClientGhostWatchdog", "1.1.0")] public class Plugin : BaseUnityPlugin { public const string ModGUID = "dreamwraith.ClientGhostWatchdog"; public const string ModName = "ClientGhostWatchdog"; public const string ModVersion = "1.1.0"; internal static ManualLogSource Log; private Harmony? _harmony; public static ConfigEntry<float> TimeoutSeconds; public static ConfigEntry<float> CheckIntervalSeconds; public static ConfigEntry<float> TeleportTimeoutSeconds; public static ConfigEntry<string> WarningNoticeMessage; public static ConfigEntry<string> RecoveredNoticeMessage; public static ConfigEntry<string> DisconnectNoticeMessage; public static ConfigEntry<float> DisconnectDelaySeconds; public static ConfigEntry<bool> ShowDisconnectReason; public static ConfigEntry<string> DisconnectMessage; public static ConfigEntry<bool> EnableDebugLogs; public static ConfigEntry<KeyCode> DebugSimulateSeverKey; internal static Plugin? Instance { get; private set; } private void Awake() { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Expected O, but got Unknown //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; TimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "TimeoutSeconds", 30f, new ConfigDescription("Total seconds of server silence before disconnecting to prevent zombie/ghost client desyncs. Halfway through this time (e.g. 15s at default 30s), a warning is displayed. Set to 0 to disable.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 300f), Array.Empty<object>())); CheckIntervalSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "CheckIntervalSeconds", 1f, new ConfigDescription("Interval in seconds between watchdog checks in the background coroutine timer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 5f), Array.Empty<object>())); TeleportTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "TeleportTimeoutSeconds", 30f, new ConfigDescription("Maximum seconds a player can remain in a teleporting/portal state before connection health checks resume. Prevents infinite limbo if a portal handshake hangs. Set to 0 to disable ceiling.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 180f), Array.Empty<object>())); WarningNoticeMessage = ((BaseUnityPlugin)this).Config.Bind<string>("2 - On-Screen Notices", "WarningNoticeMessage", "Server connection lost, attempting to reconnect... ({0}s)", "Large on-screen notice displayed halfway through the check period. {0} is replaced with remaining seconds until disconnect."); RecoveredNoticeMessage = ((BaseUnityPlugin)this).Config.Bind<string>("2 - On-Screen Notices", "RecoveredNoticeMessage", "Server connection restored.", "Large on-screen notice displayed if server connection recovers before timing out."); DisconnectNoticeMessage = ((BaseUnityPlugin)this).Config.Bind<string>("2 - On-Screen Notices", "DisconnectNoticeMessage", "Server connection lost. Disconnecting...", "Large on-screen notice displayed when the timeout is reached and the player is being disconnected."); DisconnectDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - On-Screen Notices", "DisconnectDelaySeconds", 5f, new ConfigDescription("Seconds to wait after displaying the on-screen disconnect notice before executing logout, giving players time to read the notice. Set to 0 for instantaneous logout.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>())); ShowDisconnectReason = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Main Menu Dialog", "ShowDisconnectReason", true, "Whether to display an explanatory message on the main menu dialog after being disconnected by this watchdog."); DisconnectMessage = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Main Menu Dialog", "DisconnectMessage", "Server connection lost (Ghost connection prevented). Progress was saved.", "Custom message displayed on the main menu dialog when disconnected by this watchdog."); EnableDebugLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("4 - Debug", "EnableDebugLogs", false, "Whether to print detailed debug logs with every watchdog check, timer tick, and network/lifecycle event."); DebugSimulateSeverKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("4 - Debug", "DebugSimulateSeverKey", (KeyCode)0, "DEBUG ONLY: Keybind to simulate an abrupt external peer disconnection (ServerCharacters behavior). Requires EnableDebugLogs to be true. Keep as None for normal play."); _harmony = new Harmony("dreamwraith.ClientGhostWatchdog"); _harmony.PatchAll(); Log.LogInfo((object)"ClientGhostWatchdog v1.1.0 loaded successfully."); } private void OnDestroy() { WatchdogManager.Stop("Plugin.OnDestroy"); Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void Update() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (EnableDebugLogs != null && EnableDebugLogs.Value && DebugSimulateSeverKey != null && (int)DebugSimulateSeverKey.Value != 0 && Input.GetKeyDown(DebugSimulateSeverKey.Value)) { SimulateSeveredPeer(); } } internal static void SimulateSeveredPeer() { if (EnableDebugLogs == null || !EnableDebugLogs.Value) { Log.LogWarning((object)"[DEBUG TEST] Cannot simulate peer disconnect: EnableDebugLogs must be true in config."); return; } if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer() || (Object)(object)Player.m_localPlayer == (Object)null) { Log.LogWarning((object)"[DEBUG TEST] Cannot simulate peer disconnect: Must be in an active multiplayer session as a client."); return; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null) { Log.LogWarning((object)"[DEBUG TEST] Server peer is already null."); return; } Log.LogWarning((object)"[DEBUG TEST] Intentionally calling ZNet.instance.Disconnect(serverPeer) to simulate badly behaved third-party mod behavior..."); ZNet.instance.Disconnect(serverPeer); } internal static void LogDebug(string message) { if (EnableDebugLogs != null && EnableDebugLogs.Value) { Log.LogInfo((object)("[DEBUG] " + message)); } } } public static class WatchdogManager { private static Coroutine? s_watchdogCoroutine; private static bool s_isWarningActive = false; private static int s_lastLoggedRemainingSec = -1; private static float s_lastNoticeDisplayTime = 0f; private static float s_teleportStartTime = -1f; public static bool IsRunning => s_watchdogCoroutine != null; public static bool IsWarningActive => s_isWarningActive; public static void Start(string caller) { Plugin.LogDebug("WatchdogManager.Start invoked by '" + caller + "'. Checking eligibility..."); if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { object arg = (Object)(object)ZNet.instance == (Object)null; ZNet instance = ZNet.instance; Plugin.LogDebug($"WatchdogManager.Start: Bypassed. Local world or server host detected (instance null: {arg}, IsServer: {((instance != null) ? new bool?(instance.IsServer()) : ((bool?)null))})."); return; } if (Plugin.TimeoutSeconds.Value <= 0f) { Plugin.LogDebug("WatchdogManager.Start: Bypassed. Watchdog disabled (TimeoutSeconds <= 0)."); return; } Stop("restarting from " + caller); if ((Object)(object)Plugin.Instance != (Object)null) { s_watchdogCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(WatchdogRoutine()); Plugin.Log.LogInfo((object)"[ClientGhostWatchdog] Watchdog coroutine started for server connection."); Plugin.LogDebug($"WatchdogManager.Start: Coroutine initiated successfully. Interval={Plugin.CheckIntervalSeconds.Value}s, Timeout={Plugin.TimeoutSeconds.Value}s."); } } public static void Stop(string reason) { if (s_watchdogCoroutine != null) { Plugin.LogDebug("WatchdogManager.Stop: Halting active coroutine. Reason: " + reason + "."); if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StopCoroutine(s_watchdogCoroutine); } s_watchdogCoroutine = null; } else { Plugin.LogDebug("WatchdogManager.Stop: No active coroutine to stop. Reason: " + reason + "."); } s_isWarningActive = false; s_lastLoggedRemainingSec = -1; s_lastNoticeDisplayTime = 0f; s_teleportStartTime = -1f; } private static IEnumerator DisconnectRoutine(string reasonDetail) { Plugin.Log.LogWarning((object)("[ClientGhostWatchdog] " + reasonDetail + ". Forcing clean logout to preserve player progress.")); s_isWarningActive = false; if ((int)ZNet.GetConnectionStatus() == 2) { if ((Object)(object)MessageHud.instance != (Object)null && !string.IsNullOrEmpty(Plugin.DisconnectNoticeMessage.Value)) { MessageHud.instance.ShowMessage((MessageType)2, Plugin.DisconnectNoticeMessage.Value, 0, (Sprite)null, false, true); } if (Plugin.ShowDisconnectReason.Value) { DisconnectReasonManager.SetReason(Plugin.DisconnectMessage.Value); } float num = Mathf.Clamp(Plugin.DisconnectDelaySeconds?.Value ?? 5f, 0f, 30f); if (num > 0f) { yield return (object)new WaitForSecondsRealtime(num); } ZNet.SetExternalError((ConnectionStatus)4); } else { Plugin.LogDebug($"DisconnectRoutine: Preserving existing native connection status ({ZNet.GetConnectionStatus()})."); } s_watchdogCoroutine = null; if ((Object)(object)Game.instance != (Object)null && !Game.instance.IsShuttingDown()) { Game.instance.Logout(true, true); } } private static IEnumerator WatchdogRoutine() { Plugin.LogDebug("WatchdogManager: Coroutine execution loop entered."); float timeSinceLastPing; float value2; while (true) { yield return (object)new WaitForSecondsRealtime(Mathf.Max(1f, Plugin.CheckIntervalSeconds.Value)); if (Plugin.TimeoutSeconds.Value <= 0f) { Plugin.LogDebug("Watchdog tick: TimeoutSeconds <= 0 (watchdog disabled via config)."); s_isWarningActive = false; continue; } if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { object arg = (Object)(object)ZNet.instance == (Object)null; ZNet instance = ZNet.instance; Plugin.LogDebug($"Watchdog tick: Terminating. Local world or server host detected (instance null: {arg}, IsServer: {((instance != null) ? new bool?(instance.IsServer()) : ((bool?)null))})."); s_watchdogCoroutine = null; yield break; } if ((Object)(object)Game.instance == (Object)null || Game.instance.IsShuttingDown()) { Plugin.LogDebug("Watchdog tick: Terminating. Game instance is null or shutting down."); s_watchdogCoroutine = null; yield break; } if ((int)ZNet.GetConnectionStatus() != 2) { if ((Object)(object)Player.m_localPlayer != (Object)null) { yield return DisconnectRoutine($"Connection status lost ({ZNet.GetConnectionStatus()})"); yield break; } Plugin.LogDebug($"Watchdog tick: Initial connection status is {ZNet.GetConnectionStatus()} before player spawn. Terminating."); s_watchdogCoroutine = null; yield break; } if ((Object)(object)Player.m_localPlayer == (Object)null) { Plugin.LogDebug("Watchdog tick: Waiting for local player to spawn into world..."); continue; } if (((Character)Player.m_localPlayer).IsTeleporting()) { float value = Plugin.TeleportTimeoutSeconds.Value; if (value <= 0f) { Plugin.LogDebug("Watchdog tick: Skipping check during player teleportation (ceiling disabled via config)."); continue; } if (s_teleportStartTime < 0f) { s_teleportStartTime = Time.unscaledTime; } float num = Time.unscaledTime - s_teleportStartTime; if (num < value) { Plugin.LogDebug($"Watchdog tick: Skipping check during player teleportation (elapsed: {num:F1}s / max: {value:F1}s)."); continue; } Plugin.Log.LogWarning((object)$"[ClientGhostWatchdog] Player has been in teleporting state for {num:F1}s (ceiling: {value:F1}s). Resuming connection health checks."); } else { s_teleportStartTime = -1f; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || serverPeer.m_rpc == null) { yield return DisconnectRoutine("Server peer connection lost (disposed by network layer)"); yield break; } bool flag = !serverPeer.m_rpc.IsConnected(); if (flag) { yield return DisconnectRoutine("Server socket connection closed"); yield break; } timeSinceLastPing = serverPeer.m_rpc.GetTimeSinceLastPing(); value2 = Plugin.TimeoutSeconds.Value; float num2 = value2 / 2f; Plugin.LogDebug($"Watchdog check: pingSilence={timeSinceLastPing:F2}s, socketClosed={flag}, warningThreshold={num2:F1}s, timeout={value2:F1}s, warningActive={s_isWarningActive}"); if (timeSinceLastPing >= value2) { break; } if (timeSinceLastPing >= num2) { int num3 = Mathf.Max(1, Mathf.CeilToInt(value2 - timeSinceLastPing)); if (!s_isWarningActive) { s_isWarningActive = true; Plugin.Log.LogWarning((object)$"[ClientGhostWatchdog] Server silence detected ({timeSinceLastPing:F1}s >= halfway threshold {num2:F1}s). Displaying reconnect warning."); Plugin.LogDebug($"Watchdog trigger: Stage 1 warning activated. remainingSec={num3}s."); } float unscaledTime = Time.unscaledTime; if (num3 != s_lastLoggedRemainingSec || unscaledTime - s_lastNoticeDisplayTime >= 1.5f) { s_lastLoggedRemainingSec = num3; s_lastNoticeDisplayTime = unscaledTime; if ((Object)(object)MessageHud.instance != (Object)null && !string.IsNullOrEmpty(Plugin.WarningNoticeMessage.Value)) { string text = FormatNotice(Plugin.WarningNoticeMessage.Value, num3); MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false, true); Plugin.LogDebug("Watchdog notice refreshed: \"" + text + "\""); } } } else if (s_isWarningActive) { s_isWarningActive = false; s_lastLoggedRemainingSec = -1; Plugin.Log.LogInfo((object)$"[ClientGhostWatchdog] Server connection recovered after {timeSinceLastPing:F1}s silence."); Plugin.LogDebug($"Watchdog trigger: Recovery detected (silence dropped to {timeSinceLastPing:F2}s). Warning cleared."); if ((Object)(object)MessageHud.instance != (Object)null && !string.IsNullOrEmpty(Plugin.RecoveredNoticeMessage.Value)) { MessageHud.instance.ShowMessage((MessageType)2, Plugin.RecoveredNoticeMessage.Value, 0, (Sprite)null, false, true); } } } yield return DisconnectRoutine($"Server stopped responding to pings for {timeSinceLastPing:F1}s (threshold: {value2:F1}s)"); } private static string FormatNotice(string template, int remainingSec) { if (string.IsNullOrEmpty(template)) { return string.Empty; } try { return template.Contains("{0}") ? string.Format(template, remainingSec) : template; } catch (FormatException) { return template; } } } internal static class DisconnectReasonManager { private static string? s_pendingReason; private static float s_reasonTimestamp; private const float ReasonTtlSeconds = 300f; public static string? PendingReason { get { if (string.IsNullOrEmpty(s_pendingReason)) { return null; } float num = Time.unscaledTime - s_reasonTimestamp; if (num > 300f) { Plugin.LogDebug($"DisconnectReasonManager: Pending reason expired (elapsed {num:F1}s > TTL {300f}s)."); s_pendingReason = null; s_reasonTimestamp = 0f; return null; } return s_pendingReason; } } public static void SetReason(string reason) { s_pendingReason = reason; s_reasonTimestamp = Time.unscaledTime; Plugin.LogDebug($"DisconnectReasonManager: Registered pending reason (TTL={300f}s): \"{reason}\""); } public static void Clear() { s_pendingReason = null; s_reasonTimestamp = 0f; Plugin.LogDebug("DisconnectReasonManager: Cleared pending reason."); } public static void ApplyToMenu(FejdStartup startup) { string pendingReason = PendingReason; if (!string.IsNullOrEmpty(pendingReason)) { ApplyTextAndShowPanel(startup, pendingReason); ((MonoBehaviour)startup).StartCoroutine(ReinforceNoticeRoutine(startup, pendingReason)); } } private static void ApplyTextAndShowPanel(FejdStartup startup, string reason) { if ((Object)(object)startup.m_connectionFailedPanel != (Object)null) { startup.m_connectionFailedPanel.SetActive(true); } if ((Object)(object)startup.m_connectionFailedError != (Object)null) { Component component = ((Component)startup.m_connectionFailedError).GetComponent("Localize"); Behaviour val = (Behaviour)(object)((component is Behaviour) ? component : null); if ((Object)(object)val != (Object)null) { val.enabled = false; } startup.m_connectionFailedError.text = reason; Plugin.LogDebug("DisconnectReasonManager: Applied disconnect reason to FejdStartup: \"" + reason + "\""); } } private static IEnumerator ReinforceNoticeRoutine(FejdStartup startup, string reason) { yield return null; if ((Object)(object)startup != (Object)null && (Object)(object)startup.m_connectionFailedError != (Object)null) { ApplyTextAndShowPanel(startup, reason); Plugin.LogDebug("DisconnectReasonManager: Reinforced disconnect reason on next frame."); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] public static class ZNet_RPC_PeerInfo_Patch { [HarmonyPostfix] public static void Postfix(ZNet __instance) { Plugin.LogDebug("ZNet.RPC_PeerInfo postfix triggered."); if (__instance.IsServer()) { Plugin.LogDebug("ZNet.RPC_PeerInfo: Host is server; ignoring watchdog start."); } else { WatchdogManager.Start("ZNet.RPC_PeerInfo"); } } } [HarmonyPatch(typeof(Game), "Start")] public static class Game_Start_Patch { [HarmonyPostfix] public static void Postfix() { Plugin.LogDebug("Game.Start postfix triggered."); DisconnectReasonManager.Clear(); if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer()) { Plugin.LogDebug("Game.Start: Local world or server host detected; ignoring watchdog start."); } else { WatchdogManager.Start("Game.Start"); } } } [HarmonyPatch(typeof(Game), "OnDestroy")] public static class Game_OnDestroy_Patch { [HarmonyPostfix] public static void Postfix() { Plugin.LogDebug("Game.OnDestroy postfix triggered."); WatchdogManager.Stop("Game.OnDestroy"); } } [HarmonyPatch(typeof(ZNet), "Shutdown")] public static class ZNet_Shutdown_Patch { [HarmonyPostfix] public static void Postfix() { Plugin.LogDebug("ZNet.Shutdown postfix triggered."); WatchdogManager.Stop("ZNet.Shutdown"); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] public static class FejdStartup_ShowConnectError_Patch { [HarmonyPostfix] public static void Postfix(FejdStartup __instance) { Plugin.LogDebug("FejdStartup.ShowConnectError postfix triggered."); WatchdogManager.Stop("FejdStartup.ShowConnectError"); DisconnectReasonManager.ApplyToMenu(__instance); } } [HarmonyPatch(typeof(FejdStartup), "Start")] public static class FejdStartup_Start_Patch { [HarmonyPostfix] public static void Postfix(FejdStartup __instance) { Plugin.LogDebug("FejdStartup.Start postfix triggered."); WatchdogManager.Stop("FejdStartup.Start"); DisconnectReasonManager.ApplyToMenu(__instance); } } [HarmonyPatch(typeof(FejdStartup), "OnConnectionFailedOk")] public static class FejdStartup_OnConnectionFailedOk_Patch { [HarmonyPostfix] public static void Postfix(FejdStartup __instance) { Plugin.LogDebug("FejdStartup.OnConnectionFailedOk postfix triggered."); if ((Object)(object)__instance.m_connectionFailedError != (Object)null) { Component component = ((Component)__instance.m_connectionFailedError).GetComponent("Localize"); Behaviour val = (Behaviour)(object)((component is Behaviour) ? component : null); if ((Object)(object)val != (Object)null) { val.enabled = true; } } DisconnectReasonManager.Clear(); } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] public static class Terminal_InitTerminal_Patch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; internal void <Postfix>b__0_0(ConsoleEventArgs args) { Plugin.SimulateSeveredPeer(); } } [HarmonyPostfix] public static void Postfix() { //IL_0045: 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) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown if (Terminal.commands.ContainsKey("cgw_simulatesever")) { return; } object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate { Plugin.SimulateSeveredPeer(); }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("cgw_simulatesever", "Simulates an abrupt external peer severance (ServerCharacters behavior) for watchdog testing.", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } internal static class VersionInfo { public const string Version = "1.1.0"; } }