Decompiled source of REPO Steam P2P v1.4.0

BepInEx\plugins\RepoSteamTransport.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using Photon.Voice;
using Photon.Voice.PUN;
using Photon.Voice.Unity;
using Platforms;
using RepoSteamTransport.Patches;
using RepoSteamTransport.Transport;
using Steamworks;
using Steamworks.Data;
using UnityEngine;

[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("Makfly")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Replaces REPO's Photon transport with Steamworks SDR P2P (host = in-process virtual LoadBalancing server).")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0+d21b707937412ba23d857cbe52e578e60946378c")]
[assembly: AssemblyProduct("RepoSteamTransport")]
[assembly: AssemblyTitle("RepoSteamTransport")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.4.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 RepoSteamTransport
{
	public static class ModConfig
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<string> Role;

		public static ConfigEntry<string> HostSteamId;

		public static ConfigEntry<string> HostAddress;

		public static ConfigEntry<bool> ZeroTierAuto;

		public static ConfigEntry<bool> ForceVoiceTransmit;

		public static ConfigEntry<bool> PhotonCompat;

		public static ConfigEntry<bool> Verbose;

		public static ConfigEntry<int> VirtualPort;

		public static ConfigEntry<bool> LobbyAuthCheck;

		private static ConfigFile _file;

		private static FieldInfo _currentLobbyField;

		private static int _lastLobbyReadFailedTick = -100000;

		public static void Initialize(ConfigFile file)
		{
			_file = file;
			Enabled = file.Bind<bool>("General", "Enabled", true, "Master switch for the Steam SDR transport.");
			Role = file.Bind<string>("General", "Role", "Auto", "Auto (resolve from Steam lobby) | Host | Client");
			HostSteamId = file.Bind<string>("General", "HostSteamId", "", "SteamId64 of the host when Role=Client (or Auto fallback).");
			HostAddress = file.Bind<string>("General", "HostAddress", "", "Direct host IP (e.g. 127.0.0.1 for a local two-instance test, or a LAN IP). Uses plain UDP instead of Steam relay.");
			ZeroTierAuto = file.Bind<bool>("General", "ZeroTierAuto", true, "Fallback step 2/3 of the connection ladder: try the host's ZeroTier IP published in the Steam lobby (direct UDP tunnel) before the Steam relay. The ladder: Steam P2P (LAN direct) → ZeroTier → Steam relay.");
			ForceVoiceTransmit = file.Bind<bool>("General", "ForceVoiceTransmit", false, "When true: open microphone (always transmit in a room, ignoring PushToTalk/mute). When false: REPO's native voice logic (Push to Talk setting in game options works normally).");
			PhotonCompat = file.Bind<bool>("General", "PhotonCompat", true, "Photon compatibility shim (merged from Self_Hosted_Photon): forces GpBinaryV16 serialization our virtual server is tuned for, blanks Photon cloud AppIds, overrides empty lobby Region to 'eu'. Keep enabled.");
			Verbose = file.Bind<bool>("General", "Verbose", false, "Detailed transport logging (every datagram, operation and voice event). Only enable for diagnostics — very spammy.");
			VirtualPort = file.Bind<int>("General", "VirtualPort", 0, "SteamNetworkingSockets virtual port for the SDR session.");
			LobbyAuthCheck = file.Bind<bool>("General", "LobbyAuthCheck", true, "Allow connections to the virtual server only for members of the host's current Steam lobby. If the host's lobby cannot be read, connections are allowed (fail open). Peers without a known SteamId (loopback, LAN/UDP) are always allowed.");
			_currentLobbyField = typeof(SteamLobbyManager).GetField("currentLobby", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		}

		public static bool TryGetLobby(out Lobby lobby)
		{
			bool reflectionFailed;
			return TryGetLobby(out lobby, out reflectionFailed);
		}

		public static bool TryGetLobby(out Lobby lobby, out bool reflectionFailed)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			lobby = default(Lobby);
			reflectionFailed = false;
			try
			{
				SteamLobbyManager instance = SteamLobbyManager.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return false;
				}
				if (_currentLobbyField == null)
				{
					reflectionFailed = true;
					WarnLobbyReadFailed("field handle not resolved");
					return false;
				}
				lobby = (Lobby)_currentLobbyField.GetValue(instance);
				SteamId id = ((Lobby)(ref lobby)).Id;
				return ((SteamId)(ref id)).IsValid;
			}
			catch (Exception ex)
			{
				reflectionFailed = true;
				WarnLobbyReadFailed(ex.Message);
				return false;
			}
		}

		private static void WarnLobbyReadFailed(string reason)
		{
			int tickCount = Environment.TickCount;
			if (tickCount - _lastLobbyReadFailedTick >= 30000)
			{
				_lastLobbyReadFailedTick = tickCount;
				ManualLogSource log = Plugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("[config] SteamLobbyManager.currentLobby read failed (" + reason + ") - lobby-derived values are degraded"));
				}
			}
		}

		public static string GetZeroTierIp()
		{
			try
			{
				NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
				foreach (NetworkInterface networkInterface in allNetworkInterfaces)
				{
					if (networkInterface.OperationalStatus != OperationalStatus.Up)
					{
						continue;
					}
					string text = networkInterface.Name + " " + networkInterface.Description;
					if (text.IndexOf("ZeroTier", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses)
					{
						if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
						{
							return unicastAddress.Address.ToString();
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (Plugin.Verbose)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[config] ZeroTier NIC scan failed: " + ex.Message));
					}
				}
			}
			return null;
		}

		public static bool IsHostRole()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			string text = (Role?.Value ?? "Auto").Trim().ToLowerInvariant();
			if (!(text == "host"))
			{
				if (text == "client")
				{
					return false;
				}
				if (!TryGetLobby(out var lobby, out var reflectionFailed))
				{
					if (reflectionFailed)
					{
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogWarning((object)"[config] role resolution degraded to the Auto default (no lobby owner available)");
						}
					}
					return true;
				}
				try
				{
					return SteamId.op_Implicit(((Lobby)(ref lobby)).Owner.Id) == SteamId.op_Implicit(SteamClient.SteamId);
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						ManualLogSource log2 = Plugin.Log;
						if (log2 != null)
						{
							log2.LogWarning((object)("[config] lobby owner / local SteamId compare failed: " + ex.Message));
						}
					}
					return true;
				}
			}
			return true;
		}

		public static ulong ResolveHostSteamId()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (TryGetLobby(out var lobby))
			{
				SteamId id = ((Lobby)(ref lobby)).Id;
				if (((SteamId)(ref id)).IsValid)
				{
					try
					{
						ulong num = SteamId.op_Implicit(((Lobby)(ref lobby)).Owner.Id);
						if (num != 0L)
						{
							return num;
						}
					}
					catch (Exception ex)
					{
						if (Plugin.Verbose)
						{
							ManualLogSource log = Plugin.Log;
							if (log != null)
							{
								log.LogWarning((object)("[config] lobby owner read failed: " + ex.Message));
							}
						}
					}
				}
			}
			string s = (HostSteamId?.Value ?? "").Trim();
			if (ulong.TryParse(s, out var result) && result != 0L)
			{
				return result;
			}
			return 0uL;
		}

		public static string GetZeroTierFallbackIp()
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			ConfigEntry<bool> zeroTierAuto = ZeroTierAuto;
			if (zeroTierAuto != null && !zeroTierAuto.Value)
			{
				return null;
			}
			if (TryGetLobby(out var lobby))
			{
				SteamId id = ((Lobby)(ref lobby)).Id;
				if (((SteamId)(ref id)).IsValid)
				{
					try
					{
						string data = ((Lobby)(ref lobby)).GetData("ztip");
						if (!string.IsNullOrEmpty(data))
						{
							return data;
						}
					}
					catch (Exception ex)
					{
						if (Plugin.Verbose)
						{
							ManualLogSource log = Plugin.Log;
							if (log != null)
							{
								log.LogWarning((object)("[config] lobby ztip read failed: " + ex.Message));
							}
						}
					}
				}
			}
			return null;
		}

		public static string ResolveTargetAddress()
		{
			string text = (HostAddress?.Value ?? "").Trim();
			if (text.Length > 0)
			{
				return text;
			}
			ulong num = ResolveHostSteamId();
			if (num != 0L)
			{
				return num.ToString();
			}
			string zeroTierFallbackIp = GetZeroTierFallbackIp();
			if (!string.IsNullOrEmpty(zeroTierFallbackIp))
			{
				if (Plugin.Verbose)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogInfo((object)("[config] no SteamId available -> ZeroTier fallback: " + zeroTierFallbackIp));
					}
				}
				return zeroTierFallbackIp;
			}
			return "0";
		}
	}
	internal static class ObjectLetExtensions
	{
		public static void Let<T>(this T obj, Action<T> action)
		{
			if (obj != null)
			{
				action(obj);
			}
		}
	}
	[BepInPlugin("local.repoversteam.transport", "REPO Steam Transport", "1.4.0")]
	public class Plugin : BaseUnityPlugin
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static LogCallback <>9__17_0;

			public static SerializeMethod <>9__32_0;

			public static DeserializeMethod <>9__32_1;

			public static SerializeStreamMethod <>9__32_2;

			public static DeserializeStreamMethod <>9__32_3;

			internal void <Awake>b__17_0(string msg, string _, LogType type)
			{
				//IL_0103: 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_0108: Invalid comparison between Unknown and I4
				if (msg == null)
				{
					return;
				}
				if (msg.Contains("Unity microphone input source creation failure"))
				{
					MicStormGuardPatch.NotifyMicFailure();
				}
				if (!Verbose || (!msg.Contains("PhotonVoice") && !msg.Contains("VoiceClient") && !msg.Contains("Voice room") && !msg.Contains("Voice client") && !msg.Contains("OnVoiceStateChanged") && !msg.Contains("FollowLeader") && !msg.Contains("voice room") && !msg.Contains("Recorder") && !msg.Contains("Photon") && !msg.Contains("Join") && !msg.Contains("scene") && !msg.Contains("Level") && !msg.Contains("Property") && !msg.Contains("index")))
				{
					return;
				}
				string text = ((msg.Length > 220) ? msg.Substring(0, 220) : msg);
				if ((int)type == 0 || (int)type == 4)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogWarning((object)("[pv] " + text));
					}
				}
				else
				{
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogInfo((object)("[pv] " + text));
					}
				}
			}

			internal byte[] <RegisterVoiceCustomTypes>b__32_0(object obj)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Expected O, but got Unknown
				ByteArraySlice val = (ByteArraySlice)obj;
				byte[] array = new byte[val.Count];
				Buffer.BlockCopy(val.Buffer, val.Offset, array, 0, val.Count);
				return array;
			}

			internal object <RegisterVoiceCustomTypes>b__32_1(byte[] buffer)
			{
				return buffer;
			}

			internal short <RegisterVoiceCustomTypes>b__32_2(StreamBuffer dout, object obj)
			{
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Expected O, but got Unknown
				ByteArraySlice val = (ByteArraySlice)obj;
				dout.Write(val.Buffer, val.Offset, val.Count);
				return (short)val.Count;
			}

			internal object <RegisterVoiceCustomTypes>b__32_3(StreamBuffer din, short len)
			{
				byte[] array = new byte[len];
				int num = din.Read(array, 0, (int)len);
				if (num != len)
				{
					return array[..num];
				}
				return array;
			}
		}

		public const string PluginGuid = "local.repoversteam.transport";

		public const string PluginName = "REPO Steam Transport";

		public const string PluginVersion = "1.4.0";

		private static bool _runInBackgroundSet;

		private MethodInfo _sendVoiceInfoMethod;

		private int _voiceResendTick = Environment.TickCount - 10000;

		private static int _lastResendFailWarnTick = -100000;

		private MethodInfo _remoteVoicesGetter;

		private FieldInfo _localVoicesField;

		private PropertyInfo _pvcInstance;

		private PropertyInfo _voiceClientProp;

		private PropertyInfo _primaryRecorderGetter;

		private int _voiceDiagTick = Environment.TickCount - 15000;

		private static int _lastDiagFailWarnTick = -100000;

		private static bool _voiceSliceBusyLogged;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static bool Verbose => ModConfig.Verbose?.Value ?? false;

		internal Harmony Harmony { get; private set; }

		private void Awake()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Expected O, but got Unknown
			Instance = this;
			Log = Logger.CreateLogSource("local.repoversteam.transport");
			ModConfig.Initialize(((BaseUnityPlugin)this).Config);
			ConfigEntry<bool> enabled = ModConfig.Enabled;
			if (enabled == null || !enabled.Value)
			{
				Log.LogInfo((object)"[plugin] mod disabled by config (Enabled=false) - transport not initialized");
				return;
			}
			RegisterVoiceCustomTypes();
			object obj = <>c.<>9__17_0;
			if (obj == null)
			{
				LogCallback val = delegate(string msg, string _, LogType val2)
				{
					//IL_0103: 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_0108: Invalid comparison between Unknown and I4
					if (msg != null)
					{
						if (msg.Contains("Unity microphone input source creation failure"))
						{
							MicStormGuardPatch.NotifyMicFailure();
						}
						if (Verbose && (msg.Contains("PhotonVoice") || msg.Contains("VoiceClient") || msg.Contains("Voice room") || msg.Contains("Voice client") || msg.Contains("OnVoiceStateChanged") || msg.Contains("FollowLeader") || msg.Contains("voice room") || msg.Contains("Recorder") || msg.Contains("Photon") || msg.Contains("Join") || msg.Contains("scene") || msg.Contains("Level") || msg.Contains("Property") || msg.Contains("index")))
						{
							string text2 = ((msg.Length > 220) ? msg.Substring(0, 220) : msg);
							if ((int)val2 == 0 || (int)val2 == 4)
							{
								ManualLogSource log3 = Log;
								if (log3 != null)
								{
									log3.LogWarning((object)("[pv] " + text2));
								}
							}
							else
							{
								ManualLogSource log4 = Log;
								if (log4 != null)
								{
									log4.LogInfo((object)("[pv] " + text2));
								}
							}
						}
					}
				};
				<>c.<>9__17_0 = val;
				obj = (object)val;
			}
			Application.logMessageReceived += (LogCallback)obj;
			Application.runInBackground = true;
			Thread thread = new Thread(SdrFabric.PumpUdpLoop)
			{
				IsBackground = true,
				Name = "RepoSteamTransport udp"
			};
			thread.Start();
			Harmony = new Harmony("local.repoversteam.transport");
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			foreach (Type type in types)
			{
				if (type.GetCustomAttributes(typeof(HarmonyAttribute), inherit: false).Length == 0)
				{
					continue;
				}
				try
				{
					Harmony.CreateClassProcessor(type).Patch();
				}
				catch (Exception ex)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogError((object)("[patch] FAILED to apply " + type.Name + ": " + ex.Message));
					}
				}
			}
			string text = "";
			try
			{
				string location = Assembly.GetExecutingAssembly().Location;
				if (!string.IsNullOrEmpty(location))
				{
					text = $" build {File.GetLastWriteTime(location):yyyy-MM-dd HH:mm:ss}";
				}
			}
			catch (Exception ex2)
			{
				if (Verbose)
				{
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[plugin] build time read failed: " + ex2.Message));
					}
				}
			}
			if (Verbose)
			{
				Log.LogInfo((object)("REPO Steam Transport v1.4.0" + text + " loaded. Transport is Steam SDR; virtual LB server = in-process on host."));
			}
		}

		private void Update()
		{
			ConfigEntry<bool> enabled = ModConfig.Enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			if (!_runInBackgroundSet)
			{
				_runInBackgroundSet = true;
				Application.runInBackground = true;
			}
			try
			{
				SdrFabric.Pump();
			}
			catch (Exception ex)
			{
				if (Verbose)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogWarning((object)("[pump] update pump failed: " + ex.Message));
					}
				}
			}
			VoiceDiagnosticsTick();
			VoiceInfoResendTick();
		}

		private void VoiceInfoResendTick()
		{
			try
			{
				if (Environment.TickCount - _voiceResendTick < 10000)
				{
					return;
				}
				_voiceResendTick = Environment.TickCount;
				if (_pvcInstance == null || _voiceClientProp == null || _localVoicesField == null)
				{
					return;
				}
				object value = _pvcInstance.GetValue(null);
				if (value == null)
				{
					return;
				}
				object value2 = _voiceClientProp.GetValue(value);
				if (value2 == null || !(_localVoicesField.GetValue(value2) is IDictionary { Count: not 0 } dictionary))
				{
					return;
				}
				if (_sendVoiceInfoMethod == null)
				{
					object obj = null;
					{
						IEnumerator enumerator = dictionary.Values.GetEnumerator();
						try
						{
							if (enumerator.MoveNext())
							{
								object current = enumerator.Current;
								obj = current;
							}
						}
						finally
						{
							IDisposable disposable = enumerator as IDisposable;
							if (disposable != null)
							{
								disposable.Dispose();
							}
						}
					}
					Type type = obj?.GetType().BaseType;
					_sendVoiceInfoMethod = type?.GetMethod("sendVoiceInfoAndConfigFrame", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2]
					{
						typeof(bool),
						typeof(int[])
					}, null);
					if (_sendVoiceInfoMethod == null && type != null)
					{
						MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
						foreach (MethodInfo methodInfo in methods)
						{
							if (methodInfo.Name == "sendVoiceInfoAndConfigFrame" && methodInfo.GetParameters().Length == 2)
							{
								_sendVoiceInfoMethod = methodInfo;
								break;
							}
						}
					}
				}
				if (_sendVoiceInfoMethod == null)
				{
					return;
				}
				foreach (object value3 in dictionary.Values)
				{
					_sendVoiceInfoMethod.Invoke(value3, new object[2] { false, null });
				}
				if (Verbose)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogInfo((object)$"[voice-resend] re-published voice info for {dictionary.Count} local voice(s)");
					}
				}
			}
			catch (Exception ex)
			{
				int tickCount = Environment.TickCount;
				if (tickCount - _lastResendFailWarnTick >= 30000)
				{
					_lastResendFailWarnTick = tickCount;
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[voice-resend] tick failed: " + ex.Message));
					}
				}
			}
		}

		private void VoiceDiagnosticsTick()
		{
			try
			{
				if (Environment.TickCount - _voiceDiagTick < 15000)
				{
					return;
				}
				_voiceDiagTick = Environment.TickCount;
				if (_pvcInstance == null)
				{
					Type typeFromHandle = typeof(PunVoiceClient);
					_pvcInstance = typeFromHandle.GetProperty("ExistingInstance", BindingFlags.Static | BindingFlags.Public);
					_voiceClientProp = typeFromHandle.GetProperty("VoiceClient", BindingFlags.Instance | BindingFlags.Public);
					Type typeFromHandle2 = typeof(VoiceClient);
					_remoteVoicesGetter = typeFromHandle2.GetProperty("RemoteVoiceInfos", BindingFlags.Instance | BindingFlags.Public)?.GetMethod;
					_localVoicesField = typeFromHandle2.GetField("localVoices", BindingFlags.Instance | BindingFlags.NonPublic);
					_primaryRecorderGetter = typeFromHandle.GetProperty("PrimaryRecorder", BindingFlags.Instance | BindingFlags.Public);
				}
				if (_pvcInstance == null || _voiceClientProp == null)
				{
					return;
				}
				object value = _pvcInstance.GetValue(null);
				if (value == null)
				{
					return;
				}
				object value2 = _voiceClientProp.GetValue(value);
				if (value2 == null)
				{
					return;
				}
				int localCount = -1;
				_localVoicesField?.GetValue(value2)?.Let(delegate(object o)
				{
					if (o is IDictionary dictionary2)
					{
						localCount = dictionary2.Count;
					}
				});
				int num = -1;
				object obj = _remoteVoicesGetter?.Invoke(value2, null);
				if (obj is IDictionary dictionary)
				{
					num = dictionary.Count;
				}
				else if (obj is IEnumerable enumerable)
				{
					int num2 = 0;
					foreach (object item in enumerable)
					{
						num2++;
					}
					num = num2;
				}
				string arg = "?";
				try
				{
					object obj2 = _primaryRecorderGetter?.GetValue(value);
					if (obj2 != null)
					{
						arg = (((bool)obj2.GetType().GetProperty("TransmitEnabled").GetValue(obj2)) ? "True" : "False");
					}
				}
				catch
				{
				}
				if ((localCount >= 0 || num >= 0) && Verbose)
				{
					ManualLogSource log = Log;
					if (log != null)
					{
						log.LogInfo((object)$"[voice-diag] localVoices={localCount} remoteVoices={num} transmit={arg}");
					}
				}
			}
			catch (Exception ex)
			{
				int tickCount = Environment.TickCount;
				if (tickCount - _lastDiagFailWarnTick >= 30000)
				{
					_lastDiagFailWarnTick = tickCount;
					ManualLogSource log2 = Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[voice-diag] tick failed: " + ex.Message));
					}
				}
			}
		}

		private void RegisterVoiceCustomTypes()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Expected O, but got Unknown
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			try
			{
				Type typeFromHandle = typeof(ByteArraySlice);
				for (byte b = 68; b < 80; b++)
				{
					byte num = b;
					object obj = <>c.<>9__32_0;
					if (obj == null)
					{
						SerializeMethod val = delegate(object obj5)
						{
							//IL_0001: Unknown result type (might be due to invalid IL or missing references)
							//IL_0007: Expected O, but got Unknown
							ByteArraySlice val5 = (ByteArraySlice)obj5;
							byte[] array = new byte[val5.Count];
							Buffer.BlockCopy(val5.Buffer, val5.Offset, array, 0, val5.Count);
							return array;
						};
						<>c.<>9__32_0 = val;
						obj = (object)val;
					}
					object obj2 = <>c.<>9__32_1;
					if (obj2 == null)
					{
						DeserializeMethod val2 = (byte[] buffer) => buffer;
						<>c.<>9__32_1 = val2;
						obj2 = (object)val2;
					}
					if (Protocol.TryRegisterType(typeFromHandle, num, (SerializeMethod)obj, (DeserializeMethod)obj2))
					{
						byte num2 = b;
						object obj3 = <>c.<>9__32_2;
						if (obj3 == null)
						{
							SerializeStreamMethod val3 = delegate(StreamBuffer dout, object obj5)
							{
								//IL_0001: Unknown result type (might be due to invalid IL or missing references)
								//IL_0007: Expected O, but got Unknown
								ByteArraySlice val5 = (ByteArraySlice)obj5;
								dout.Write(val5.Buffer, val5.Offset, val5.Count);
								return (short)val5.Count;
							};
							<>c.<>9__32_2 = val3;
							obj3 = (object)val3;
						}
						object obj4 = <>c.<>9__32_3;
						if (obj4 == null)
						{
							DeserializeStreamMethod val4 = delegate(StreamBuffer din, short len)
							{
								byte[] array = new byte[len];
								int num3 = din.Read(array, 0, (int)len);
								return (num3 != len) ? ((object)array[..num3]) : ((object)array);
							};
							<>c.<>9__32_3 = val4;
							obj4 = (object)val4;
						}
						if (Protocol.TryRegisterType(typeFromHandle, num2, (SerializeStreamMethod)obj3, (DeserializeStreamMethod)obj4))
						{
							if (Verbose)
							{
								Log.LogInfo((object)$"[Voice] ByteArraySlice registered as custom serializer type 0x{b:X2}");
							}
							return;
						}
					}
				}
				if (!_voiceSliceBusyLogged)
				{
					_voiceSliceBusyLogged = true;
					if (Verbose)
					{
						Log.LogInfo((object)"[Voice] ByteArraySlice serializer already registered (custom type codes busy) - ok, skipping");
					}
				}
			}
			catch (Exception ex)
			{
				if (Verbose)
				{
					Log.LogWarning((object)("[Voice] ByteArraySlice registration failed: " + ex.Message));
				}
			}
		}
	}
}
namespace RepoSteamTransport.Transport
{
	public static class EnetCodec
	{
		public struct IncomingCommand
		{
			public byte Type;

