using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Alpha;
using Alpha.Core.Command;
using Alpha.Core.Util;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PurrNet;
using PurrNet.Transports;
using Reconnect.Core;
using Reconnect.Core.Commands;
using Reconnect.Patches;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("AndrewLin")]
[assembly: AssemblyConfiguration("Publish")]
[assembly: AssemblyDescription("Reconnect: Auto-reconnect to lobby after unexpected disconnection.")]
[assembly: AssemblyFileVersion("0.0.3.0")]
[assembly: AssemblyInformationalVersion("0.0.3+c44534b8174c63ca731fad68ab6e521c84d1b689")]
[assembly: AssemblyProduct("AndrewLin.Reconnect")]
[assembly: AssemblyTitle("AndrewLin.Reconnect")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/andrewlimforfun/ot-mods")]
[assembly: AssemblyVersion("0.0.3.0")]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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]
[Microsoft.CodeAnalysis.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 Reconnect
{
public static class BuildInfo
{
public const string Version = "0.0.3";
}
[BepInPlugin("com.andrewlin.ontogether.reconnect", "Reconnect", "0.0.3")]
public class ReconnectPlugin : BaseUnityPlugin
{
[CompilerGenerated]
private sealed class <ReconnectCoroutine>d__36 : IEnumerator<object>, IEnumerator, IDisposable
{
private int <>1__state;
private object <>2__current;
private ConnectionState <state>5__1;
private float <waited>5__2;
private float <timeout>5__3;
private Exception <ex>5__4;
private ConnectionState <current>5__5;
object? IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object? IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ReconnectCoroutine>d__36(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<ex>5__4 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
//IL_01d9: Invalid comparison between Unknown and I4
//IL_00a5: 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_00b0: Unknown result type (might be due to invalid IL or missing references)
//IL_00b6: Invalid comparison between Unknown and I4
//IL_0251: Unknown result type (might be due to invalid IL or missing references)
//IL_0257: Invalid comparison between Unknown and I4
int num = <>1__state;
if (num != 0)
{
if (num != 1)
{
return false;
}
<>1__state = -1;
<waited>5__2 += Time.unscaledDeltaTime;
<current>5__5 = NetworkManager.main.clientState;
if ((int)<current>5__5 == 1)
{
ChatUtils.AddGlobalNotification($"Reconnected successfully on attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}!");
Log.LogInfo((object)$"Reconnected successfully on attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}!");
ReconnectManager.OnConnected();
return false;
}
if ((int)<current>5__5 != 2 || !(<waited>5__2 > 2f))
{
goto IL_0272;
}
}
else
{
<>1__state = -1;
if (!ReconnectManager.TryBeginSequence(Time.unscaledTime))
{
Log.LogWarning((object)$"Reconnect cooldown active ({ReconnectManager.CooldownSec}s). Allowing normal disconnect flow.");
FallbackToMenu();
return false;
}
Log.LogInfo((object)$"Starting reconnect sequence. Max attempts: {ReconnectManager.MaxAttempts}, interval: {ReconnectManager.AttemptIntervalSec}s");
}
goto IL_028a;
IL_0272:
if (<waited>5__2 < <timeout>5__3)
{
<>2__current = null;
<>1__state = 1;
return true;
}
goto IL_028a;
IL_028a:
if (ReconnectManager.TryNextAttempt())
{
<state>5__1 = NetworkManager.main.clientState;
if ((int)<state>5__1 == 1)
{
Log.LogInfo((object)"Already connected - reconnect succeeded (or wasn't needed).");
ReconnectManager.OnConnected();
return false;
}
ChatUtils.AddGlobalNotification($"Reconnect attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}...");
Log.LogInfo((object)$"Reconnect attempt {ReconnectManager.CurrentAttempt}/{ReconnectManager.MaxAttempts}...");
try
{
NetworkManager.main.StartClient();
}
catch (Exception ex)
{
<ex>5__4 = ex;
Log.LogError((object)("StartClient() threw: " + <ex>5__4.Message));
goto IL_029d;
}
<waited>5__2 = 0f;
<timeout>5__3 = ReconnectManager.AttemptIntervalSec;
goto IL_0272;
}
goto IL_029d;
IL_029d:
Log.LogWarning((object)"Reconnect failed after all attempts. Returning to menu.");
ReconnectManager.OnFailed();
FallbackToMenu();
return false;
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
public const string ModGUID = "com.andrewlin.ontogether.reconnect";
public const string ModName = "Reconnect";
public const string ModVersion = "0.0.3";
internal static ManualLogSource Log = null;
private static ReconnectPlugin? _instance;
public static ConfigEntry<bool>? Enabled { get; private set; }
public static ConfigEntry<int>? MaxAttempts { get; private set; }
public static ConfigEntry<float>? AttemptIntervalSec { get; private set; }
public static ConfigEntry<float>? CooldownSec { get; private set; }
internal static ReconnectManager ReconnectManager { get; private set; } = new ReconnectManager(() => MaxAttempts?.Value ?? 10, () => AttemptIntervalSec?.Value ?? 5f, () => CooldownSec?.Value ?? 30f);
public static bool IsIntentionalLeave
{
get
{
return ReconnectManager.IsIntentionalLeave;
}
set
{
ReconnectManager.IsIntentionalLeave = value;
}
}
public static bool IsReconnecting => ReconnectManager.IsReconnecting;
public static string? SavedLobbyId
{
get
{
return ReconnectManager.SavedLobbyId;
}
set
{
ReconnectManager.SavedLobbyId = value;
}
}
private void Awake()
{
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Expected O, but got Unknown
_instance = this;
Log = ((BaseUnityPlugin)this).Logger;
((BaseUnityPlugin)this).Logger.LogInfo((object)"Reconnect v0.0.3 is loaded!");
InitConfig();
Harmony val = new Harmony("com.andrewlin.ontogether.reconnect");
val.PatchAll(typeof(MainSceneManagerPatch));
val.PatchAll(typeof(MultiplayerManagerPatch));
ChatCommandManager commandManager = AlphaPlugin.CommandManager;
if (commandManager != null)
{
commandManager.Register((IChatCommand)(object)new ReconnectToggleCommand());
}
ChatCommandManager commandManager2 = AlphaPlugin.CommandManager;
if (commandManager2 != null)
{
commandManager2.Register((IChatCommand)(object)new ReconnectMaxAttemptsCommand());
}
ChatCommandManager commandManager3 = AlphaPlugin.CommandManager;
if (commandManager3 != null)
{
commandManager3.Register((IChatCommand)(object)new ReconnectIntervalCommand());
}
ChatCommandManager commandManager4 = AlphaPlugin.CommandManager;
if (commandManager4 != null)
{
commandManager4.Register((IChatCommand)(object)new ReconnectCooldownCommand());
}
ChatCommandManager commandManager5 = AlphaPlugin.CommandManager;
if (commandManager5 != null)
{
commandManager5.Register((IChatCommand)(object)new ReconnectSimulateCommand());
}
}
private void InitConfig()
{
Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable auto-reconnect on unexpected disconnection.");
MaxAttempts = ((BaseUnityPlugin)this).Config.Bind<int>("General", "MaxAttempts", 100, "Maximum reconnect attempts before giving up (1-100).");
AttemptIntervalSec = ((BaseUnityPlugin)this).Config.Bind<float>("General", "AttemptIntervalSec", 5f, "Seconds between reconnect attempts.");
CooldownSec = ((BaseUnityPlugin)this).Config.Bind<float>("General", "CooldownSec", 30f, "Minimum seconds between reconnect sequences to prevent rapid-fire loops.");
}
internal static Coroutine? StartReconnectCoroutine()
{
if ((Object)(object)_instance == (Object)null)
{
return null;
}
return ((MonoBehaviour)_instance).StartCoroutine(ReconnectCoroutine());
}
[IteratorStateMachine(typeof(<ReconnectCoroutine>d__36))]
private static IEnumerator ReconnectCoroutine()
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ReconnectCoroutine>d__36(0);
}
private static void FallbackToMenu()
{
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
try
{
MainSceneManager i = MonoSingleton<MainSceneManager>.I;
if (!((Object)(object)i == (Object)null))
{
AccessTools.Field(typeof(MainSceneManager), "_returnMenuStarted")?.SetValue(i, false);
MultiplayerManager i2 = MonoSingleton<MultiplayerManager>.I;
if ((Object)(object)i2 != (Object)null)
{
i2._notificationState = (NotificationStatus)3;
}
i.ReturnMenu(false);
}
}
catch (Exception arg)
{
Log.LogError((object)$"FallbackToMenu failed: {arg}");
}
}
private void OnDestroy()
{
_instance = null;
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "AndrewLin.Reconnect";
public const string PLUGIN_NAME = "AndrewLin.Reconnect";
public const string PLUGIN_VERSION = "0.0.3";
}
}
namespace Reconnect.Patches
{
[HarmonyPatch(typeof(MainSceneManager))]
public static class MainSceneManagerPatch
{
private static ManualLogSource _log = Logger.CreateLogSource("Reconnect.MainSceneManagerPatch");
[HarmonyPatch("ConnectionLost")]
[HarmonyPrefix]
private static bool ConnectionLost_Prefix(NotificationStatus notificationStatus)
{
//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
ConfigEntry<bool>? enabled = ReconnectPlugin.Enabled;
if (enabled == null || !enabled.Value)
{
return true;
}
if (ReconnectPlugin.IsIntentionalLeave)
{
_log.LogInfo((object)"Intentional leave - allowing normal disconnect flow.");
ReconnectPlugin.IsIntentionalLeave = false;
return true;
}
if (ReconnectPlugin.IsReconnecting)
{
_log.LogInfo((object)"Already reconnecting - suppressing duplicate ConnectionLost.");
return false;
}
if (NetworkManager.main.isHost)
{
_log.LogInfo((object)"Host disconnected - cannot self-reconnect, allowing normal flow.");
return true;
}
try
{
MultiplayerManager i = MonoSingleton<MultiplayerManager>.I;
if ((Object)(object)i != (Object)null && i.LobbyStatus)
{
ReconnectPlugin.SavedLobbyId = i.LobbyCode;
}
}
catch
{
}
_log.LogInfo((object)$"Unexpected disconnect (status={notificationStatus}). Attempting reconnect...");
ChatUtils.AddGlobalNotification("Connection lost - attempting to reconnect...");
ReconnectPlugin.StartReconnectCoroutine();
return false;
}
}
[HarmonyPatch(typeof(MultiplayerManager))]
public static class MultiplayerManagerPatch
{
private static ManualLogSource Logger = Logger.CreateLogSource("Reconnect.MultiplayerManagerPatch");
[HarmonyPatch("QuitSession")]
[HarmonyPrefix]
private static void QuitSession_Prefix()
{
Logger.LogDebug((object)"QuitSession called - marking as intentional leave.");
ReconnectPlugin.IsIntentionalLeave = true;
}
}
[HarmonyPatch(typeof(MainSceneManager))]
public static class MainSceneManagerQuitPatch
{
private static ManualLogSource _log = Logger.CreateLogSource("Reconnect.MainSceneManagerQuitPatch");
[HarmonyPatch("ButtonQuit")]
[HarmonyPrefix]
private static void ButtonQuit_Prefix()
{
_log.LogInfo((object)"ButtonQuit called - marking as intentional leave.");
ReconnectPlugin.IsIntentionalLeave = true;
}
}
}
namespace Reconnect.Core
{
public class ReconnectManager
{
private float _lastSequenceTime;
private int _currentAttempt;
private readonly Func<int> _maxAttempts;
private readonly Func<float> _attemptIntervalSec;
private readonly Func<float> _cooldownSec;
public int MaxAttempts => _maxAttempts();
public float AttemptIntervalSec => _attemptIntervalSec();
public float CooldownSec => _cooldownSec();
public bool IsIntentionalLeave { get; set; }
public bool IsReconnecting { get; private set; }
public string? SavedLobbyId { get; set; }
public int CurrentAttempt => _currentAttempt;
public ReconnectManager(Func<int> maxAttempts, Func<float> attemptIntervalSec, Func<float> cooldownSec)
{
_maxAttempts = maxAttempts;
_attemptIntervalSec = attemptIntervalSec;
_cooldownSec = cooldownSec;
}
public bool TryBeginSequence(float currentTime)
{
if (_lastSequenceTime > 0f && currentTime - _lastSequenceTime < CooldownSec)
{
return false;
}
_lastSequenceTime = currentTime;
_currentAttempt = 0;
IsReconnecting = true;
return true;
}
public bool TryNextAttempt()
{
if (IsIntentionalLeave)
{
IsReconnecting = false;
return false;
}
if (_currentAttempt >= MaxAttempts)
{
IsReconnecting = false;
return false;
}
_currentAttempt++;
return true;
}
public void OnConnected()
{
IsReconnecting = false;
IsIntentionalLeave = false;
}
public void OnFailed()
{
IsReconnecting = false;
}
}
}
namespace Reconnect.Core.Commands
{
public class ReconnectCooldownCommand : IChatCommand, IComparable<IChatCommand>
{
public string Name => "reconnectcooldown";
public string ShortName => "rccd";
public string Description => "Get or set cooldown between sequences in seconds (10-120). Usage: /rccd [value]";
public string Namespace => "reconnect";
public void Execute(string[] args)
{
if (ReconnectPlugin.CooldownSec == null)
{
return;
}
if (args.Length >= 1)
{
if (!float.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 10f || result > 120f)
{
ChatUtils.AddGlobalNotification("Usage: /rccd [10-120]");
return;
}
ReconnectPlugin.CooldownSec.Value = result;
}
ChatUtils.AddGlobalNotification($"CooldownSec = {ReconnectPlugin.CooldownSec.Value}");
}
}
public class ReconnectIntervalCommand : IChatCommand, IComparable<IChatCommand>
{
public string Name => "reconnectinterval";
public string ShortName => "rciv";
public string Description => "Get or set seconds between attempts (2-30). Usage: /rciv [value]";
public string Namespace => "reconnect";
public void Execute(string[] args)
{
if (ReconnectPlugin.AttemptIntervalSec == null)
{
return;
}
if (args.Length >= 1)
{
if (!float.TryParse(args[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 2f || result > 30f)
{
ChatUtils.AddGlobalNotification("Usage: /rciv [2-30]");
return;
}
ReconnectPlugin.AttemptIntervalSec.Value = result;
}
ChatUtils.AddGlobalNotification($"AttemptIntervalSec = {ReconnectPlugin.AttemptIntervalSec.Value}");
}
}
public class ReconnectMaxAttemptsCommand : IChatCommand, IComparable<IChatCommand>
{
public string Name => "reconnectmaxattempts";
public string ShortName => "rcma";
public string Description => "Get or set max reconnect attempts (1-10). Usage: /rcma [value]";
public string Namespace => "reconnect";
public void Execute(string[] args)
{
if (ReconnectPlugin.MaxAttempts == null)
{
return;
}
if (args.Length >= 1)
{
if (!int.TryParse(args[0], out var result) || result < 1 || result > 100)
{
ChatUtils.AddGlobalNotification("Usage: /rcma [1-100]");
return;
}
ReconnectPlugin.MaxAttempts.Value = result;
}
ChatUtils.AddGlobalNotification($"MaxAttempts = {ReconnectPlugin.MaxAttempts.Value}");
}
}
public class ReconnectSimulateCommand : IChatCommand, IComparable<IChatCommand>
{
public string Name => "reconnectsimulate";
public string ShortName => "rcs";
public string Description => "Simulate an unintentional disconnect to test the reconnect sequence.";
public string Namespace => "reconnect";
public void Execute(string[] args)
{
ConfigEntry<bool>? enabled = ReconnectPlugin.Enabled;
if (enabled == null || !enabled.Value)
{
ChatUtils.AddGlobalNotification("Reconnect is disabled. Enable it first with /rct on");
return;
}
if (ReconnectPlugin.IsReconnecting)
{
ChatUtils.AddGlobalNotification("Already reconnecting.");
return;
}
if (NetworkManager.main.isHost)
{
ChatUtils.AddGlobalNotification("Cannot test reconnect as host.");
return;
}
ChatUtils.AddGlobalNotification("Simulating unintentional disconnect...");
ReconnectPlugin.IsIntentionalLeave = false;
NetworkManager.main.StopClient();
}
}
public class ReconnectToggleCommand : IChatCommand, IComparable<IChatCommand>
{
public string Name => "reconnecttoggle";
public string ShortName => "rct";
public string Description => "Get or set auto-reconnect on/off. Usage: /rct [on|off]";
public string Namespace => "reconnect";
public void Execute(string[] args)
{
if (ReconnectPlugin.Enabled == null)
{
return;
}
if (args.Length >= 1)
{
string text = args[0].ToLowerInvariant();
if (text == "on" || text == "true" || text == "1")
{
ReconnectPlugin.Enabled.Value = true;
}
else
{
if (!(text == "off") && !(text == "false") && !(text == "0"))
{
ChatUtils.AddGlobalNotification("Usage: /rct [on|off]");
return;
}
ReconnectPlugin.Enabled.Value = false;
}
}
else
{
ReconnectPlugin.Enabled.Value = !ReconnectPlugin.Enabled.Value;
}
ChatUtils.AddGlobalNotification("Reconnect is now " + (ReconnectPlugin.Enabled.Value ? "enabled" : "disabled") + ".");
}
}
}