Decompiled source of DriftwoodConnect v0.1.4

DriftwoodConnect.dll

Decompiled 11 hours ago
using System;
using System.Collections;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using FishNet;
using FishNet.Managing;
using FishNet.Managing.Client;
using FishNet.Object;
using FishNet.Transporting;
using FishNet.Transporting.Multipass;
using FishNet.Transporting.UTP;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("HumanGenome")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 HumanGenome")]
[assembly: AssemblyFileVersion("0.1.4.0")]
[assembly: AssemblyInformationalVersion("0.1.4+530ef4b059826b59e2f02a1c2d8fc8749661580b")]
[assembly: AssemblyProduct("Driftwood")]
[assembly: AssemblyTitle("DriftwoodConnect")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/HumanGenome/Driftwood")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyVersion("0.1.4.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 Driftwood.Connect
{
	internal static class AddressParser
	{
		internal static bool TryParse(string text, out string host, out int port)
		{
			host = null;
			port = 0;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			text = text.Trim();
			if (text.Length == 0 || text.Length > 260)
			{
				return false;
			}
			bool flag = true;
			string text2 = text;
			foreach (char c in text2)
			{
				if (char.IsWhiteSpace(c))
				{
					return false;
				}
				if (c < '0' || c > '9')
				{
					flag = false;
				}
			}
			if (flag)
			{
				return false;
			}
			string text3 = null;
			string text4;
			if (text[0] == '[')
			{
				int num = text.IndexOf(']');
				if (num <= 1)
				{
					return false;
				}
				text4 = text.Substring(1, num - 1);
				if (text4.IndexOf(':') < 0)
				{
					return false;
				}
				string text5 = text.Substring(num + 1);
				if (text5.Length > 0)
				{
					if (text5[0] != ':' || text5.Length < 2)
					{
						return false;
					}
					text3 = text5.Substring(1);
				}
			}
			else
			{
				int num2 = text.LastIndexOf(':');
				if (num2 >= 0 && text.IndexOf(':') != num2)
				{
					return false;
				}
				if (num2 >= 0)
				{
					text4 = text.Substring(0, num2);
					text3 = text.Substring(num2 + 1);
				}
				else
				{
					text4 = text;
				}
				if (text4.Length == 0 || text4[0] == '.' || text4[0] == '-')
				{
					return false;
				}
				bool flag2 = false;
				text2 = text4;
				foreach (char c2 in text2)
				{
					if ((c2 < 'a' || c2 > 'z') && (c2 < 'A' || c2 > 'Z') && (c2 < '0' || c2 > '9') && c2 != '.' && c2 != '-')
					{
						return false;
					}
					switch (c2)
					{
					default:
						if (c2 < 'A' || c2 > 'Z')
						{
							continue;
						}
						break;
					case '.':
					case 'a':
					case 'b':
					case 'c':
					case 'd':
					case 'e':
					case 'f':
					case 'g':
					case 'h':
					case 'i':
					case 'j':
					case 'k':
					case 'l':
					case 'm':
					case 'n':
					case 'o':
					case 'p':
					case 'q':
					case 'r':
					case 's':
					case 't':
					case 'u':
					case 'v':
					case 'w':
					case 'x':
					case 'y':
					case 'z':
						break;
					}
					flag2 = true;
				}
				if (!flag2)
				{
					return false;
				}
			}
			if (text3 != null)
			{
				if (!int.TryParse(text3, NumberStyles.None, CultureInfo.InvariantCulture, out var result))
				{
					return false;
				}
				if (result < 1 || result > 65535)
				{
					return false;
				}
				port = result;
			}
			host = text4;
			return true;
		}

		internal static string LabelFor(string host, int port)
		{
			if (port <= 0)
			{
				return host;
			}
			return host + ":" + port;
		}
	}
	internal static class ClaimRules
	{
		internal const string Route = "/api/v1/identity";

		internal const ulong FirstRealSteamId = 76561197960265729uL;

		internal const ulong LastRealSteamId = 76561202255233023uL;

		internal const int MaxNameLength = 64;

		internal static bool IsClaimableSteamId(ulong steamId)
		{
			if (steamId >= 76561197960265729L)
			{
				return steamId <= 76561202255233023L;
			}
			return false;
		}

		internal static int StatusPortFor(int gamePort)
		{
			if (gamePort <= 0 || gamePort >= 65535)
			{
				return 0;
			}
			return gamePort + 1;
		}

		internal static string BuildBody(int clientId, ulong steamId, string name)
		{
			StringBuilder stringBuilder = new StringBuilder(128);
			stringBuilder.Append("{\"clientId\":\"").Append(clientId.ToString(CultureInfo.InvariantCulture)).Append('"');
			stringBuilder.Append(",\"steamId\":\"").Append(steamId.ToString(CultureInfo.InvariantCulture)).Append('"');
			string text = CleanName(name);
			if (text != null)
			{
				stringBuilder.Append(",\"name\":\"").Append(EscapeJson(text)).Append('"');
			}
			stringBuilder.Append('}');
			return stringBuilder.ToString();
		}

		internal static string BuildRequest(string host, int statusPort, string body)
		{
			int byteCount = Encoding.UTF8.GetByteCount(body);
			string text = ((host.IndexOf(':') >= 0) ? ("[" + host + "]") : host);
			return "POST /api/v1/identity HTTP/1.1\r\nHost: " + text + ":" + statusPort.ToString(CultureInfo.InvariantCulture) + "\r\nContent-Type: application/json\r\nContent-Length: " + byteCount.ToString(CultureInfo.InvariantCulture) + "\r\nConnection: close\r\n\r\n" + body;
		}

		internal static bool IsOkStatusLine(string line)
		{
			if (string.IsNullOrEmpty(line) || !line.StartsWith("HTTP/1.", StringComparison.Ordinal))
			{
				return false;
			}
			string[] array = line.Split(' ');
			if (array.Length >= 2)
			{
				return array[1] == "200";
			}
			return false;
		}

		internal static string CleanName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			StringBuilder stringBuilder = new StringBuilder(Math.Min(name.Length, 64));
			foreach (char c in name)
			{
				if (!char.IsControl(c))
				{
					stringBuilder.Append(c);
					if (stringBuilder.Length >= 64)
					{
						break;
					}
				}
			}
			string text = stringBuilder.ToString().Trim();
			if (text.Length != 0)
			{
				return text;
			}
			return null;
		}

		internal static string EscapeJson(string text)
		{
			StringBuilder stringBuilder = new StringBuilder(text.Length + 8);
			foreach (char c in text)
			{
				if (c != '"')
				{
					if (c == '\\')
					{
						stringBuilder.Append("\\\\");
					}
					else if (c < ' ' || c == '\u2028' || c == '\u2029')
					{
						StringBuilder stringBuilder2 = stringBuilder.Append("\\u");
						int num = c;
						stringBuilder2.Append(num.ToString("x4", CultureInfo.InvariantCulture));
					}
					else
					{
						stringBuilder.Append(c);
					}
				}
				else
				{
					stringBuilder.Append("\\\"");
				}
			}
			return stringBuilder.ToString();
		}
	}
	internal static class ConnectPatch
	{
		private static FieldInfo _fMultipass;

		private static FieldInfo _fClientConnectionExpected;

		private static FieldInfo _fReturnToMenuRequested;

		internal static bool Applied { get; private set; }

		internal static bool Apply(Harmony harmony)
		{
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(ConnectionManager), "JoinOfflineLobby", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				DriftwoodConnectPlugin.L.LogError((object)"ConnectionManager.JoinOfflineLobby not found. The game build has moved.");
				return false;
			}
			_fMultipass = AccessTools.Field(typeof(ConnectionManager), "_multipass");
			_fClientConnectionExpected = AccessTools.Field(typeof(ConnectionManager), "_clientConnectionExpected");
			_fReturnToMenuRequested = AccessTools.Field(typeof(ConnectionManager), "_returnToMenuRequested");
			if (_fMultipass == null || _fClientConnectionExpected == null || _fReturnToMenuRequested == null)
			{
				DriftwoodConnectPlugin.L.LogError((object)("ConnectionManager fields moved: _multipass=" + (_fMultipass != null) + " _clientConnectionExpected=" + (_fClientConnectionExpected != null) + " _returnToMenuRequested=" + (_fReturnToMenuRequested != null)));
				return false;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(ConnectPatch), "JoinOfflineLobbyPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Applied = true;
			return true;
		}

		private static bool ResolveTarget(out string host, out int port, out string label)
		{
			if (MenuJoin.TryTakePending(out host, out port, out label))
			{
				return true;
			}
			if (DriftwoodConnectPlugin.SessionArmed)
			{
				host = DriftwoodConnectPlugin.ConnectAddress.Value;
				if (!string.IsNullOrEmpty(host))
				{
					port = DriftwoodConnectPlugin.ConnectPort.Value;
					label = DriftwoodConnectPlugin.Label();
					return true;
				}
				DriftwoodConnectPlugin.L.LogWarning((object)"No ConnectAddress configured; leaving JoinOfflineLobby alone.");
			}
			host = null;
			port = 0;
			label = null;
			return false;
		}

		private static bool JoinOfflineLobbyPrefix(ConnectionManager __instance)
		{
			if (!ResolveTarget(out var host, out var port, out var label))
			{
				IdentityClaim.Disarm();
				return true;
			}
			try
			{
				object? value = _fMultipass.GetValue(__instance);
				Multipass val = (Multipass)((value is Multipass) ? value : null);
				if ((Object)(object)val == (Object)null)
				{
					DriftwoodConnectPlugin.L.LogError((object)"Multipass was null on ConnectionManager; falling back to the stock method.");
					return true;
				}
				ConnectionManager.IsUsingSteam = false;
				val.SetClientTransport<UnityTransport>();
				Transport clientTransport = val.ClientTransport;
				if ((Object)(object)clientTransport == (Object)null)
				{
					DriftwoodConnectPlugin.L.LogError((object)"Multipass returned no client transport after selecting UnityTransport.");
					JoinStatus.Fail("Could not select the network transport.");
					return false;
				}
				if (port > 0 && port <= 65535)
				{
					clientTransport.SetPort((ushort)port);
				}
				clientTransport.SetClientAddress(host);
				_fClientConnectionExpected.SetValue(__instance, true);
				bool flag = clientTransport.StartConnection(false);
				DriftwoodConnectPlugin.L.LogInfo((object)("Dialling " + label + " -> StartConnection returned " + flag + "."));
				if (flag)
				{
					IdentityClaim.Arm(host, clientTransport.GetPort());
				}
				else
				{
					IdentityClaim.Disarm();
				}
				if (!flag)
				{
					_fReturnToMenuRequested.SetValue(__instance, true);
					JoinStatus.Fail("Couldn't reach " + label + ".");
				}
				MainMenuManager.CrashAnimation();
				return false;
			}
			catch (Exception ex)
			{
				DriftwoodConnectPlugin.L.LogError((object)("Joining " + label + " threw (" + ex?.ToString() + ")."));
				JoinStatus.Fail("Could not start the connection.");
				try
				{
					_fReturnToMenuRequested.SetValue(__instance, true);
				}
				catch
				{
				}
				return false;
			}
		}
	}
	internal static class IdentityClaim
	{
		private sealed class ClaimJob
		{
			private readonly string _host;

			private readonly int _port;

			private readonly string _request;

			internal volatile bool Done;

			internal bool Ok;

			internal string Failure;

			internal ClaimJob(string host, int port, string request)
			{
				_host = host;
				_port = port;
				_request = request;
			}

			internal void Run()
			{
				try
				{
					IPAddress iPAddress = Resolve(_host);
					if (iPAddress == null)
					{
						Failure = "could not resolve " + _host;
						return;
					}
					using TcpClient tcpClient = new TcpClient(iPAddress.AddressFamily);
					IAsyncResult asyncResult = tcpClient.BeginConnect(iPAddress, _port, null, null);
					if (!asyncResult.AsyncWaitHandle.WaitOne(2500))
					{
						Failure = "the status port did not answer";
						return;
					}
					tcpClient.EndConnect(asyncResult);
					tcpClient.SendTimeout = 2500;
					tcpClient.ReceiveTimeout = 2500;
					NetworkStream stream = tcpClient.GetStream();
					byte[] bytes = Encoding.UTF8.GetBytes(_request);
					stream.Write(bytes, 0, bytes.Length);
					stream.Flush();
					string text = ReadLine(stream, 256);
					Ok = ClaimRules.IsOkStatusLine(text);
					if (!Ok)
					{
						Failure = "the server answered \"" + text + "\"";
					}
				}
				catch (Exception ex)
				{
					Failure = ex.GetType().Name + ": " + ex.Message;
				}
				finally
				{
					Done = true;
				}
			}

			private static IPAddress Resolve(string host)
			{
				if (IPAddress.TryParse(host, out IPAddress address))
				{
					return address;
				}
				IPAddress[] hostAddresses = Dns.GetHostAddresses(host);
				IPAddress[] array = hostAddresses;
				foreach (IPAddress iPAddress in array)
				{
					if (iPAddress.AddressFamily == AddressFamily.InterNetwork)
					{
						return iPAddress;
					}
				}
				if (hostAddresses.Length == 0)
				{
					return null;
				}
				return hostAddresses[0];
			}

			private static string ReadLine(NetworkStream stream, int max)
			{
				StringBuilder stringBuilder = new StringBuilder();
				int num;
				while (stringBuilder.Length < max && (num = stream.ReadByte()) >= 0)
				{
					switch (num)
					{
					default:
						stringBuilder.Append((char)num);
						continue;
					case 13:
						continue;
					case 10:
						break;
					}
					break;
				}
				return stringBuilder.ToString();
			}
		}

		private static MethodInfo _sendSpawnPlayer;

		private static MethodInfo _setLocalClient;

		private static string _host;

		private static int _gamePort;

		private const float ClaimBudgetSeconds = 6f;

		private const int AttemptTimeoutMs = 2500;

		internal static bool Applied { get; private set; }

		internal static bool Apply(Harmony harmony)
		{
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(Client), "InitializeLocal", (Type[])null, (Type[])null);
			_sendSpawnPlayer = AccessTools.Method(typeof(Client), "SendSpawnPlayer", (Type[])null, (Type[])null);
			_setLocalClient = AccessTools.PropertySetter(typeof(Client), "LocalClient");
			if (methodInfo == null || _sendSpawnPlayer == null || _setLocalClient == null || !typeof(IEnumerator).IsAssignableFrom(_sendSpawnPlayer.ReturnType))
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("IdentityClaim: the game's spawn path moved (initializeLocal=" + (methodInfo != null) + " sendSpawnPlayer=" + (_sendSpawnPlayer != null) + " localClientSetter=" + (_setLocalClient != null) + "). Joins still work; the server will key characters on the join slot."));
				return false;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(AccessTools.Method(typeof(IdentityClaim), "InitializeLocalPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Applied = true;
			return true;
		}

		internal static void Arm(string host, int gamePort)
		{
			_host = host;
			_gamePort = gamePort;
		}

		internal static void Disarm()
		{
			_host = null;
			_gamePort = 0;
		}

		private static bool InitializeLocalPrefix(Client __instance)
		{
			string host = _host;
			int gamePort = _gamePort;
			Disarm();
			if (string.IsNullOrEmpty(host))
			{
				return true;
			}
			if (ConnectionManager.IsUsingSteam)
			{
				return true;
			}
			try
			{
				NetworkManager networkManager = InstanceFinder.NetworkManager;
				if ((Object)(object)networkManager != (Object)null && networkManager.IsServerStarted)
				{
					return true;
				}
			}
			catch
			{
			}
			int num = ClaimRules.StatusPortFor(gamePort);
			if (num == 0)
			{
				return true;
			}
			if (!TryReadSteam(out var steamId, out var name))
			{
				return true;
			}
			int clientId;
			try
			{
				clientId = ((NetworkBehaviour)__instance).Owner.ClientId;
			}
			catch
			{
				return true;
			}
			if (clientId < 0)
			{
				return true;
			}
			IEnumerator enumerator;
			try
			{
				_setLocalClient.Invoke(null, new object[1] { __instance });
				enumerator = (IEnumerator)_sendSpawnPlayer.Invoke(__instance, null);
				if (enumerator == null)
				{
					return true;
				}
			}
			catch (Exception ex)
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("IdentityClaim: could not stage the spawn (" + ex.Message + "); joining without a claim."));
				return true;
			}
			((MonoBehaviour)__instance).StartCoroutine(ClaimThenSpawn(host, num, clientId, steamId, name, enumerator));
			return false;
		}

		private static bool TryReadSteam(out ulong steamId, out string name)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			steamId = 0uL;
			name = null;
			try
			{
				steamId = SteamUser.GetSteamID().m_SteamID;
			}
			catch (Exception ex)
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("IdentityClaim: Steam did not say who is signed in (" + ex.Message + "). Joining anyway; the server will key this character on the join slot."));
				return false;
			}
			if (!ClaimRules.IsClaimableSteamId(steamId))
			{
				DriftwoodConnectPlugin.L.LogWarning((object)"IdentityClaim: Steam reported an id that is not a player account; not sending it.");
				return false;
			}
			try
			{
				name = SteamFriends.GetPersonaName();
			}
			catch
			{
			}
			return true;
		}

		private static IEnumerator ClaimThenSpawn(string host, int statusPort, int clientId, ulong steamId, string name, IEnumerator spawn)
		{
			string request = ClaimRules.BuildRequest(host, statusPort, ClaimRules.BuildBody(clientId, steamId, name));
			float started = Time.realtimeSinceStartup;
			string failure = null;
			bool sent = false;
			for (int attempt = 1; attempt <= 2; attempt++)
			{
				if (sent)
				{
					break;
				}
				ClaimJob job = new ClaimJob(host, statusPort, request);
				ThreadPool.QueueUserWorkItem(delegate
				{
					job.Run();
				});
				while (!job.Done && Time.realtimeSinceStartup - started < 6f)
				{
					yield return null;
				}
				if (job.Done && job.Ok)
				{
					sent = true;
					break;
				}
				failure = (job.Done ? job.Failure : ("no answer within " + 6f + " s"));
				if (Time.realtimeSinceStartup - started >= 6f)
				{
					break;
				}
				yield return (object)new WaitForSecondsRealtime(0.5f);
			}
			float num = Time.realtimeSinceStartup - started;
			if (sent)
			{
				DriftwoodConnectPlugin.L.LogInfo((object)("IdentityClaim: told " + host + ":" + statusPort + " this player's Steam ID for connection " + clientId + " in " + Math.Round(num * 1000f) + " ms; the saved character follows the player."));
			}
			else
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("IdentityClaim: could not reach " + host + ":" + statusPort + " (" + failure + "). Joining anyway; on this join the server keys the character on the join slot."));
			}
			yield return spawn;
		}
	}
	internal static class JoinStatus
	{
		private sealed class Driver : MonoBehaviour
		{
			private static Driver _self;

			private float _clearAt = -1f;

			internal static Driver EnsureExists()
			{
				//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_0023: Expected O, but got Unknown
				//IL_0023: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)_self != (Object)null)
				{
					return _self;
				}
				GameObject val = new GameObject("Driftwood.JoinStatus");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
				_self = val.AddComponent<Driver>();
				return _self;
			}

			internal void HoldThenClear(float seconds)
			{
				_clearAt = Time.realtimeSinceStartup + seconds;
			}

			private void LateUpdate()
			{
				Pump();
				if (_clearAt > 0f && Time.realtimeSinceStartup >= _clearAt)
				{
					_clearAt = -1f;
					Clear();
				}
				else if (_message != null && _clearAt < 0f && WorldIsUp())
				{
					Clear();
				}
			}
		}

		private static TextMeshProUGUI _text;

		private static CanvasGroup _perma;

		private static bool _resolved;

		private static string _origText;

		private static float _origFontSize;

		private static Color _origColor;

		private static TextAlignmentOptions _origAlignment;

		private static Vector2 _origAnchorMin;

		private static Vector2 _origAnchorMax;

		private static Vector2 _origPivot;

		private static Vector2 _origAnchoredPos;

		private static Vector2 _origSizeDelta;

		private static bool _origEnabled;

		private static float _origPermaAlpha;

		private static TextWrappingModes _origWrapping;

		private static TextOverflowModes _origOverflow;

		private static bool _origAutoSize;

		private static Transform _origParent;

		private static int _origSiblingIndex;

		private static bool _reparented;

		private static GameObject[] _children;

		private static bool[] _childrenActive;

		private static ContentSizeFitter _fitter;

		private static bool _origFitterEnabled;

		private static bool _hostWarned;

		private static bool _saved;

		private static bool _broken;

		private static string _message;

		private static bool _watching;

		private static readonly Vector2 BandAnchorMin = new Vector2(0.1f, 0.16f);

		private static readonly Vector2 BandAnchorMax = new Vector2(0.9f, 0.16f);

		private const float BandHeight = 120f;

		private const float BandFontSize = 34f;

		internal static void Set(string message)
		{
			_message = message;
			Pump();
		}

		internal static void Fail(string message)
		{
			Set(message);
			Driver.EnsureExists().HoldThenClear(8f);
		}

		internal static void Clear()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			_message = null;
			if (!_saved || (Object)(object)_text == (Object)null)
			{
				return;
			}
			try
			{
				((TMP_Text)_text).text = _origText;
				((TMP_Text)_text).fontSize = _origFontSize;
				((TMP_Text)_text).enableAutoSizing = _origAutoSize;
				((Graphic)_text).color = _origColor;
				((TMP_Text)_text).alignment = _origAlignment;
				((TMP_Text)_text).textWrappingMode = _origWrapping;
				((TMP_Text)_text).overflowMode = _origOverflow;
				((Behaviour)_text).enabled = _origEnabled;
				RectTransform rectTransform = ((TMP_Text)_text).rectTransform;
				if (_reparented && (Object)(object)_origParent != (Object)null)
				{
					((Transform)rectTransform).SetParent(_origParent, false);
					((Transform)rectTransform).SetSiblingIndex(_origSiblingIndex);
				}
				_reparented = false;
				if ((Object)(object)_fitter != (Object)null)
				{
					((Behaviour)_fitter).enabled = _origFitterEnabled;
				}
				if (_children != null)
				{
					for (int i = 0; i < _children.Length; i++)
					{
						if ((Object)(object)_children[i] != (Object)null)
						{
							_children[i].SetActive(_childrenActive[i]);
						}
					}
				}
				_children = null;
				rectTransform.anchorMin = _origAnchorMin;
				rectTransform.anchorMax = _origAnchorMax;
				rectTransform.pivot = _origPivot;
				rectTransform.anchoredPosition = _origAnchoredPos;
				rectTransform.sizeDelta = _origSizeDelta;
				if ((Object)(object)_perma != (Object)null)
				{
					_perma.alpha = _origPermaAlpha;
				}
			}
			catch (Exception e)
			{
				Break("restoring the version stamp", e);
			}
			_saved = false;
		}

		internal static void Watch()
		{
			if (!_watching)
			{
				_watching = true;
				Driver.EnsureExists();
				ClientManager clientManager = InstanceFinder.ClientManager;
				if ((Object)(object)clientManager != (Object)null)
				{
					clientManager.OnClientConnectionState += OnClientConnectionState;
				}
			}
		}

		private static void OnClientConnectionState(ClientConnectionStateArgs args)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			if ((int)args.ConnectionState == 8)
			{
				Set("Connected. Loading the island...");
			}
			else if ((int)args.ConnectionState == 1 && _message != null)
			{
				Fail("Lost the connection to " + DriftwoodConnectPlugin.Label() + ".");
			}
		}

		private static bool WorldIsUp()
		{
			try
			{
				return (Object)(object)Island.CurIsland != (Object)null && !IslandManager.IsLoading && (Object)(object)Player.LocalPlayer != (Object)null;
			}
			catch
			{
				return false;
			}
		}

		private static void Pump()
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0267: Unknown result type (might be due to invalid IL or missing references)
			//IL_027c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_029c: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e8: Invalid comparison between Unknown and I4
			//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0320: Unknown result type (might be due to invalid IL or missing references)
			if (_message == null || _broken || !Resolve())
			{
				return;
			}
			try
			{
				RectTransform rectTransform = ((TMP_Text)_text).rectTransform;
				if (!_saved)
				{
					_origText = ((TMP_Text)_text).text;
					_origFontSize = ((TMP_Text)_text).fontSize;
					_origAutoSize = ((TMP_Text)_text).enableAutoSizing;
					_origColor = ((Graphic)_text).color;
					_origAlignment = ((TMP_Text)_text).alignment;
					_origWrapping = ((TMP_Text)_text).textWrappingMode;
					_origOverflow = ((TMP_Text)_text).overflowMode;
					_origEnabled = ((Behaviour)_text).enabled;
					_origParent = ((Transform)rectTransform).parent;
					_origSiblingIndex = ((Transform)rectTransform).GetSiblingIndex();
					_origAnchorMin = rectTransform.anchorMin;
					_origAnchorMax = rectTransform.anchorMax;
					_origPivot = rectTransform.pivot;
					_origAnchoredPos = rectTransform.anchoredPosition;
					_origSizeDelta = rectTransform.sizeDelta;
					_origPermaAlpha = (((Object)(object)_perma != (Object)null) ? _perma.alpha : 1f);
					_fitter = ((Component)_text).GetComponent<ContentSizeFitter>();
					_origFitterEnabled = (Object)(object)_fitter != (Object)null && ((Behaviour)_fitter).enabled;
					int childCount = ((Transform)rectTransform).childCount;
					_children = (GameObject[])(object)new GameObject[childCount];
					_childrenActive = new bool[childCount];
					for (int i = 0; i < childCount; i++)
					{
						_children[i] = ((Component)((Transform)rectTransform).GetChild(i)).gameObject;
						_childrenActive[i] = _children[i].activeSelf;
					}
					_reparented = false;
					_saved = true;
				}
				if ((Object)(object)_fitter != (Object)null && ((Behaviour)_fitter).enabled)
				{
					((Behaviour)_fitter).enabled = false;
				}
				if (_children != null)
				{
					for (int j = 0; j < _children.Length; j++)
					{
						if ((Object)(object)_children[j] != (Object)null && _children[j].activeSelf)
						{
							_children[j].SetActive(false);
						}
					}
				}
				RectTransform val = HostRect();
				if ((Object)(object)val != (Object)null)
				{
					if ((Object)(object)((Transform)rectTransform).parent != (Object)(object)val)
					{
						((Transform)rectTransform).SetParent((Transform)(object)val, false);
						((Transform)rectTransform).SetAsLastSibling();
						_reparented = true;
					}
				}
				else if (!_hostWarned)
				{
					_hostWarned = true;
					DriftwoodConnectPlugin.L.LogWarning((object)"JoinStatus: no root canvas above the version stamp; placing the status against its own parent, which may not be the screen.");
				}
				rectTransform.anchorMin = BandAnchorMin;
				rectTransform.anchorMax = BandAnchorMax;
				rectTransform.pivot = new Vector2(0.5f, 0.5f);
				rectTransform.anchoredPosition = Vector2.zero;
				rectTransform.sizeDelta = new Vector2(0f, 120f);
				if (((TMP_Text)_text).enableAutoSizing)
				{
					((TMP_Text)_text).enableAutoSizing = false;
				}
				if (((TMP_Text)_text).fontSize < 34f)
				{
					((TMP_Text)_text).fontSize = 34f;
				}
				if ((int)((TMP_Text)_text).textWrappingMode != 1)
				{
					((TMP_Text)_text).textWrappingMode = (TextWrappingModes)1;
				}
				if ((int)((TMP_Text)_text).overflowMode != 0)
				{
					((TMP_Text)_text).overflowMode = (TextOverflowModes)0;
				}
				((TMP_Text)_text).alignment = (TextAlignmentOptions)514;
				((Graphic)_text).color = Color.white;
				if (!((Behaviour)_text).enabled)
				{
					((Behaviour)_text).enabled = true;
				}
				if (((TMP_Text)_text).text != _message)
				{
					((TMP_Text)_text).text = _message;
				}
				if ((Object)(object)_perma != (Object)null && _perma.alpha < 1f)
				{
					_perma.alpha = 1f;
				}
			}
			catch (Exception e)
			{
				Break("placing the connecting message", e);
			}
		}

		private static RectTransform HostRect()
		{
			Canvas val = ((Graphic)_text).canvas;
			if ((Object)(object)val == (Object)null)
			{
				val = ((Component)_text).GetComponentInParent<Canvas>();
			}
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			Transform transform = ((Component)(((Object)(object)val.rootCanvas != (Object)null) ? val.rootCanvas : val)).transform;
			return (RectTransform)(object)((transform is RectTransform) ? transform : null);
		}

		private static void Break(string doing, Exception e)
		{
			_broken = true;
			_message = null;
			try
			{
				DriftwoodConnectPlugin.L.LogError((object)("JoinStatus switched itself off for this session while " + doing + ". The join is unaffected; only the on-screen message is gone. " + e));
			}
			catch
			{
			}
		}

		private static bool Resolve()
		{
			if ((Object)(object)_text != (Object)null)
			{
				return true;
			}
			_saved = false;
			_reparented = false;
			if (_resolved)
			{
				_resolved = false;
			}
			Type type = AccessTools.TypeByName("CanvasManager");
			if (type == null)
			{
				return false;
			}
			object obj = AccessTools.Field(type, "_instance")?.GetValue(null);
			if (obj == null)
			{
				return false;
			}
			object? obj2 = AccessTools.Field(type, "_versionText")?.GetValue(obj);
			_text = (TextMeshProUGUI)((obj2 is TextMeshProUGUI) ? obj2 : null);
			object? obj3 = AccessTools.Field(type, "_permaCanvas")?.GetValue(obj);
			_perma = (CanvasGroup)((obj3 is CanvasGroup) ? obj3 : null);
			if ((Object)(object)_text == (Object)null)
			{
				if (!_resolved)
				{
					_resolved = true;
					DriftwoodConnectPlugin.L.LogWarning((object)"CanvasManager._versionText not found; the join will proceed but nothing will narrate it on screen.");
				}
				return false;
			}
			_resolved = true;
			LogHierarchy();
			return true;
		}

		private static void LogHierarchy()
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_028e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a4: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.Append("JoinStatus host element: screen ").Append(Screen.width).Append('x')
					.Append(Screen.height);
				RectTransform rectTransform = ((TMP_Text)_text).rectTransform;
				int num = 0;
				Transform val = (Transform)(object)rectTransform;
				while ((Object)(object)val != (Object)null && num < 16)
				{
					stringBuilder.Append("\n  ").Append((num == 0) ? "stamp " : "parent ").Append(((Object)val).name);
					RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null);
					if ((Object)(object)val2 != (Object)null)
					{
						StringBuilder stringBuilder2 = stringBuilder.Append(" anchors(").Append(F(val2.anchorMin)).Append("..")
							.Append(F(val2.anchorMax))
							.Append(')')
							.Append(" pos ")
							.Append(F(val2.anchoredPosition))
							.Append(" size ")
							.Append(F(val2.sizeDelta))
							.Append(" rect ");
						Rect rect = val2.rect;
						stringBuilder2.Append(F(((Rect)(ref rect)).size));
					}
					Canvas component = ((Component)val).GetComponent<Canvas>();
					if ((Object)(object)component != (Object)null)
					{
						stringBuilder.Append(" [Canvas ").Append(component.renderMode).Append(component.isRootCanvas ? " ROOT" : "")
							.Append(']');
						CanvasScaler component2 = ((Component)val).GetComponent<CanvasScaler>();
						if ((Object)(object)component2 != (Object)null)
						{
							stringBuilder.Append(" [CanvasScaler ").Append(component2.uiScaleMode).Append(' ')
								.Append(F(component2.referenceResolution))
								.Append(']');
						}
					}
					if ((Object)(object)((Component)val).GetComponent<CanvasGroup>() != (Object)null)
					{
						stringBuilder.Append(" [CanvasGroup]");
					}
					if ((Object)(object)((Component)val).GetComponent<LayoutGroup>() != (Object)null)
					{
						stringBuilder.Append(" [LayoutGroup]");
					}
					if ((Object)(object)((Component)val).GetComponent<ContentSizeFitter>() != (Object)null)
					{
						stringBuilder.Append(" [ContentSizeFitter]");
					}
					val = val.parent;
					num++;
				}
				for (int i = 0; i < ((Transform)rectTransform).childCount; i++)
				{
					Transform child = ((Transform)rectTransform).GetChild(i);
					stringBuilder.Append("\n  child ").Append(((Object)child).name).Append(((Component)child).gameObject.activeSelf ? "" : " (inactive)");
					RectTransform val3 = (RectTransform)(object)((child is RectTransform) ? child : null);
					if ((Object)(object)val3 != (Object)null)
					{
						stringBuilder.Append(" anchors(").Append(F(val3.anchorMin)).Append("..")
							.Append(F(val3.anchorMax))
							.Append(") size ")
							.Append(F(val3.sizeDelta));
					}
				}
				DriftwoodConnectPlugin.L.LogInfo((object)stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("JoinStatus: could not describe the version stamp's hierarchy: " + ex.Message));
			}
		}

		private static string F(Vector2 v)
		{
			return "(" + v.x.ToString("0.##") + "," + v.y.ToString("0.##") + ")";
		}
	}
	internal static class MenuJoin
	{
		private static FieldInfo _fField;

		private static FieldInfo _fButton;

		private static string _pendingHost;

		private static int _pendingPort;

		private static bool _changeWarned;

		internal static bool Applied { get; private set; }

		internal static bool Apply(Harmony harmony)
		{
			//IL_014f: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Expected O, but got Unknown
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(ButtonManager), "OnLobbyIDInputFieldChange", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(ButtonManager), "JoinByIDButton", (Type[])null, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(ButtonManager), "Start", (Type[])null, (Type[])null);
			_fField = AccessTools.Field(typeof(ButtonManager), "_lobbyIdInputField");
			_fButton = AccessTools.Field(typeof(ButtonManager), "_joinByIDButton");
			if (methodInfo == null || methodInfo2 == null || _fField == null || _fButton == null)
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("MenuJoin: the Join Game menu moved (change=" + (methodInfo != null) + " join=" + (methodInfo2 != null) + " field=" + (_fField != null) + " button=" + (_fButton != null) + "). Addresses cannot be typed in the menu this build; the launcher path is unaffected."));
				return false;
			}
			harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MenuJoin), "ChangePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(AccessTools.Method(typeof(MenuJoin), "JoinPrefix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			if (methodInfo3 != null)
			{
				harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(MenuJoin), "StartPostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			Applied = true;
			return true;
		}

		internal static bool TryTakePending(out string host, out int port, out string label)
		{
			if (string.IsNullOrEmpty(_pendingHost))
			{
				host = null;
				port = 0;
				label = null;
				return false;
			}
			host = _pendingHost;
			port = _pendingPort;
			label = LabelFor(host, port);
			_pendingHost = null;
			_pendingPort = 0;
			return true;
		}

		private static void StartPostfix(ButtonManager __instance)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				object? value = _fField.GetValue(__instance);
				TMP_InputField val = (TMP_InputField)((value is TMP_InputField) ? value : null);
				if (!((Object)(object)val == (Object)null))
				{
					if ((int)val.contentType != 0)
					{
						val.contentType = (ContentType)0;
					}
					if ((int)val.characterValidation != 0)
					{
						val.characterValidation = (CharacterValidation)0;
					}
					if (val.characterLimit > 0 && val.characterLimit < 64)
					{
						val.characterLimit = 64;
					}
					Graphic placeholder = val.placeholder;
					TMP_Text val2 = (TMP_Text)(object)((placeholder is TMP_Text) ? placeholder : null);
					if ((Object)(object)val2 != (Object)null && !string.IsNullOrEmpty(val2.text) && val2.text.Length <= 24 && val2.text.IndexOf("IP", StringComparison.OrdinalIgnoreCase) < 0)
					{
						val2.text += " or IP:Port";
					}
				}
			}
			catch (Exception ex)
			{
				DriftwoodConnectPlugin.L.LogWarning((object)("MenuJoin: could not widen the lobby-ID box (" + ex.Message + "). Pasting an address may not work this session."));
			}
		}

		private static void ChangePostfix(ButtonManager __instance)
		{
			try
			{
				object? value = _fButton.GetValue(__instance);
				Button val = (Button)((value is Button) ? value : null);
				if (!((Object)(object)val == (Object)null) && !((Behaviour)val).enabled)
				{
					object? value2 = _fField.GetValue(__instance);
					TMP_InputField val2 = (TMP_InputField)((value2 is TMP_InputField) ? value2 : null);
					if (!((Object)(object)val2 == (Object)null) && TryParseAddress(val2.text, out var _, out var _))
					{
						((Behaviour)val).enabled = true;
					}
				}
			}
			catch (Exception ex)
			{
				if (!_changeWarned)
				{
					_changeWarned = true;
					DriftwoodConnectPlugin.L.LogWarning((object)("MenuJoin: the Join button cannot follow the lobby box (" + ex.Message + "). Typed addresses may leave it greyed out."));
				}
			}
		}

		private static bool JoinPrefix(ButtonManager __instance)
		{
			bool flag = false;
			try
			{
				object? value = _fField.GetValue(__instance);
				TMP_InputField val = (TMP_InputField)((value is TMP_InputField) ? value : null);
				if ((Object)(object)val == (Object)null)
				{
					return true;
				}
				string text = (val.text ?? "").Trim();
				if (text.Length == 0 || ulong.TryParse(text, out var _))
				{
					return true;
				}
				if (!TryParseAddress(text, out var host, out var port))
				{
					return true;
				}
				if (port == 0)
				{
					port = 22003;
				}
				if (!ConnectPatch.Applied)
				{
					DriftwoodConnectPlugin.L.LogError((object)"MenuJoin: address typed but the join patch is not in place; not dialling.");
					return true;
				}
				ConnectionManager instance = ConnectionManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					DriftwoodConnectPlugin.L.LogError((object)"MenuJoin: ConnectionManager.Instance is null; cannot join from the menu.");
					JoinStatus.Fail("Could not start the connection.");
					return false;
				}
				_pendingHost = host;
				_pendingPort = port;
				flag = true;
				DriftwoodConnectPlugin.L.LogInfo((object)("MenuJoin: joining " + LabelFor(host, port) + " typed in the Join Game menu."));
				JoinStatus.Set("Connecting to " + LabelFor(host, port));
				JoinStatus.Watch();
				Application.runInBackground = true;
				instance.JoinOfflineLobby();
				return false;
			}
			catch (Exception ex)
			{
				DriftwoodConnectPlugin.L.LogError((object)("MenuJoin: join from the menu failed (" + ex?.ToString() + ")."));
				_pendingHost = null;
				_pendingPort = 0;
				return !flag;
			}
		}

		internal static string LabelFor(string host, int port)
		{
			return AddressParser.LabelFor(host, port);
		}

		internal static bool TryParseAddress(string text, out string host, out int port)
		{
			return AddressParser.TryParse(text, out host, out port);
		}
	}
	[BepInPlugin("com.humangenome.driftwood.connect", "DriftwoodConnect", "0.1.4")]
	public class DriftwoodConnectPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.humangenome.driftwood.connect";

		public const string Version = "0.1.4";

		internal static ManualLogSource L;

		internal static bool SessionArmed;

		internal static ConfigEntry<bool> Enable;

		internal static ConfigEntry<bool> AutoConnect;

		internal static ConfigEntry<string> ConnectAddress;

		internal static ConfigEntry<int> ConnectPort;

		internal static ConfigEntry<string> ServerLabel;

		internal static ConfigEntry<string> JoinPassword;

		internal static ConfigEntry<float> AutoConnectDelaySeconds;

		internal static ConfigEntry<string> ArmedAtUtc;

		private const double ArmWindowHours = 2.0;

		private const double ArmFutureSkewMinutes = 10.0;

		private void Awake()
		{
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Expected O, but got Unknown
			L = ((BaseUnityPlugin)this).Logger;
			Enable = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "Enable", false, "Master switch. Default OFF so a stale install of this mod can never hijack a normal singleplayer or Steam-lobby session. The launcher writes true on every Connect.");
			AutoConnect = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "AutoConnect", true, "Join ConnectAddress as soon as the main menu is up, instead of waiting for the player to do something.");
			ConnectAddress = ((BaseUnityPlugin)this).Config.Bind<string>("Client", "ConnectAddress", "", "Host to dial. The game hardcodes \"localhost\" here; this is the value that replaces it.");
			ConnectPort = ((BaseUnityPlugin)this).Config.Bind<int>("Client", "ConnectPort", 0, "UDP gameplay port. 0 leaves the transport's own port untouched.");
			ServerLabel = ((BaseUnityPlugin)this).Config.Bind<string>("Client", "ServerLabel", "", "Shown in the on-screen connecting banner so the player can see which server they are joining.");
			JoinPassword = ((BaseUnityPlugin)this).Config.Bind<string>("Client", "JoinPassword", "", "Reserved. Current Driftwood hosts do not check a join password - they accept any client that knows the address - so this value is not presented to anything. It exists so the key has one meaning if that changes.");
			AutoConnectDelaySeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "AutoConnectDelaySeconds", 3f, "Seconds to let the menu scene settle before auto-joining.");
			ArmedAtUtc = ((BaseUnityPlugin)this).Config.Bind<string>("Client", "ArmedAtUtc", "", "Round-trip UTC timestamp of the Connect that armed this file. An arm older than a couple of hours is refused, so a config that outlived its session can never auto-join. Written by the launcher; cleared by this mod once used.");
			Silence.InstallIfHeadless();
			Harmony harmony = new Harmony("com.humangenome.driftwood.connect");
			bool flag = ConnectPatch.Apply(harmony);
			if (flag)
			{
				MenuJoin.Apply(harmony);
				IdentityClaim.Apply(harmony);
			}
			else
			{
				L.LogError((object)"DriftwoodConnect could not patch ConnectionManager.JoinOfflineLobby. Neither the launcher nor a typed address can join this session.");
			}
			if (!Enable.Value)
			{
				L.LogInfo((object)"DriftwoodConnect 0.1.4 is idle (Client.Enable=false). Vanilla behaviour is untouched; a server address typed into the Join Game menu's lobby box still joins.");
				return;
			}
			if (!ArmIsFresh(out var age))
			{
				L.LogWarning((object)("DriftwoodConnect found an armed config that is " + age + ". Not joining anything: launch through Driftwood to join a server, or type its address into the Join Game menu."));
				Disarm();
				return;
			}
			bool value = AutoConnect.Value;
			if (!Disarm())
			{
				L.LogError((object)("DriftwoodConnect could not clear the arm from " + ((BaseUnityPlugin)this).Config.ConfigFilePath + ". Joining anyway; the arm expires on its own within " + 2.0 + " hours."));
			}
			Application.runInBackground = true;
			if (flag)
			{
				SessionArmed = true;
				L.LogInfo((object)string.Format("DriftwoodConnect {0} armed. target={1} autoConnect={2}", "0.1.4", Describe(), value));
				if (value)
				{
					((MonoBehaviour)this).StartCoroutine(AutoConnectRoutine());
				}
			}
		}

		private bool ArmIsFresh(out string age)
		{
			string text = (ArmedAtUtc.Value ?? "").Trim();
			if (text.Length == 0)
			{
				age = "not dated (no ArmedAtUtc), so its age cannot be established";
				return false;
			}
			if (!DateTime.TryParse(text, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var result))
			{
				age = "dated with something that is not a timestamp (" + text + ")";
				return false;
			}
			TimeSpan timeSpan = DateTime.UtcNow - result.ToUniversalTime();
			if (timeSpan.TotalMinutes < -10.0)
			{
				age = "dated " + Math.Round(0.0 - timeSpan.TotalMinutes) + " minutes in the FUTURE, so this machine's clock and the stamp disagree";
				return false;
			}
			if (timeSpan.TotalHours > 2.0)
			{
				age = "from " + Math.Round(timeSpan.TotalHours, 1) + " hours ago, past the " + 2.0 + " hour limit";
				return false;
			}
			age = "from " + Math.Round(Math.Max(0.0, timeSpan.TotalSeconds)) + " seconds ago";
			return true;
		}

		private bool Disarm()
		{
			try
			{
				Enable.Value = false;
				AutoConnect.Value = false;
				ArmedAtUtc.Value = "";
				((BaseUnityPlugin)this).Config.Save();
				string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
				if (!File.Exists(configFilePath))
				{
					L.LogWarning((object)("The connect config is gone from " + configFilePath + " after clearing the arm. Nothing left to auto-join from, which is the safe outcome."));
					return true;
				}
				string[] array = File.ReadAllLines(configFilePath);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length == 0 || text[0] == '#' || text[0] == '[')
					{
						continue;
					}
					int num = text.IndexOf('=');
					if (num > 0 && text.Substring(0, num).Trim().Equals("Enable", StringComparison.OrdinalIgnoreCase))
					{
						string text2 = text.Substring(num + 1).Trim();
						if (text2.Equals("true", StringComparison.OrdinalIgnoreCase) || text2 == "1")
						{
							return false;
						}
						return true;
					}
				}
				return true;
			}
			catch (Exception ex)
			{
				L.LogError((object)("Clearing the arm threw (" + ex.GetType().Name + ": " + ex.Message + ")."));
				return false;
			}
		}

		internal static string Describe()
		{
			string value = ConnectAddress.Value;
			if (string.IsNullOrEmpty(value))
			{
				return "(none)";
			}
			if (ConnectPort.Value <= 0)
			{
				return value;
			}
			return value + ":" + ConnectPort.Value;
		}

		internal static string Label()
		{
			string value = ServerLabel.Value;
			if (!string.IsNullOrEmpty(value))
			{
				return value;
			}
			return Describe();
		}

		private IEnumerator AutoConnectRoutine()
		{
			if (string.IsNullOrEmpty(ConnectAddress.Value))
			{
				L.LogWarning((object)"AutoConnect is on but ConnectAddress is empty. Staying on the menu.");
				yield break;
			}
			yield return (object)new WaitForSeconds(Mathf.Max(0f, AutoConnectDelaySeconds.Value));
			ConnectionManager instance = ConnectionManager.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				L.LogError((object)"ConnectionManager.Instance is null after the menu settled. Cannot join.");
				JoinStatus.Fail("Could not start the connection.");
			}
			else
			{
				JoinStatus.Set("Connecting to " + Label());
				JoinStatus.Watch();
				instance.JoinOfflineLobby();
			}
		}
	}
	internal class Silence : MonoBehaviour
	{
		internal static void InstallIfHeadless()
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if (IsHeadless())
			{
				AudioListener.pause = true;
				AudioListener.volume = 0f;
				GameObject val = new GameObject("Driftwood.Silence");
				Object.DontDestroyOnLoad((Object)val);
				((Object)val).hideFlags = (HideFlags)61;
				val.AddComponent<Silence>();
			}
		}

		private static bool IsHeadless()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			if (!Application.isBatchMode)
			{
				return (int)SystemInfo.graphicsDeviceType == 4;
			}
			return true;
		}

		private void Update()
		{
			if (AudioListener.volume != 0f)
			{
				AudioListener.volume = 0f;
			}
			if (!AudioListener.pause)
			{
				AudioListener.pause = true;
			}
		}
	}
}