			public byte Channel;

			public byte Flags;

			public int Size;

			public int ReliableSeq;

			public int UnreliableSeq;

			public byte[] Payload;

			public byte[] Raw;
		}

		public const byte CT_ACK = 1;

		public const byte CT_CONNECT = 2;

		public const byte CT_VERIFYCONNECT = 3;

		public const byte CT_DISCONNECT = 4;

		public const byte CT_PING = 5;

		public const byte CT_SENDRELIABLE = 6;

		public const byte CT_SENDUNRELIABLE = 7;

		public const byte CT_SENDFRAGMENT = 8;

		public const byte CT_SENDUNSEQUENCED = 11;

		public const byte CT_SYSTEM = 12;

		public const int HeaderSize = 12;

		public const int DatagramHeaderSize = 12;

		public const int FragmentHeaderSize = 20;

		public const int FragmentChunkBytes = 1100;

		public const int FragmentMaxCount = 128;

		public const int FragmentMaxTotalBytes = 262144;

		public static bool IsFlaggedReliable(byte flags)
		{
			return (flags & 1) != 0;
		}

		public static int ReadIntBE(byte[] b, int off)
		{
			return (b[off] << 24) | (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3];
		}

		public static void WriteIntBE(byte[] b, int off, int v)
		{
			b[off] = (byte)(v >> 24);
			b[off + 1] = (byte)(v >> 16);
			b[off + 2] = (byte)(v >> 8);
			b[off + 3] = (byte)v;
		}

		public static void WriteShortBE(byte[] b, int off, int v)
		{
			b[off] = (byte)(v >> 8);
			b[off + 1] = (byte)v;
		}

		public static bool TryParseDatagram(byte[] datagram, out int peerId, out int timeInt, out int challenge, out List<IncomingCommand> commands)
		{
			peerId = 0;
			timeInt = 0;
			challenge = 0;
			commands = null;
			if (datagram == null || datagram.Length < 12)
			{
				return false;
			}
			peerId = (datagram[0] << 8) | datagram[1];
			byte b = datagram[3];
			timeInt = ReadIntBE(datagram, 4);
			challenge = ReadIntBE(datagram, 8);
			commands = new List<IncomingCommand>(b);
			int num = 12;
			for (int i = 0; i < b; i++)
			{
				if (num + 12 > datagram.Length)
				{
					return false;
				}
				int num2 = ReadIntBE(datagram, num + 4);
				if (num2 < 12 || (long)num + (long)num2 > datagram.Length)
				{
					return false;
				}
				byte b2 = datagram[num];
				int num3 = 0;
				if (b2 == 7 || b2 == 11)
				{
					if (num2 < 16)
					{
						return false;
					}
					num3 = 4;
				}
				IncomingCommand item = new IncomingCommand
				{
					Type = b2,
					Channel = datagram[num + 1],
					Flags = datagram[num + 2],
					Size = num2,
					ReliableSeq = ReadIntBE(datagram, num + 8),
					Raw = new byte[num2]
				};
				if (num3 == 4)
				{
					item.UnreliableSeq = ReadIntBE(datagram, num + 12);
				}
				Array.Copy(datagram, num, item.Raw, 0, num2);
				int sourceIndex = num + 12 + num3;
				int num4 = num2 - 12 - num3;
				if (num4 > 0)
				{
					item.Payload = new byte[num4];
					Array.Copy(datagram, sourceIndex, item.Payload, 0, num4);
				}
				commands.Add(item);
				num += num2;
			}
			return true;
		}

		public static byte[] BuildDatagram(int clientPeerId, int timeInt, int challenge, List<byte[]> commands)
		{
			int num = 12;
			foreach (byte[] command in commands)
			{
				num += command.Length;
			}
			byte[] array = new byte[num];
			WriteShortBE(array, 0, clientPeerId);
			array[2] = 0;
			array[3] = (byte)commands.Count;
			WriteIntBE(array, 4, timeInt);
			WriteIntBE(array, 8, challenge);
			int num2 = 12;
			foreach (byte[] command2 in commands)
			{
				Buffer.BlockCopy(command2, 0, array, num2, command2.Length);
				num2 += command2.Length;
			}
			return array;
		}

		public static byte[] BuildAck(IncomingCommand cmd, int sentTime)
		{
			byte[] array = new byte[20]
			{
				1, cmd.Channel, 0, 4, 0, 0, 0, 0, 0, 0,
				0, 0, 0, 0, 0, 0, 0, 0, 0, 0
			};
			WriteIntBE(array, 4, 20);
			WriteIntBE(array, 8, 0);
			WriteIntBE(array, 12, cmd.ReliableSeq);
			WriteIntBE(array, 16, sentTime);
			return array;
		}

		public static byte[] BuildReliablePayload(byte channel, int seq, byte[] payload)
		{
			byte[] array = new byte[12 + ((payload != null) ? payload.Length : 0)];
			array[0] = 6;
			array[1] = channel;
			array[2] = 1;
			array[3] = 4;
			WriteIntBE(array, 4, array.Length);
			WriteIntBE(array, 8, seq);
			if (payload != null && payload.Length != 0)
			{
				Buffer.BlockCopy(payload, 0, array, 12, payload.Length);
			}
			return array;
		}

		public static byte[] BuildUnreliablePayload(byte channel, int reliableSeqField, int unreliableSeq, byte[] payload)
		{
			byte[] array = new byte[16 + ((payload != null) ? payload.Length : 0)];
			array[0] = 7;
			array[1] = channel;
			array[2] = 0;
			array[3] = 4;
			WriteIntBE(array, 4, array.Length);
			WriteIntBE(array, 8, reliableSeqField);
			WriteIntBE(array, 12, unreliableSeq);
			if (payload != null && payload.Length != 0)
			{
				Buffer.BlockCopy(payload, 0, array, 16, payload.Length);
			}
			return array;
		}

		public static List<byte[]> BuildFragmentCommands(int seqStart, byte[] payload, byte channel)
		{
			if (payload == null || payload.Length == 0)
			{
				return null;
			}
			int num = payload.Length;
			int num2 = (num + 1100 - 1) / 1100;
			if (num2 > 128 || num > 262144)
			{
				return null;
			}
			List<byte[]> list = new List<byte[]>(num2);
			int num3 = 0;
			for (int i = 0; i < num2; i++)
			{
				int num4 = Math.Min(1100, num - num3);
				byte[] array = new byte[32 + num4];
				array[0] = 8;
				array[1] = channel;
				array[2] = 1;
				array[3] = 4;
				WriteIntBE(array, 4, array.Length);
				WriteIntBE(array, 8, seqStart + i);
				WriteIntBE(array, 12, seqStart);
				WriteIntBE(array, 16, num2);
				WriteIntBE(array, 20, i);
				WriteIntBE(array, 24, num);
				WriteIntBE(array, 28, num3);
				Buffer.BlockCopy(payload, num3, array, 32, num4);
				list.Add(array);
				num3 += num4;
			}
			return list;
		}

		public static byte[] BuildVerifyConnect(byte[] connectCmd)
		{
			byte[] array = new byte[connectCmd.Length];
			Buffer.BlockCopy(connectCmd, 0, array, 0, connectCmd.Length);
			array[0] = 3;
			array[2] = 0;
			WriteIntBE(array, 8, 0);
			return array;
		}

		public static byte[] BuildDisconnect()
		{
			byte[] array = new byte[12]
			{
				4, 0, 0, 4, 0, 0, 0, 0, 0, 0,
				0, 0
			};
			WriteIntBE(array, 4, 12);
			WriteIntBE(array, 8, 0);
			return array;
		}

		public static byte[] BuildPing()
		{
			byte[] array = new byte[12]
			{
				5, 0, 0, 4, 0, 0, 0, 0, 0, 0,
				0, 0
			};
			WriteIntBE(array, 4, 12);
			WriteIntBE(array, 8, 0);
			return array;
		}
	}
	public static class LanDiscovery
	{
		private sealed class DiscoveryCacheEntry
		{
			public string Ip;

			public int Tick;
		}

		private const int Port = 47977;

		private const string QueryPrefix = "REPOQ|";

		private const string AnswerPrefix = "REPOA|";

		private const int CacheTtlMs = 600000;

		private static readonly Dictionary<ulong, DiscoveryCacheEntry> Cache = new Dictionary<ulong, DiscoveryCacheEntry>();

		private static readonly object CacheSync = new object();

		private static readonly object ResponderSync = new object();

		private static volatile bool _hostRunning;

		private static UdpClient? _responderSocket;

		private static Thread? _responderThread;

		private static ulong _responderHostId;

		private static string? _responderToken;

		public static void InvalidateCache()
		{
			lock (CacheSync)
			{
				Cache.Clear();
			}
		}

		public static string FindHostCached(ulong hostSteamId, int timeoutMs, string token, Func<bool>? cancelled = null)
		{
			int tickCount = Environment.TickCount;
			lock (CacheSync)
			{
				if (Cache.TryGetValue(hostSteamId, out var value) && tickCount - value.Tick < 600000)
				{
					return value.Ip;
				}
			}
			string text = FindHost(hostSteamId, timeoutMs, token, cancelled);
			if (cancelled != null && cancelled())
			{
				return text;
			}
			lock (CacheSync)
			{
				Cache[hostSteamId] = new DiscoveryCacheEntry
				{
					Ip = text,
					Tick = Environment.TickCount
				};
				return text;
			}
		}

		public static void StartHostResponder(ulong hostSteamId, string token)
		{
			lock (ResponderSync)
			{
				if (_responderThread != null && _responderThread.IsAlive)
				{
					for (int i = 0; i < 10; i++)
					{
						if (!_responderThread.IsAlive)
						{
							break;
						}
						Thread.Sleep(10);
					}
					if (_responderThread.IsAlive)
					{
						if (Plugin.Verbose)
						{
							ManualLogSource log = Plugin.Log;
							if (log != null)
							{
								log.LogWarning((object)"[LanDiscovery] previous responder still alive - responder not restarted");
							}
						}
						return;
					}
				}
				_hostRunning = true;
				_responderHostId = hostSteamId;
				_responderToken = token;
				_responderThread = new Thread(ResponderLoop)
				{
					IsBackground = true,
					Name = "RepoSteamTransport lan-responder"
				};
				_responderThread.Start();
			}
			if (Plugin.Verbose)
			{
				ManualLogSource log2 = Plugin.Log;
				if (log2 != null)
				{
					log2.LogInfo((object)string.Format("[LanDiscovery] host responder listening on udp/{0} (steamId {1}, token {2})", 47977, hostSteamId, string.IsNullOrEmpty(token) ? "none" : "set"));
				}
			}
		}

		public static void StopHostResponder()
		{
			lock (ResponderSync)
			{
				_hostRunning = false;
				_responderToken = null;
				try
				{
					_responderSocket?.Close();
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						ManualLogSource log = Plugin.Log;
						if (log != null)
						{
							log.LogWarning((object)("[LanDiscovery] responder close failed: " + ex.Message));
						}
					}
				}
				_responderSocket = null;
			}
		}

		private static void ResponderLoop()
		{
			UdpClient udpClient = null;
			try
			{
				lock (ResponderSync)
				{
					if (!_hostRunning)
					{
						return;
					}
					udpClient = (_responderSocket = new UdpClient(47977));
				}
				while (_hostRunning)
				{
					IPEndPoint iPEndPoint = null;
					byte[] data;
					try
					{
						iPEndPoint = new IPEndPoint(IPAddress.Any, 0);
						data = udpClient.Receive(ref iPEndPoint);
					}
					catch (SocketException ex)
					{
						if (ex.SocketErrorCode == SocketError.Interrupted)
						{
							break;
						}
						continue;
					}
					catch (ObjectDisposedException)
					{
						break;
					}
					catch (Exception ex3)
					{
						if (Plugin.Verbose)
						{
							ManualLogSource log = Plugin.Log;
							if (log != null)
							{
								log.LogWarning((object)("[LanDiscovery] receive failed: " + ex3.Message));
							}
						}
						continue;
					}
					if (iPEndPoint != null)
					{
						TryAnswer(udpClient, iPEndPoint, data);
					}
				}
			}
			catch (Exception ex4)
			{
				if (Plugin.Verbose)
				{
					ManualLogSource log2 = Plugin.Log;
					if (log2 != null)
					{
						log2.LogWarning((object)("[LanDiscovery] host responder stopped: " + ex4.Message));
					}
				}
			}
			finally
			{
				lock (ResponderSync)
				{
					if (_responderSocket == udpClient)
					{
						_responderSocket = null;
					}
				}
				try
				{
					udpClient?.Close();
				}
				catch (Exception ex5)
				{
					if (Plugin.Verbose)
					{
						ManualLogSource log3 = Plugin.Log;
						if (log3 != null)
						{
							log3.LogWarning((object)("[LanDiscovery] responder socket dispose failed: " + ex5.Message));
						}
					}
				}
			}
		}

		private static void TryAnswer(UdpClient udp, IPEndPoint requester, byte[] data)
		{
			ulong responderHostId;
			string responderToken;
			lock (ResponderSync)
			{
				if (!_hostRunning)
				{
					return;
				}
				responderHostId = _responderHostId;
				responderToken = _responderToken;
			}
			string text = Encoding.ASCII.GetString(data);
			if (!text.StartsWith("REPOQ|", StringComparison.Ordinal))
			{
				return;
			}
			string[] array = text.Substring("REPOQ|".Length).Trim().Split('|');
			string text2 = array[0];
			if (text2.Length <= 0 || !ulong.TryParse(text2, out var result) || result == 0L || result == responderHostId)
			{
				string text3 = ((array.Length > 1) ? array[1] : "");
				if ((!SdrFabric.TryGetHostLanToken(out var token) || !(text3 != token)) && SdrFabric.UdpPhaseListenersBound())
				{
					string s = "REPOA|" + responderHostId + (string.IsNullOrEmpty(responderToken) ? "" : ("|" + responderToken));
					byte[] bytes = Encoding.ASCII.GetBytes(s);
					udp.Send(bytes, bytes.Length, requester);
				}
			}
		}

		private static List<IPEndPoint> BroadcastEndpoints()
		{
			List<IPEndPoint> list = new List<IPEndPoint>();
			HashSet<string> seen = new HashSet<string>(StringComparer.Ordinal);
			try
			{
				Add(new IPEndPoint(IPAddress.Broadcast, 47977));
				NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
				foreach (NetworkInterface networkInterface in allNetworkInterfaces)
				{
					if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback)
					{
						continue;
					}
					foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses)
					{
						if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork)
						{
							byte[] addressBytes = unicastAddress.Address.GetAddressBytes();
							if (addressBytes.Length == 4 && addressBytes[3] != byte.MaxValue)
							{
								addressBytes[3] = byte.MaxValue;
								Add(new IPEndPoint(new IPAddress(addressBytes), 47977));
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (Plugin.Verbose)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[LanDiscovery] interface scan failed: " + ex.Message));
					}
				}
			}
			return list;
			void Add(IPEndPoint ep)
			{
				string item = ep.Address.ToString();
				if (seen.Add(item))
				{
					list.Add(ep);
				}
			}
		}

		public static string FindHost(ulong hostSteamId, int timeoutMs, string token, Func<bool>? cancelled = null)
		{
			try
			{
				using UdpClient udpClient = new UdpClient();
				udpClient.Client.ReceiveTimeout = 400;
				List<IPEndPoint> list = BroadcastEndpoints();
				byte[] bytes = Encoding.ASCII.GetBytes("REPOQ|" + hostSteamId + (string.IsNullOrEmpty(token) ? "" : ("|" + token)));
				int num = Environment.TickCount + timeoutMs;
				while (num - Environment.TickCount > 0)
				{
					if (cancelled != null && cancelled())
					{
						return null;
					}
					foreach (IPEndPoint item in list)
					{
						udpClient.Send(bytes, bytes.Length, item);
					}
					while (true)
					{
						IPEndPoint remoteEP = null;
						byte[] bytes2;
						try
						{
							bytes2 = udpClient.Receive(ref remoteEP);
						}
						catch (SocketException ex)
						{
							if (ex.SocketErrorCode == SocketError.TimedOut)
							{
								break;
							}
							throw;
						}
						string text = Encoding.ASCII.GetString(bytes2);
						if (text.StartsWith("REPOA|", StringComparison.Ordinal))
						{
							string[] array = text.Substring("REPOA|".Length).Trim().Split('|');
							if (array.Length >= 1 && ulong.TryParse(array[0], out var result) && result == hostSteamId && remoteEP != null && (array.Length <= 1 || string.IsNullOrEmpty(array[1]) || string.IsNullOrEmpty(token) || !(array[1] != token)))
							{
								return remoteEP.Address.ToString();
							}
						}
					}
				}
			}
			catch (Exception ex2)
			{
				if (Plugin.Verbose)
				{
					ManualLogSource log = Plugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("[LanDiscovery] probe failed: " + ex2.Message));
					}
				}
			}
			return null;
		}
	}
	public static class PhasePorts
	{
		public const int Master = 7777;

		public const int Game = 7778;

		public const int Voice = 7779;
	}
	public class HostListener : SocketManager
	{
		public int VirtualPort;

		public ConcurrentQueue<RemoteDatagram> Inbox = new ConcurrentQueue<RemoteDatagram>();

		public ConcurrentDictionary<uint, Connection> Connections = new ConcurrentDictionary<uint, Connection>();

		public readonly ConcurrentDictionary<uint, ulong> RemoteSteamIds = new ConcurrentDictionary<uint, ulong>();

		private int _loggedMessages;

		private string Key(ulong steamId)
		{
			return $"sdr:{steamId}:{VirtualPort}";
		}

		public override void OnConnecting(Connection connection, ConnectionInfo info)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			NetIdentity identity;
			if (Plugin.Verbose)
			{
				ManualLogSource log = Plugin.Log;
				object arg = VirtualPort;
				identity = ((ConnectionInfo)(ref info)).Identity;
				log.LogInfo((object)$"[SDR host:{arg}] incoming from {((NetIdentity)(ref identity)).SteamId} -> accept");
			}
			Connections[((Connection)(ref connection)).Id] = connection;
			ConcurrentDictionary<uint, ulong> remoteSteamIds = RemoteSteamIds;
			uint id = ((Connection)(ref connection)).Id;
			identity = ((ConnectionInfo)(ref info)).Identity;
			remoteSteamIds[id] = SteamId.op_Implicit(((NetIdentity)(ref identity)).SteamId);
			Result val = ((Connection)(ref connection)).Accept();
			if (Plugin.Verbose)
			{
				Plugin.Log.LogInfo((object)$"[SDR host:{VirtualPort}] accept result: {val}");
			}
		}

		public override void OnConnected(Connection connection, ConnectionInfo info)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			((SocketManager)this).OnConnected(connection, info);
			if (Plugin.Verbose)
			{
				ManualLogSource log = Plugin.Log;
				object arg = VirtualPort;
				NetIdentity identity = ((ConnectionInfo)(ref info)).Identity;
				log.LogInfo((object)$"[SDR host:{arg}] connection established with {((NetIdentity)(ref identity)).SteamId}");
			}
		}

		public override void OnDisconnected(Connection connection, ConnectionInfo info)
		{
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: 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_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Verbose)
			{
				ManualLogSource log = Plugin.Log;
				object[] obj = new object[4] { VirtualPort, null, null, null };
				NetIdentity identity = ((ConnectionInfo)(ref info)).Identity;
				obj[1] = ((NetIdentity)(ref identity)).SteamId;
				obj[2] = ((ConnectionInfo)(ref info)).State;
				obj[3] = ((ConnectionInfo)(ref info)).EndReason;
				log.LogWarning((object)string.Format("[SDR host:{0}] connection {1} closed ({2}, end={3})", obj));
			}
			Connections.TryRemove(((Connection)(ref connection)).Id, out var _);
			RemoteSteamIds.TryRemove(((Connection)(ref connection)).Id, out var _);
			VirtualServer.EvictSession($"sdr:c{((Connection)(ref connection)).Id}:{VirtualPort}", $"state={((ConnectionInfo)(ref info)).State}, end={((ConnectionInfo)(ref info)).EndReason}");
			((SocketManager)this).OnDisconnected(connection, info);
		}

		public override void OnMessage(Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			byte[] array = new byte[size];
			Marshal.Copy(data, array, 0, size);
			Inbox.Enqueue(new RemoteDatagram(array, SteamId.op_Implicit(((NetIdentity)(ref identity)).SteamId), VirtualPort, connection));
			if (_loggedMessages < 8)
			{
				_loggedMessages++;
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SDR host:{VirtualPort}] got datagram #{_loggedMessages}: {size}B from {((NetIdentity)(ref identity)).SteamId}");
				}
			}
		}
	}
	public readonly struct RemoteDatagram
	{
		public readonly byte[] Data;

		public readonly ulong SteamId;

		public readonly int VirtualPort;

		public readonly Connection Connection;

		public string SourceKey => $"sdr:c{((Connection)(ref Connection)).Id}:{VirtualPort}";

		public RemoteDatagram(byte[] data, ulong steamId, int virtualPort, Connection connection)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			Data = data;
			SteamId = steamId;
			VirtualPort = virtualPort;
			Connection = connection;
		}
	}
	public class HostConnection : ConnectionManager
	{
		public ConcurrentQueue<byte[]> Inbox = new ConcurrentQueue<byte[]>();

		public bool Established;

		public Action OnEstablished;

		public Action OnClosed;

		public Action<byte[]> DataSink;

		public SteamSdrSocket Owner;

		public override void OnConnected(ConnectionInfo info)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Verbose)
			{
				ManualLogSource log = Plugin.Log;
				NetIdentity identity = ((ConnectionInfo)(ref info)).Identity;
				log.LogInfo((object)$"[SDR client] connected to host {((NetIdentity)(ref identity)).SteamId}");
			}
			((ConnectionManager)this).OnConnected(info);
			Established = true;
			OnEstablished?.Invoke();
		}

		public override void OnDisconnected(ConnectionInfo info)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Verbose)
			{
				Plugin.Log.LogWarning((object)$"[SDR client] disconnected from host ({((ConnectionInfo)(ref info)).State}, end={((ConnectionInfo)(ref info)).EndReason}, established={Established}, owner={Owner != null})");
			}
			bool established = Established;
			Established = false;
			SdrFabric.ClientDied(this);
			if (Owner != null)
			{
				if (established)
				{
					OnClosed?.Invoke();
				}
				else if (Owner == null || !Owner.RetryConnect())
				{
					OnClosed?.Invoke();
				}
			}
		}

		public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel)
		{
			byte[] array = new byte[size];
			Marshal.Copy(data, array, 0, size);
			Inbox.Enqueue(array);
		}
	}
	public static class SdrFabric
	{
		private sealed class QueuedAction
		{
			public Func<object> Work;

			public TaskCompletionSource<object> Done;

			public volatile bool Abandoned;
		}

		private sealed class LoopbackPipe
		{
			public int SocketId;

			public int Port;

			public string Key;

			public ConcurrentQueue<byte[]> ClientToServer = new ConcurrentQueue<byte[]>();

			public Action<byte[]> Sink;
		}

		internal static readonly object Sync = new object();

		public static ulong HostSteamId;

		private static readonly ConcurrentQueue<QueuedAction> UnityActions = new ConcurrentQueue<QueuedAction>();

		private const int UnityActionWaitSliceMs = 100;

		private const int UnityActionWaitTimeoutMs = 5000;

		private static int _nextLoopbackSocketId;

		private static readonly List<LoopbackPipe> Loopbacks = new List<LoopbackPipe>();

		private static readonly List<HostListener> Hosts = new List<HostListener>();

		private static readonly List<HostConnection> Clients = new List<HostConnection>();

		private static readonly List<UdpClient> UdpListeners = new List<UdpClient>();

		private static readonly ConcurrentQueue<(string Key, byte[] Data, Action<byte[]> Send, string Advertise)> UdpInbox = new ConcurrentQueue<(string, byte[], Action<byte[]>, string)>();

		private const int UdpInboxMaxQueued = 256;

		private const int UdpInboxDrainPerFrame = 128;

		private static int _udpInboxCount;

		private const int PhaseListenerCount = 3;

		private static volatile string _hostToken;

		private static volatile bool _hostTokenPublished;

		private static volatile string _hostLobbyToken;

		private const int HostTokenBytes = 8;

		private const int ZtPublishIntervalMs = 3000;

		private static int _ztPublishTick = Environment.TickCount - 3000;

		public const string UdpHelloPrefix = "RREPO1|";

		public const string UdpHelloAckPrefix = "RREPO2|";

		private static readonly byte[] UdpHelloPrefixBytes = Encoding.ASCII.GetBytes("RREPO1|");

		private const int UdpAllowlistTtlMs = 300000;

		private static readonly Dictionary<string, int> UdpAllowlist = new Dictionary<string, int>();

		private static readonly HashSet<string> UdpSessionKeys = new HashSet<string>();

		private const int UdpDropLogIntervalMs = 2000;

		private static int _udpDropLogTick;

		private static List<IPAddress> _localV4Cache;

		private static int _localV4CacheTick = Environment.TickCount - 10000;

		private static int _receivedBatches;

		private static int _pumpErrorCount;

		private static int _connDumpTick;

		private static int _lastPumpTick = Environment.TickCount;

		private const int LongFrameSuspensionMs = 2000;

		private static readonly object InternalSingleton = ResolveSingleton();

		public static ulong LocalSteamId => SteamId.op_Implicit(SteamClient.IsValid ? SteamClient.SteamId : SteamId.op_Implicit(0uL));

		internal static T RunOnUnityThread<T>(Func<T> work, CancellationToken token)
		{
			QueuedAction queuedAction = new QueuedAction
			{
				Work = () => token.IsCancellationRequested ? ((object)default(T)) : ((object)work()),
				Done = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously)
			};
			UnityActions.Enqueue(queuedAction);
			int num2;
			for (int num = 0; num < 5000; num += num2)
			{
				if (queuedAction.Done.Task.IsCompleted)
				{
					break;
				}
				if (token.IsCancellationRequested)
				{
					return default(T);
				}
				num2 = Math.Min(100, 5000 - num);
				queuedAction.Done.Task.Wait(num2);
			}
			if (!queuedAction.Done.Task.IsCompleted)
			{
				queuedAction.Abandoned = true;
				return default(T);
			}
			object result = queuedAction.Done.Task.Result;
			if (result is T)
			{
				return (T)result;
			}
			return default(T);
		}

		internal static void PostToUnityThread(Action work)
		{
			UnityActions.Enqueue(new QueuedAction
			{
				Work = delegate
				{
					work();
					return (object)null;
				},
				Done = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously)
			});
		}

		public static ulong GetRemotePeerSteamId(string sourceKey)
		{
			try
			{
				if (string.IsNullOrEmpty(sourceKey))
				{
					return 0uL;
				}
				string[] array = sourceKey.Split(':');
				if (array.Length < 2 || array[0] != "sdr")
				{
					return 0uL;
				}
				if (ulong.TryParse(array[1], out var result))
				{
					return result;
				}
				string text = array[1];
				if (text.Length < 2 || text[0] != 'c' || !uint.TryParse(text.Substring(1), out var result2))
				{
					return 0uL;
				}
				lock (Sync)
				{
					foreach (HostListener host in Hosts)
					{
						if (host.RemoteSteamIds.TryGetValue(result2, out var value))
						{
							return value;
						}
					}
				}
			}
			catch
			{
			}
			return 0uL;
		}

		public static void StartLoopback(int socketId, int port, Action<byte[]> sink)
		{
			lock (Sync)
			{
				foreach (LoopbackPipe loopback in Loopbacks)
				{
					if (loopback.SocketId == socketId)
					{
						loopback.Port = port;
						loopback.Key = $"loopback:{socketId}:{port}";
						loopback.Sink = sink;
						return;
					}
				}
				Loopbacks.Add(new LoopbackPipe
				{
					SocketId = socketId,
					Port = port,
					Key = $"loopback:{socketId}:{port}",
					Sink = sink
				});
			}
			StartHost();
		}

		public static void LoopbackSend(int socketId, int port, byte[] datagram)
		{
			lock (Sync)
			{
				foreach (LoopbackPipe loopback in Loopbacks)
				{
					if (loopback.SocketId == socketId)
					{
						loopback.ClientToServer.Enqueue(datagram);
						break;
					}
				}
			}
		}

		public static void UnregisterLoopbackSink(int socketId)
		{
			lock (Sync)
			{
				for (int num = Loopbacks.Count - 1; num >= 0; num--)
				{
					if (Loopbacks[num].SocketId == socketId)
					{
						Loopbacks.RemoveAt(num);
					}
				}
			}
		}

		public static bool StartHost()
		{
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			lock (Sync)
			{
				if (Hosts.Count > 0)
				{
					return true;
				}
			}
			if (!ModConfig.IsHostRole())
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)"[SDR] not the host role - remote-facing stack not raised");
				}
				return false;
			}
			if (!SteamClient.IsValid)
			{
				Plugin.Log.LogError((object)"[SDR] SteamClient is not initialized; cannot host.");
				return false;
			}
			try
			{
				_hostToken = GenerateHostToken();
				_hostTokenPublished = false;
				int[] array = new int[3] { 7777, 7778, 7779 };
				foreach (int num in array)
				{
					HostListener hostListener = SteamNetworkingSockets.CreateRelaySocket<HostListener>(num);
					hostListener.VirtualPort = num;
					lock (Sync)
					{
						Hosts.Add(hostListener);
					}
				}
				HostSteamId = SteamId.op_Implicit(SteamClient.SteamId);
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SDR] host listening on relay ports {7777}/{7778}/{7779} (SteamId {HostSteamId})");
				}
				LanDiscovery.StartHostResponder(HostSteamId, _hostToken);
				int[] array2 = new int[3] { 7777, 7778, 7779 };
				foreach (int port in array2)
				{
					StartUdpListener(port);
				}
				return true;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[SDR] failed to create relay socket: {arg}");
				return false;
			}
		}

		private static void StartUdpListener(int port)
		{
			try
			{
				UdpClient item = new UdpClient(port);
				lock (Sync)
				{
					UdpListeners.Add(item);
				}
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SDR] direct UDP listener on :{port} (LAN / local test mode)");
				}
			}
			catch (Exception ex)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)$"[SDR] UDP listener on :{port} unavailable: {ex.Message}");
				}
			}
		}

		internal static bool UdpPhaseListenersBound()
		{
			lock (Sync)
			{
				return UdpListeners.Count >= 3;
			}
		}

		public static void StopHost()
		{
			HostListener[] array;
			UdpClient[] array2;
			string[] array3;
			lock (Sync)
			{
				array = Hosts.ToArray();
				Hosts.Clear();
				array2 = UdpListeners.ToArray();
				UdpListeners.Clear();
				UdpAllowlist.Clear();
				array3 = new string[UdpSessionKeys.Count];
				UdpSessionKeys.CopyTo(array3);
				UdpSessionKeys.Clear();
				_hostToken = null;
				_hostTokenPublished = false;
				_hostLobbyToken = null;
				_ztPublishTick = Environment.TickCount - 3000;
				(string, byte[], Action<byte[]>, string) result;
				while (UdpInbox.TryDequeue(out result))
				{
					Interlocked.Decrement(ref _udpInboxCount);
				}
			}
			HostListener[] array4 = array;
			foreach (HostListener hostListener in array4)
			{
				foreach (KeyValuePair<uint, Connection> connection in hostListener.Connections)
				{
					VirtualServer.EvictSession($"sdr:c{connection.Key}:{hostListener.VirtualPort}", "host stack stopped");
				}
				hostListener.Connections.Clear();
				hostListener.RemoteSteamIds.Clear();
				RemoteDatagram result2;
				while (hostListener.Inbox.TryDequeue(out result2))
				{
				}
				try
				{
					((SocketManager)hostListener).Close();
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)$"[SDR] relay socket :{hostListener.VirtualPort} close failed: {ex.Message}");
					}
				}
			}
			string[] array5 = array3;
			foreach (string sourceKey in array5)
			{
				VirtualServer.EvictSession(sourceKey, "host stack stopped");
			}
			UdpClient[] array6 = array2;
			foreach (UdpClient udpClient in array6)
			{
				try
				{
					udpClient.Close();
				}
				catch (Exception ex2)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)("[SDR] udp listener close failed: " + ex2.Message));
					}
				}
			}
			LanDiscovery.StopHostResponder();
			LanDiscovery.InvalidateCache();
			if (array.Length != 0 && Plugin.Verbose)
			{
				Plugin.Log.LogInfo((object)"[SDR] host stack stopped: relay sockets closed, UDP listeners unbound, LAN responder stopped, allowlist cleared");
			}
		}

		private static string GenerateHostToken()
		{
			byte[] array = new byte[8];
			using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
			{
				randomNumberGenerator.GetBytes(array);
			}
			StringBuilder stringBuilder = new StringBuilder(16);
			byte[] array2 = array;
			foreach (byte b in array2)
			{
				stringBuilder.Append(b.ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		internal static bool TryGetHostLanToken(out string token)
		{
			token = _hostToken;
			if (!string.IsNullOrEmpty(token))
			{
				return _hostTokenPublished;
			}
			return false;
		}

		private static void RefreshHostLobbyToken()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (ModConfig.TryGetLobby(out var lobby))
				{
					SteamId id = ((Lobby)(ref lobby)).Id;
					if (((SteamId)(ref id)).IsValid)
					{
						string data = ((Lobby)(ref lobby)).GetData("rtok");
						_hostLobbyToken = (string.IsNullOrEmpty(data) ? null : data);
						return;
					}
				}
				_hostLobbyToken = null;
			}
			catch (Exception ex)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)("[SDR] rtok refresh failed: " + ex.Message));
				}
			}
		}

		public static string GetHostLobbyToken()
		{
			return _hostLobbyToken;
		}

		private static void PumpZtPublish()
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			int tickCount = Environment.TickCount;
			if (tickCount - _ztPublishTick < 3000)
			{
				return;
			}
			RefreshHostLobbyToken();
			lock (Sync)
			{
				if (Hosts.Count == 0)
				{
					return;
				}
			}
			_ztPublishTick = tickCount;
			try
			{
				if (ModConfig.TryGetLobby(out var lobby))
				{
					SteamId id = ((Lobby)(ref lobby)).Id;
					if (((SteamId)(ref id)).IsValid)
					{
						string zeroTierIp = ModConfig.GetZeroTierIp();
						if (!string.IsNullOrEmpty(zeroTierIp))
						{
							string data = ((Lobby)(ref lobby)).GetData("ztip");
							if (data != zeroTierIp && ((Lobby)(ref lobby)).SetData("ztip", zeroTierIp) && Plugin.Verbose)
							{
								Plugin.Log.LogInfo((object)("[ZT] published ZeroTier address to lobby: " + zeroTierIp));
							}
						}
						string hostToken = _hostToken;
						if (!string.IsNullOrEmpty(hostToken))
						{
							string data2 = ((Lobby)(ref lobby)).GetData("rtok");
							if (data2 == hostToken)
							{
								_hostTokenPublished = true;
							}
							else if (((Lobby)(ref lobby)).SetData("rtok", hostToken))
							{
								_hostTokenPublished = true;
								if (Plugin.Verbose)
								{
									Plugin.Log.LogInfo((object)"[ZT] published host session token to lobby (rtok)");
								}
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)("[ZT] lobby publish failed: " + ex.Message));
				}
			}
			SweepUdpAllowlist(tickCount);
		}

		private static void LogUdpDropVerbose(string message)
		{
			int tickCount = Environment.TickCount;
			if (tickCount - _udpDropLogTick >= 2000)
			{
				_udpDropLogTick = tickCount;
				if (Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)message);
				}
			}
		}

		private static bool IsUdpHello(byte[] datagram)
		{
			if (datagram == null || datagram.Length < UdpHelloPrefixBytes.Length)
			{
				return false;
			}
			for (int i = 0; i < UdpHelloPrefixBytes.Length; i++)
			{
				if (datagram[i] != UdpHelloPrefixBytes[i])
				{
					return false;
				}
			}
			return true;
		}

		private static bool UdpPeerAllowed(string peerKey, int now)
		{
			lock (Sync)
			{
				int value;
				return UdpAllowlist.TryGetValue(peerKey, out value) && now - value <= 300000;
			}
		}

		private static void AuthorizeUdpPeer(string peerKey, int now)
		{
			lock (Sync)
			{
				UdpAllowlist[peerKey] = now;
			}
		}

		private static void SweepUdpAllowlist(int now)
		{
			lock (Sync)
			{
				if (UdpAllowlist.Count == 0)
				{
					return;
				}
				List<string> list = null;
				foreach (KeyValuePair<string, int> item in UdpAllowlist)
				{
					if (now - item.Value > 300000)
					{
						(list ?? (list = new List<string>())).Add(item.Key);
					}
				}
				if (list == null)
				{
					return;
				}
				foreach (string item2 in list)
				{
					UdpAllowlist.Remove(item2);
				}
			}
		}

		private static void HandleUdpHello(UdpClient listener, IPEndPoint peer, byte[] datagram, int now, int listenPort)
		{
			string text = Encoding.ASCII.GetString(datagram, UdpHelloPrefixBytes.Length, datagram.Length - UdpHelloPrefixBytes.Length);
			if (TryGetHostLanToken(out var token) && text != token)
			{
				LogUdpDropVerbose($"[SDR] udp hello rejected (token mismatch): {peer} on :{listenPort}");
				return;
			}
			string text2 = peer.ToString();
			AuthorizeUdpPeer(text2, now);
			try
			{
				byte[] bytes = Encoding.ASCII.GetBytes("RREPO2|" + HostSteamId);
				listener.Send(bytes, bytes.Length, peer);
			}
			catch (Exception ex)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)("[SDR] udp hello ack failed: " + ex.Message));
				}
			}
			if (Plugin.Verbose)
			{
				Plugin.Log.LogInfo((object)$"[SDR] udp hello accepted: {text2} on :{listenPort} (allowlisted, window {300}s)");
			}
		}

		public static void PumpUdpLoop()
		{
			while (true)
			{
				try
				{
					PumpUdp();
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)("[udp] background pump failed: " + ex.Message));
					}
				}
				Thread.Sleep(2);
			}
		}

		private static void PumpUdp()
		{
			UdpClient[] array;
			lock (Sync)
			{
				array = UdpListeners.ToArray();
			}
			UdpClient[] array2 = array;
			foreach (UdpClient listener in array2)
			{
				try
				{
					PumpUdpListener(listener);
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)("[SDR] udp pump listener skipped: " + ex.Message));
					}
				}
			}
		}

		private static void PumpUdpListener(UdpClient listener)
		{
			int port = ((IPEndPoint)listener.Client.LocalEndPoint).Port;
			while (listener.Available > 0)
			{
				try
				{
					IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
					byte[] array = listener.Receive(ref remoteEP);
					int tickCount = Environment.TickCount;
					if (IsUdpHello(array))
					{
						HandleUdpHello(listener, remoteEP, array, tickCount, port);
						continue;
					}
					string text = remoteEP.ToString();
					if (!UdpPeerAllowed(text, tickCount))
					{
						LogUdpDropVerbose($"[SDR] udp drop (endpoint not allowlisted): {text} on :{port}");
						continue;
					}
					AuthorizeUdpPeer(text, tickCount);
					string item = ResolveAdvertiseIp(remoteEP.Address);
					string text2 = $"udp:{port}:{remoteEP}";
					IPEndPoint peer = remoteEP;
					lock (Sync)
					{
						UdpSessionKeys.Add(text2);
					}
					if (Interlocked.Increment(ref _udpInboxCount) > 256)
					{
						Interlocked.Decrement(ref _udpInboxCount);
						LogUdpDropVerbose($"[SDR] udp inbox full (>{256}) - datagram dropped: {text} on :{port}");
					}
					else
					{
						UdpInbox.Enqueue((text2, array, delegate(byte[] b)
						{
							listener.Send(b, b.Length, peer);
						}, item));
					}
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogError((object)("[SDR] udp pump: " + ex.Message));
					}
					break;
				}
			}
		}

		private static string ResolveAdvertiseIp(IPAddress remote)
		{
			int tickCount = Environment.TickCount;
			if (_localV4Cache == null || tickCount - _localV4CacheTick > 10000)
			{
				List<IPAddress> list = new List<IPAddress>();
				try
				{
					NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
					foreach (NetworkInterface networkInterface in allNetworkInterfaces)
					{
						if (networkInterface.OperationalStatus != OperationalStatus.Up)
						{
							continue;
						}
						foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses)
						{
							if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.Loopback.Equals(unicastAddress.Address))
							{
								list.Add(unicastAddress.Address);
							}
						}
					}
				}
				catch
				{
				}
				_localV4Cache = list;
				_localV4CacheTick = tickCount;
			}
			if (remote != null && remote.AddressFamily == AddressFamily.InterNetwork)
			{
				byte[] addressBytes = remote.GetAddressBytes();
				if (addressBytes.Length == 4)
				{
					foreach (IPAddress item in _localV4Cache)
					{
						byte[] addressBytes2 = item.GetAddressBytes();
						if (addressBytes2.Length == 4 && addressBytes2[0] == addressBytes[0] && addressBytes2[1] == addressBytes[1] && addressBytes2[2] == addressBytes[2])
						{
							return item.ToString();
						}
					}
				}
			}
			if (_localV4Cache.Count <= 0)
			{
				return null;
			}
			return _localV4Cache[0].ToString();
		}

		public static void Pump()
		{
			//IL_02f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0451: Unknown result type (might be due to invalid IL or missing references)
			//IL_0456: Unknown result type (might be due to invalid IL or missing references)
			int tickCount = Environment.TickCount;
			int num = tickCount - _lastPumpTick;
			_lastPumpTick = tickCount;
			if (num > 2000)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SDR] long frame {num}ms - eviction/give-up timers suspended for one cycle");
				}
				VirtualServer.SuspendTimersOneCycle();
			}
			int num2 = 0;
			(string, byte[], Action<byte[]>, string) result;
			while (num2 < 128 && UdpInbox.TryDequeue(out result))
			{
				Interlocked.Decrement(ref _udpInboxCount);
				num2++;
				try
				{
					VirtualServer.HandleClientDatagram(result.Item1, result.Item2, result.Item3, result.Item4);
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"[SDR] udp datagram processing failed ({result.Item1}): {arg}");
				}
			}
			QueuedAction result2;
			while (UnityActions.TryDequeue(out result2))
			{
				if (result2.Abandoned)
				{
					result2.Done.TrySetResult(null);
					continue;
				}
				object result3 = null;
				try
				{
					result3 = result2.Work();
				}
				catch (Exception arg2)
				{
					Plugin.Log.LogError((object)$"[SDR] queued steam action failed: {arg2}");
				}
				result2.Done.TrySetResult(result3);
			}
			HostListener[] array;
			HostConnection[] array2;
			lock (Sync)
			{
				array = Hosts.ToArray();
				array2 = Clients.ToArray();
			}
			LoopbackPipe[] array3;
			lock (Sync)
			{
				array3 = Loopbacks.ToArray();
			}
			LoopbackPipe[] array4 = array3;
			foreach (LoopbackPipe loopbackPipe in array4)
			{
				if (loopbackPipe.ClientToServer.IsEmpty)
				{
					continue;
				}
				string key = loopbackPipe.Key;
				Action<byte[]> sink = loopbackPipe.Sink;
				byte[] result4;
				while (loopbackPipe.ClientToServer.TryDequeue(out result4))
				{
					if (sink != null)
					{
						VirtualServer.HandleClientDatagram(key, result4, delegate(byte[] b)
						{
							sink(b);
						});
					}
				}
			}
			try
			{
				int num3 = 0;
				HostListener[] array5 = array;
				foreach (HostListener hostListener in array5)
				{
					num3 += ((SocketManager)hostListener).Receive(32, true);
				}
				HostConnection[] array6 = array2;
				foreach (HostConnection hostConnection in array6)
				{
					num3 += ((ConnectionManager)hostConnection).Receive(32, true);
				}
				if (Environment.TickCount - _connDumpTick > 2000 && ConnectionsAny())
				{
					_connDumpTick = Environment.TickCount;
					HostListener[] array7 = array;
					foreach (HostListener hostListener2 in array7)
					{
						foreach (KeyValuePair<uint, Connection> connection in hostListener2.Connections)
						{
							try
							{
								if (Plugin.Verbose)
								{
									ManualLogSource log = Plugin.Log;
									object arg3 = hostListener2.VirtualPort;
									object arg4 = connection.Key;
									Connection value = connection.Value;
									log.LogInfo((object)$"[SDR diag] :{arg3} handle={arg4}\n{((Connection)(ref value)).DetailedStatus()}");
								}
							}
							catch (Exception ex)
							{
								if (Plugin.Verbose)
								{
									Plugin.Log.LogInfo((object)$"[SDR diag] :{hostListener2.VirtualPort} handle={connection.Key} status query failed: {ex.Message}");
								}
							}
						}
					}
				}
				if (num3 > 0)
				{
					_receivedBatches++;
					if (_receivedBatches <= 8 && Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)$"[SDR] receive pump: {num3} message(s) pushed to callbacks");
					}
				}
			}
			catch (Exception ex2)
			{
				if (_pumpErrorCount < 3 && Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)$"[SDR] receive pump failed: {ex2}\n{ex2.StackTrace}");
				}
				_pumpErrorCount++;
			}
			HostListener[] array8;
			HostConnection[] array9;
			lock (Sync)
			{
				array8 = Hosts.ToArray();
				array9 = Clients.ToArray();
			}
			HostListener[] array10 = array8;
			foreach (HostListener hostListener3 in array10)
			{
				RemoteDatagram result5;
				while (hostListener3.Inbox.TryDequeue(out result5))
				{
					Connection conn = result5.Connection;
					VirtualServer.HandleClientDatagram(result5.SourceKey, result5.Data, delegate(byte[] b)
					{
						//IL_0009: Unknown result type (might be due to invalid IL or missing references)
						((Connection)(ref conn)).SendMessage(b, (SendType)1, (ushort)0);
					});
				}
			}
			HostConnection[] array11 = array9;
			foreach (HostConnection hostConnection2 in array11)
			{
				Action<byte[]> dataSink = hostConnection2.DataSink;
				if (dataSink != null)
				{
					byte[] result6;
					while (hostConnection2.Inbox.TryDequeue(out result6))
					{
						dataSink(result6);
					}
				}
			}
			VirtualServer.PumpSessions();
			PumpZtPublish();
		}

		public static HostConnection ConnectClient(ulong hostSteamId, int virtualPort)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			if (!SteamClient.IsValid)
			{
				Plugin.Log.LogError((object)"[SDR] SteamClient is not initialized; cannot connect.");
				return null;
			}
			if (hostSteamId == 0L)
			{
				Plugin.Log.LogError((object)"[SDR] no host SteamId configured and no Steam lobby owner to resolve.");
				return null;
			}
			try
			{
				SteamNetworkingUtils.ConnectionTimeout = 30000;
				SteamNetworkingUtils.Timeout = 30000;
				HostConnection hostConnection = SteamNetworkingSockets.ConnectRelay<HostConnection>(SteamId.op_Implicit(hostSteamId), virtualPort);
				lock (Sync)
				{
					Clients.Add(hostConnection);
				}
				HostSteamId = hostSteamId;
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SDR] connecting to host {hostSteamId} on virtual port {virtualPort} (timeout 30s)...");
				}
				return hostConnection;
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"[SDR] failed to start relay connect: {arg}");
				return null;
			}
		}

		public static void ClientDied(HostConnection mgr)
		{
			lock (Sync)
			{
				Clients.Remove(mgr);
			}
		}

		public static void DisconnectClient(HostConnection mgr)
		{
			if (mgr == null)
			{
				return;
			}
			lock (Sync)
			{
				Clients.Remove(mgr);
			}
			PostToUnityThread(delegate
			{
				try
				{
					((ConnectionManager)mgr).Close(false, 0, "Closing Connection");
				}
				catch (Exception arg)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)$"[SDR] client close failed: {arg}");
					}
				}
			});
		}

		public static void CloseHostConnection(string sourceKey, string reason)
		{
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(sourceKey))
			{
				return;
			}
			string[] array = sourceKey.Split(':');
			if (array.Length < 3 || array[0] != "sdr")
			{
				return;
			}
			string text = array[1];
			if (text.Length < 2 || text[0] != 'c' || !uint.TryParse(text.Substring(1), out var result) || !int.TryParse(array[2], out var result2))
			{
				return;
			}
			lock (Sync)
			{
				foreach (HostListener host in Hosts)
				{
					if (host.VirtualPort == result2 && host.Connections.TryGetValue(result, out var value))
					{
						if (Plugin.Verbose)
						{
							Plugin.Log.LogInfo((object)$"[SDR host:{result2}] closing rejected connection {result}: {reason}");
						}
						((Connection)(ref value)).Flush();
						((Connection)(ref value)).Close(false, 0, reason);
					}
				}
			}
		}

		private static object ResolveSingleton()
		{
			try
			{
				return typeof(SteamNetworkingSockets).GetProperty("Internal", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null);
			}
			catch
			{
				return null;
			}
		}

		private static bool ConnectionsAny()
		{
			lock (Sync)
			{
				foreach (HostListener host in Hosts)
				{
					if (host.Connections.Count > 0)
					{
						return true;
					}
				}
			}
			return false;
		}
	}
	public class SteamSdrSocket : IPhotonSocket
	{
		private enum Mode
		{
			None,
			Loopback,
			Client,
			Udp
		}

		private volatile Mode _mode;

		private volatile HostConnection _clientMgr;

		private volatile UdpClient _udp;

		private int _phasePort = 7777;

		private ulong _hostId;

		private volatile string _hostToken;

		private volatile bool _udpHelloSent;

		private int _retries;

		private readonly int _socketId = ++_nextSocketId;

		private static int _nextSocketId;

		private Action<byte[]>? _loopbackSink;

		private readonly ConcurrentQueue<byte[]> _inbound = new ConcurrentQueue<byte[]>();

		private readonly object _stateLock = new object();

		private CancellationTokenSource _connectCts;

		private Thread _connectWorker;

		private Thread _pumpThread;

		private Thread _udpThread;

		private int _sendsLogged;

		private int _skipsLogged;

		private const int UdpProbeTimeoutMs = 500;

		public SteamSdrSocket(PeerBase peer)
			: base(peer)
		{
			if (((IPhotonSocket)this).ReportDebugOfLevel((DebugLevel)3))
			{
				((IPhotonSocket)this).EnqueueDebugReturn((DebugLevel)3, "SteamSdrSocket created");
			}
		}

		public override bool Connect()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			lock (_stateLock)
			{
				if ((int)((IPhotonSocket)this).State != 0)
				{
					((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, "SteamSdrSocket.Connect(): already in state " + ((object)((IPhotonSocket)this).State/*cast due to .constrained prefix*/).ToString());
					return false;
				}
			}
			string text = base.peerBase.ServerAddress ?? "";
			if (Plugin.Verbose)
			{
				Plugin.Log.LogInfo((object)("[SteamSdrSocket] connecting to '" + text + "'"));
			}
			if (text.StartsWith("sdr://", StringComparison.OrdinalIgnoreCase))
			{
				string text2 = text.Substring("sdr://".Length).Trim();
				int port = 7777;
				int num = text2.LastIndexOf(':');
				if (num > 0 && int.TryParse(text2.Substring(num + 1), out var result))
				{
					port = result;
					text2 = text2.Substring(0, num);
				}
				_phasePort = port;
				if (IPAddress.TryParse(text2, out IPAddress address))
				{
					_mode = Mode.Udp;
					_hostToken = SdrFabric.GetHostLobbyToken();
					try
					{
						_udp = new UdpClient(0);
						_udp.Connect(address, port);
					}
					catch (Exception arg)
					{
						((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, $"SteamSdrSocket: UDP connect failed: {arg}");
						return false;
					}
					_udpThread = new Thread(UdpReceiveLoop)
					{
						IsBackground = true,
						Name = "SteamSdrSocket udp-recv"
					};
					_udpThread.Start();
					OnEstablished();
					return true;
				}
				if (text2.Equals("loopback", StringComparison.OrdinalIgnoreCase))
				{
					_mode = Mode.Loopback;
					_loopbackSink = DeliverFromServer;
					SdrFabric.StartLoopback(_socketId, port, _loopbackSink);
					OnEstablished();
					return true;
				}
				if (ulong.TryParse(text2, out var hostId) && hostId != 0L)
				{
					if (SteamClient.IsValid && hostId == SteamId.op_Implicit(SteamClient.SteamId))
					{
						if (Plugin.Verbose)
						{
							Plugin.Log.LogInfo((object)"[SteamSdrSocket] target is our own SteamId -> loopback (host peer)");
						}
						_mode = Mode.Loopback;
						_loopbackSink = DeliverFromServer;
						SdrFabric.StartLoopback(_socketId, port, _loopbackSink);
						OnEstablished();
						return true;
					}
					lock (_stateLock)
					{
						_connectCts?.Cancel();
						_connectCts = new CancellationTokenSource();
						CancellationToken token = _connectCts.Token;
						_hostId = hostId;
						_connectWorker = new Thread((ThreadStart)delegate
						{
							ConnectLadderWorker(hostId, port, token);
						})
						{
							IsBackground = true,
							Name = "SteamSdrSocket connect-ladder"
						};
						((IPhotonSocket)this).State = (PhotonSocketState)1;
						_connectWorker.Start();
					}
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)$"[SteamSdrSocket] connect ladder handed to background worker (steamId {hostId}, port {port})");
					}
					return true;
				}
			}
			((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, "SteamSdrSocket: unsupported address '" + text + "' (expected sdr://loopback or sdr://<steamid64>)");
			return false;
		}

		private bool LadderCancelled(CancellationToken token)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Invalid comparison between Unknown and I4
			if (token.IsCancellationRequested)
			{
				return true;
			}
			lock (_stateLock)
			{
				return (int)((IPhotonSocket)this).State != 1;
			}
		}

		private void ConnectLadderWorker(ulong hostId, int port, CancellationToken token)
		{
			//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Invalid comparison between Unknown and I4
			try
			{
				_hostToken = SdrFabric.RunOnUnityThread(() => SdrFabric.GetHostLobbyToken(), token);
				if (LadderCancelled(token))
				{
					return;
				}
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SteamSdrSocket] ladder 1/3: LAN discovery for steamId {hostId} started on background worker (up to 2500 ms)");
				}
				string text = LanDiscovery.FindHostCached(hostId, 2500, _hostToken, () => token.IsCancellationRequested);
				if (LadderCancelled(token))
				{
					return;
				}
				if (!string.IsNullOrEmpty(text) && IPAddress.TryParse(text, out IPAddress address))
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)$"[SteamSdrSocket] ladder 1/3: LAN host discovered at {text} -> probing (steamId {hostId})");
					}
					UdpClient udpClient = ProbeUdpEndpoint(address, port, _hostToken);
					if (udpClient != null)
					{
						_udpHelloSent = true;
						EstablishUdpFromWorker(udpClient, "SteamSdrSocket udp-recv", token);
						return;
					}
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)"[SteamSdrSocket] ladder 1/3: LAN probe failed -> next ladder step");
					}
				}
				string text2 = SdrFabric.RunOnUnityThread(() => ModConfig.GetZeroTierFallbackIp(), token);
				if (!string.IsNullOrEmpty(text2) && IPAddress.TryParse(text2, out IPAddress address2))
				{
					UdpClient udpClient2 = ProbeUdpEndpoint(address2, port, _hostToken);
					if (udpClient2 != null)
					{
						if (Plugin.Verbose)
						{
							Plugin.Log.LogInfo((object)$"[SteamSdrSocket] ladder 2/3: ZeroTier tunnel {text2}:{port} reachable (probe ok) -> direct UDP");
						}
						_udpHelloSent = true;
						EstablishUdpFromWorker(udpClient2, "SteamSdrSocket udp-recv-zerotier", token);
						return;
					}
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)"[SteamSdrSocket] ladder 2/3: ZeroTier probe failed -> Steam relay");
					}
				}
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)"[SteamSdrSocket] ladder 3/3: no LAN host, ZeroTier unreachable -> Steam relay");
				}
				HostConnection hostConnection = SdrFabric.RunOnUnityThread(() => SdrFabric.ConnectClient(hostId, port), token);
				if (token.IsCancellationRequested)
				{
					if (hostConnection != null)
					{
						SdrFabric.DisconnectClient(hostConnection);
					}
					return;
				}
				if (hostConnection == null)
				{
					WorkerFailed("SteamSdrSocket: failed to start SDR client connect");
					return;
				}
				hostConnection.Owner = this;
				hostConnection.OnEstablished = OnEstablished;
				hostConnection.OnClosed = OnRemoteClosed;
				hostConnection.DataSink = DeliverFromServer;
				bool flag = false;
				lock (_stateLock)
				{
					if ((int)((IPhotonSocket)this).State == 1 && !token.IsCancellationRequested)
					{
						_clientMgr = hostConnection;
						_mode = Mode.Client;
						flag = true;
					}
				}
				if (!flag)
				{
					hostConnection.Owner = null;
					SdrFabric.DisconnectClient(hostConnection);
				}
			}
			catch (Exception arg)
			{
				WorkerFailed($"SteamSdrSocket: connect ladder crashed: {arg}");
			}
		}

		private bool EstablishUdpFromWorker(UdpClient udp, string recvThreadName, CancellationToken token)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			lock (_stateLock)
			{
				if ((int)((IPhotonSocket)this).State != 1 || token.IsCancellationRequested)
				{
					try
					{
						udp.Close();
					}
					catch
					{
					}
					return false;
				}
				_udp = udp;
				_mode = Mode.Udp;
				_udpThread = new Thread(UdpReceiveLoop)
				{
					IsBackground = true,
					Name = recvThreadName
				};
				_udpThread.Start();
				((IPhotonSocket)this).State = (PhotonSocketState)2;
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)"[SteamSdrSocket] transport established -> Photon peer Connected");
				}
				base.peerBase.OnConnect();
				_pumpThread = new Thread(PumpLoop)
				{
					IsBackground = true,
					Name = "SteamSdrSocket pump"
				};
				_pumpThread.Start();
				return true;
			}
		}

		private void WorkerFailed(string reason)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			lock (_stateLock)
			{
				if ((int)((IPhotonSocket)this).State != 1)
				{
					return;
				}
				_mode = Mode.None;
				((IPhotonSocket)this).State = (PhotonSocketState)0;
			}
			((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, reason);
			((IPhotonSocket)this).HandleException((StatusCode)1023);
		}

		private static UdpClient ProbeUdpEndpoint(IPAddress addr, int port, string token)
		{
			UdpClient udpClient = null;
			try
			{
				udpClient = new UdpClient(0);
				udpClient.Connect(addr, port);
				udpClient.Client.ReceiveTimeout = 500;
				byte[] bytes = Encoding.ASCII.GetBytes("RREPO1|" + token);
				udpClient.Send(bytes, bytes.Length);
				IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
				byte[] array = udpClient.Receive(ref remoteEP);
				if (array != null && AsciiStartsWith(array, "RREPO2|"))
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)$"[SteamSdrSocket] probe {addr}:{port} answered (hello ack) -> endpoint reachable and allowlisted");
					}
					return udpClient;
				}
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SteamSdrSocket] probe {addr}:{port} answered without a hello ack -> treated as a miss");
				}
				try
				{
					udpClient.Close();
				}
				catch (Exception ex)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)("[SteamSdrSocket] probe socket close failed: " + ex.Message));
					}
				}
				return null;
			}
			catch (Exception ex2)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogInfo((object)$"[SteamSdrSocket] probe {addr}:{port} failed: {ex2.Message}");
				}
				try
				{
					udpClient?.Close();
				}
				catch (Exception ex3)
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogInfo((object)("[SteamSdrSocket] probe socket close failed: " + ex3.Message));
					}
				}
				return null;
			}
		}

		private static bool AsciiStartsWith(byte[] data, string prefix)
		{
			if (data == null || data.Length < prefix.Length)
			{
				return false;
			}
			for (int i = 0; i < prefix.Length; i++)
			{
				if (data[i] != (byte)prefix[i])
				{
					return false;
				}
			}
			return true;
		}

		public bool RetryConnect()
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Invalid comparison between Unknown and I4
			if (_mode != Mode.Client)
			{
				return false;
			}
			lock (_stateLock)
			{
				if ((int)((IPhotonSocket)this).State != 1)
				{
					return false;
				}
			}
			if (++_retries > 2)
			{
				string text = ((ModConfig.GetZeroTierIp() != null) ? ModConfig.GetZeroTierFallbackIp() : null);
				if (!string.IsNullOrEmpty(text) && IPAddress.TryParse(text, out IPAddress _))
				{
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Steam P2P failed after {_retries - 1} attempt(s) -> ZeroTier fallback {text}:{_phasePort}");
					}
					if (_clientMgr != null)
					{
						_clientMgr.Owner = null;
					}
					_mode = Mode.Udp;
					_hostToken = SdrFabric.GetHostLobbyToken();
					try
					{
						_udp = new UdpClient(0);
						_udp.Connect(text, _phasePort);
					}
					catch (Exception arg)
					{
						((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, $"SteamSdrSocket: ZeroTier UDP connect failed: {arg}");
						_mode = Mode.None;
						return false;
					}
					_clientMgr = null;
					_udpThread = new Thread(UdpReceiveLoop)
					{
						IsBackground = true,
						Name = "SteamSdrSocket udp-recv-zerotier"
					};
					_udpThread.Start();
					OnEstablished();
					return true;
				}
				Plugin.Log.LogError((object)$"[SteamSdrSocket] Steam P2P connect failed after {_retries - 1} attempt(s), no ZeroTier fallback available");
				return false;
			}
			lock (_stateLock)
			{
				_connectCts?.Cancel();
				_connectCts = new CancellationTokenSource();
				CancellationToken token = _connectCts.Token;
				ulong hostId = _hostId;
				int port = _phasePort;
				_connectWorker = new Thread((ThreadStart)delegate
				{
					RetryWorker(hostId, port, token);
				})
				{
					IsBackground = true,
					Name = "SteamSdrSocket relay-retry"
				};
				_connectWorker.Start();
			}
			if (Plugin.Verbose)
			{
				Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Steam P2P retry #{_retries} scheduled on background worker -> sdr://{_hostId}:{_phasePort}");
			}
			return true;
		}

		private void RetryWorker(ulong hostId, int port, CancellationToken token)
		{
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Invalid comparison between Unknown and I4
			int num = 1200;
			while (num > 0 && !token.IsCancellationRequested)
			{
				Thread.Sleep((num >= 100) ? 100 : num);
				num -= 100;
			}
			if (token.IsCancellationRequested)
			{
				return;
			}
			HostConnection hostConnection = SdrFabric.RunOnUnityThread(() => SdrFabric.ConnectClient(hostId, port), token);
			if (token.IsCancellationRequested)
			{
				if (hostConnection != null)
				{
					SdrFabric.DisconnectClient(hostConnection);
				}
				return;
			}
			if (hostConnection == null)
			{
				OnRemoteClosed();
				return;
			}
			hostConnection.Owner = this;
			hostConnection.OnEstablished = OnEstablished;
			hostConnection.OnClosed = OnRemoteClosed;
			hostConnection.DataSink = DeliverFromServer;
			bool flag = false;
			lock (_stateLock)
			{
				if ((int)((IPhotonSocket)this).State == 1 && !token.IsCancellationRequested)
				{
					_clientMgr = hostConnection;
					_mode = Mode.Client;
					flag = true;
				}
			}
			if (!flag)
			{
				hostConnection.Owner = null;
				SdrFabric.DisconnectClient(hostConnection);
			}
			else if (Plugin.Verbose)
			{
				Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Steam P2P retry #{_retries} -> sdr://{hostId}:{port}");
			}
		}

		private void OnEstablished()
		{
			lock (_stateLock)
			{
				((IPhotonSocket)this).State = (PhotonSocketState)2;
			}
			if (Plugin.Verbose)
			{
				Plugin.Log.LogInfo((object)"[SteamSdrSocket] transport established -> Photon peer Connected");
			}
			base.peerBase.OnConnect();
			_pumpThread = new Thread(PumpLoop)
			{
				IsBackground = true,
				Name = "SteamSdrSocket pump"
			};
			_pumpThread.Start();
		}

		private void PumpLoop()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Invalid comparison between Unknown and I4
			while ((int)((IPhotonSocket)this).State == 2)
			{
				if (_inbound.TryDequeue(out var result))
				{
					try
					{
						((IPhotonSocket)this).HandleReceivedDatagram(result, result.Length, false);
					}
					catch (Exception arg)
					{
						Plugin.Log.LogError((object)$"[SteamSdrSocket] pump delivery failed: {arg}");
					}
				}
				else
				{
					Thread.Sleep(2);
				}
			}
		}

		private void UdpReceiveLoop()
		{
			while (_mode == Mode.Udp)
			{
				UdpClient udp = _udp;
				if (udp == null)
				{
					break;
				}
				try
				{
					IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
					byte[] datagram = udp.Receive(ref remoteEP);
					DeliverFromServer(datagram);
				}
				catch (SocketException ex)
				{
					if (ex.SocketErrorCode == SocketError.Interrupted)
					{
						break;
					}
					if (Plugin.Verbose)
					{
						Plugin.Log.LogWarning((object)("[SteamSdrSocket] udp recv: " + ex.Message));
					}
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				catch (Exception arg)
				{
					Plugin.Log.LogError((object)$"[SteamSdrSocket] udp recv loop: {arg}");
				}
			}
		}

		private void OnRemoteClosed()
		{
			((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)2, "SteamSdrSocket: SDR connection closed by remote");
			((IPhotonSocket)this).HandleException((StatusCode)1043);
		}

		public override bool Disconnect()
		{
			if (Plugin.Verbose)
			{
				Plugin.Log.LogInfo((object)"[SteamSdrSocket] Disconnect()");
			}
			Mode mode;
			HostConnection clientMgr;
			UdpClient udp;
			lock (_stateLock)
			{
				((IPhotonSocket)this).State = (PhotonSocketState)3;
				_connectCts?.Cancel();
				_connectCts = null;
				mode = _mode;
				clientMgr = _clientMgr;
				udp = _udp;
				_clientMgr = null;
				_udp = null;
			}
			try
			{
				if (mode == Mode.Client)
				{
					if (clientMgr != null)
					{
						clientMgr.Owner = null;
					}
					SdrFabric.DisconnectClient(clientMgr);
				}
				if (mode == Mode.Loopback)
				{
					SdrFabric.UnregisterLoopbackSink(_socketId);
				}
				if (mode == Mode.Udp)
				{
					try
					{
						udp?.Close();
					}
					catch
					{
					}
				}
			}
			catch (Exception arg)
			{
				if (Plugin.Verbose)
				{
					Plugin.Log.LogWarning((object)$"[SteamSdrSocket] exception during disconnect: {arg}");
				}
			}
			lock (_stateLock)
			{
				_mode = Mode.None;
				((IPhotonSocket)this).State = (PhotonSocketState)0;
			}
			return true;
		}

		public override PhotonSocketError Send(byte[] data, int length)
		{
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_024f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0225: 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_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_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Invalid comparison between Unknown and I4
			//IL_0164: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				switch (_mode)
				{
				case Mode.Loopback:
					SdrFabric.LoopbackSend(_socketId, _phasePort, CopyOf(data, length));
					return (PhotonSocketError)0;
				case Mode.Client:
				{
					HostConnection clientMgr = _clientMgr;
					if (clientMgr == null || !((ConnectionManager)clientMgr).Connected)
					{
						if (_skipsLogged < 8)
						{
							_skipsLogged++;
							if (Plugin.Verbose)
							{
								Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Send skipped: no live SDR connection (mgr={clientMgr != null}, connected={((ConnectionManager)(clientMgr?)).Connected})");
							}
						}
						return (PhotonSocketError)1;
					}
					byte[] array = CopyOf(data, length);
					Result val = ((Connection)(ref ((ConnectionManager)clientMgr).Connection)).SendMessage(array, (SendType)1, (ushort)0);
					if (_sendsLogged < 8)
					{
						_sendsLogged++;
						if (Plugin.Verbose)
						{
							Plugin.Log.LogInfo((object)$"[SteamSdrSocket] Send #{_sendsLogged}: {length}B -> host, result={val}, connState={((ConnectionManager)clientMgr).Connected}");
						}
					}
					if ((int)val != 1)
					{
						if (Plugin.Verbose)
						{
							Plugin.Log.LogWarning((object)$"[SteamSdrSocket] SendMessage result: {val}");
						}
						return (PhotonSocketError)1;
					}
					return (PhotonSocketError)0;
				}
				case Mode.Udp:
				{
					UdpClient udp = _udp;
					if (udp == null)
					{
						return (PhotonSocketError)1;
					}
					try
					{
						if (!_udpHelloSent)
						{
							_udpHelloSent = true;
							byte[] bytes = Encoding.ASCII.GetBytes("RREPO1|" + _hostToken);
							udp.Send(bytes, bytes.Length);
							if (Plugin.Verbose)
							{
								Plugin.Log.LogInfo((object)"[SteamSdrSocket] udp hello sent (transport authorization)");
							}
						}
						udp.Send(data, length);
					}
					catch (Exception ex)
					{
						if (_skipsLogged < 8)
						{
							_skipsLogged++;
							if (Plugin.Verbose)
							{
								Plugin.Log.LogWarning((object)("[