Decompiled source of StraftatOfflineLAN v0.2.0

plugins\StraftatLocalP2P.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using ComputerysModdingUtilities;
using FishNet;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Managing.Transporting;
using FishNet.Object;
using FishNet.Transporting;
using FishNet.Transporting.Tugboat;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: StraftatMod(true)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.1", FrameworkDisplayName = ".NET Framework 4.6.1")]
[assembly: AssemblyCompany("StraftatLocalP2P")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+0bc8fcc8d3de24c0174366cf7eb8e6f59156748f")]
[assembly: AssemblyProduct("StraftatLocalP2P")]
[assembly: AssemblyTitle("StraftatLocalP2P")]
[assembly: AssemblyVersion("0.2.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 StraftatLocalP2P
{
	[BepInPlugin("com.local.straftat.p2p", "STRAFTAT LAN P2P Optimizer", "0.2.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		private enum OfflineLanRole
		{
			None,
			Host,
			Client
		}

		private sealed class OfflineLanAuthInfo
		{
			public string Name;

			public DateTime AcceptedAtUtc;
		}

		private static class OfflineLanPatches
		{
			public static bool SteamLobbyStartPrefix(SteamLobby __instance)
			{
				if (!OfflineLanEnabled.Value || !ForceOfflineSteamLobbyStartup.Value)
				{
					return true;
				}
				try
				{
					__instance.maxPlayers = (((Object)(object)__instance.MaxPlayersDropdown != (Object)null) ? (__instance.MaxPlayersDropdown.value + 2) : 4);
					Log.LogInfo((object)"Offline LAN skipped SteamLobby.Start Steam callback setup.");
				}
				catch (Exception arg)
				{
					Log.LogWarning((object)$"Offline LAN minimal SteamLobby.Start setup failed: {arg}");
				}
				return false;
			}

			public static bool SteamLobbyCreatePrefix(SteamLobby __instance)
			{
				if (!ShouldRedirectCreateLobbyToOfflineLan())
				{
					return true;
				}
				HostOfflineLanFromNormalButton(__instance);
				return false;
			}

			public static bool SteamLobbyCreateLobbyDirectPrefix(SteamLobby __instance)
			{
				if (!ShouldRedirectCreateLobbyToOfflineLan())
				{
					return true;
				}
				HostOfflineLanFromNormalButton(__instance);
				return false;
			}

			public static bool SteamLobbySetMaxPlayersPrefix(SteamLobby __instance, TMP_Dropdown _dropdown)
			{
				if (!OfflineLanEnabled.Value || !ForceOfflineSteamLobbyStartup.Value)
				{
					return true;
				}
				__instance.maxPlayers = (((Object)(object)_dropdown != (Object)null) ? _dropdown.value : 2) + 2;
				Transport val = InstanceFinder.TransportManager?.Transport;
				if ((Object)(object)val != (Object)null)
				{
					val.SetMaximumClients(Mathf.Max(2, __instance.maxPlayers));
				}
				if (OfflineLanActive)
				{
					BroadcastOfflineMaxPlayers("dropdown changed");
				}
				Log.LogInfo((object)$"Offline LAN max players set to {__instance.maxPlayers}.");
				return false;
			}

			public static bool SteamLobbySetAllowMidMatchJoiningPrefix(SteamLobby __instance, bool value)
			{
				if (!OfflineLanActive)
				{
					return true;
				}
				__instance._allowMidMatchJoining = value;
				return false;
			}

			public static bool SteamLobbyLeaveLobbyPrefix()
			{
				if (!OfflineLanActive)
				{
					return true;
				}
				StopOfflineLanSession(loadMainMenu: true);
				return false;
			}

			public static bool SteamLobbyLeaveSteamLobbyPrefix()
			{
				return !OfflineLanActive;
			}

			public static bool ValidateSteamIdPrefix(ref bool __result)
			{
				if (!OfflineLanActive)
				{
					return true;
				}
				__result = true;
				return false;
			}

			public static bool SkipSteamOnlyMethodPrefix()
			{
				return !OfflineLanActive;
			}

			public static bool LobbyControllerUpdateLobbyNamePrefix(LobbyController __instance)
			{
				if (!OfflineLanActive)
				{
					return true;
				}
				if ((Object)(object)__instance.LobbyNameText != (Object)null)
				{
					string text = (((Object)(object)SteamLobby.Instance != (Object)null && !string.IsNullOrWhiteSpace(SteamLobby.Instance.lobbyName)) ? SteamLobby.Instance.lobbyName : "Offline LAN");
					((TMP_Text)__instance.LobbyNameText).text = ((_offlineLanRole == OfflineLanRole.Host) ? text : "Offline LAN Client");
				}
				return false;
			}

			public static void ClientInstanceOnStartClientPostfix(ClientInstance __instance)
			{
				if (OfflineLanActive && !((Object)(object)__instance == (Object)null))
				{
					__instance.nonSteamworksTransport = false;
					if (((NetworkBehaviour)__instance).IsOwner && (Object)(object)LobbyController.Instance != (Object)null)
					{
						LobbyController.Instance.LocalPlayerController = __instance;
						LobbyController.Instance.UpdatePlayerList();
					}
				}
			}

			public static bool ClientInstanceSetSyncValuesPrefix(ClientInstance __instance, NetworkObject newPlayer)
			{
				if (!OfflineLanActive || (Object)(object)__instance == (Object)null || (Object)(object)newPlayer == (Object)null)
				{
					return true;
				}
				try
				{
					NetworkConnection owner = newPlayer.Owner;
					int num = 0;
					int[] array = (from x in Object.FindObjectsOfType<ClientInstance>()
						select x.PlayerId).ToArray();
					for (int i = 0; i < Math.Max(4, array.Length + 1); i++)
					{
						if (!array.Contains(i))
						{
							num = i;
							break;
						}
					}
					ulong offlineLanId = GetOfflineLanId(owner, num);
					AccessTools.Method(typeof(ClientInstance), "AddNewPlayer", (Type[])null, (Type[])null)?.Invoke(__instance, new object[4] { owner, newPlayer, num, offlineLanId });
					AccessTools.Method(typeof(ClientInstance), "UpdateOnClients", Type.EmptyTypes, (Type[])null)?.Invoke(__instance, Array.Empty<object>());
					AccessTools.Method(typeof(ClientInstance), "RunIntoLobby", (Type[])null, (Type[])null)?.Invoke(__instance, new object[1] { num });
					AccessTools.Method(typeof(ClientInstance), "InitiateClient", (Type[])null, (Type[])null)?.Invoke(__instance, new object[2] { owner, num });
					BroadcastOfflineMaxPlayers("player registered");
					Log.LogInfo((object)$"Offline LAN registered player id={num}, lanId={offlineLanId}, connection={owner?.ClientId}.");
				}
				catch (Exception arg)
				{
					Log.LogWarning((object)$"Offline LAN SetSyncValues replacement failed: {arg}");
				}
				return false;
			}

			public static bool ClientInstanceAddNewPlayerPrefix(ClientInstance __instance, NetworkConnection owner, NetworkObject newPlayer, int id, ulong steamid)
			{
				if (!OfflineLanActive || (Object)(object)__instance == (Object)null || owner == (NetworkConnection)null || (Object)(object)newPlayer == (Object)null)
				{
					return true;
				}
				try
				{
					__instance.PlayerSteamID = steamid;
					__instance.PlayerId = id;
					__instance.ConnectionID = owner.ClientId;
					if (!SteamLobby.Instance.players.Contains(newPlayer))
					{
						SteamLobby.Instance.players.Add(newPlayer);
					}
					ClientInstance.playerInstances[id] = __instance;
					__instance.PlayerName = GetOfflineLanPlayerName(owner, id, steamid);
					((Object)((Component)__instance).gameObject).name = __instance.PlayerName;
					if (!owner.IsLocalClient && (Object)(object)VoiceChatUI.Instance != (Object)null)
					{
						VoiceChatUI.Instance.CreateVoiceChatUIObject(__instance);
					}
					Log.LogInfo((object)$"Offline LAN added player | LAN ID: {steamid}, PlayerId: {id}, ConnectionID: {owner.ClientId}, PlayerName: {__instance.PlayerName}");
				}
				catch (Exception arg)
				{
					Log.LogWarning((object)$"Offline LAN AddNewPlayer replacement failed: {arg}");
				}
				return false;
			}

			public static bool UnityDebugLogFormatPrefix(LogType logType, string format, object[] args)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				//IL_0009: Invalid comparison between Unknown and I4
				if (!OfflineLanActive || (int)logType != 2 || string.IsNullOrEmpty(format))
				{
					return true;
				}
				if (format.Contains("Cannot complete action because client is not active") || format.Contains("Cannot complete operation as server when server is not active") || format.Contains("Server socket is not started"))
				{
					return false;
				}
				return true;
			}
		}

		private static class SteamApiPatches
		{
			[HarmonyPatch(typeof(SteamAPI), "Init")]
			[HarmonyPostfix]
			private static void SteamApiInitPostfix(bool __result)
			{
				if (__result)
				{
					OnSteamApiInitialized();
				}
				else
				{
					Log.LogWarning((object)"SteamAPI.Init returned false; routing config was not applied.");
				}
			}
		}

		private static class LobbyLifecyclePatches
		{
			public static void BeforeLobbyLifecycleEvent(MethodBase __originalMethod)
			{
				OnLobbyLifecycleEvent(__originalMethod?.Name ?? "unknown");
			}
		}

		private static class FishySteamworksPatches
		{
			public static IEnumerable<CodeInstruction> ClientStartConnectionTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
			{
				List<CodeInstruction> list = new List<CodeInstruction>(instructions);
				FieldInfo objB = AccessTools.Field(typeof(OptionValue), "m_int32");
				MethodInfo operand = AccessTools.Method(typeof(Plugin), "GetPerConnectionIceEnableValue", (Type[])null, (Type[])null);
				bool flag = false;
				for (int i = 0; i < list.Count - 1; i++)
				{
					if (!flag && IsLoadInt32Zero(list[i]) && list[i + 1].opcode == OpCodes.Stfld && object.Equals(list[i + 1].operand, objB))
					{
						list[i].opcode = OpCodes.Call;
						list[i].operand = operand;
						flag = true;
					}
				}
				if (flag)
				{
					Log.LogInfo((object)"FishySteamworks ClientSocket ICE config transpiler applied.");
				}
				else
				{
					Log.LogWarning((object)"FishySteamworks ClientSocket ICE config transpiler did not find the expected m_int32=0 assignment.");
				}
				return list;
			}

			public static IEnumerable<CodeInstruction> ServerStartConnectionTranspiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
			{
				List<CodeInstruction> list = new List<CodeInstruction>(instructions);
				Type typeFromHandle = typeof(SteamNetworkingConfigValue_t);
				MethodInfo operand = AccessTools.Method(typeof(Plugin), "GetListenSocketConfigValues", (Type[])null, (Type[])null);
				bool flag = false;
				for (int i = 0; i < list.Count - 1; i++)
				{
					if (!flag && IsLoadInt32Zero(list[i]) && list[i + 1].opcode == OpCodes.Newarr && object.Equals(list[i + 1].operand, typeFromHandle))
					{
						list[i].opcode = OpCodes.Call;
						list[i].operand = operand;
						list[i + 1].opcode = OpCodes.Nop;
						list[i + 1].operand = null;
						flag = true;
					}
				}
				if (flag)
				{
					Log.LogInfo((object)"FishySteamworks ServerSocket listen config transpiler applied.");
				}
				else
				{
					Log.LogWarning((object)"FishySteamworks ServerSocket listen config transpiler did not find the expected empty config array.");
				}
				return list;
			}

			private static bool IsLoadInt32Zero(CodeInstruction instruction)
			{
				if (instruction.opcode == OpCodes.Ldc_I4_0)
				{
					return true;
				}
				if (instruction.opcode == OpCodes.Ldc_I4 && instruction.operand is int num)
				{
					return num == 0;
				}
				if (instruction.opcode == OpCodes.Ldc_I4_S && instruction.operand is sbyte b)
				{
					return b == 0;
				}
				return false;
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Action<NetworkConnection, RemoteConnectionStateArgs> <0>__OfflineServerRemoteConnectionState;

			public static ThreadStart <1>__OfflineAuthLoop;

			public static DispatchDelegate<SteamNetConnectionStatusChangedCallback_t> <2>__OnConnectionStatusChanged;

			public static FSteamNetworkingSocketsDebugOutput <3>__OnSteamNetworkingDebugOutput;
		}

		private static ConfigEntry<bool> OfflineLanEnabled;

		private static ConfigEntry<bool> ForceOfflineSteamLobbyStartup;

		private static ConfigEntry<bool> RedirectCreateLobbyToOfflineLan;

		private static ConfigEntry<bool> ShowOfflineLanPanel;

		private static ConfigEntry<string> OfflineLanJoinAddress;

		private static ConfigEntry<string> OfflineLanPasscode;

		private static ConfigEntry<string> OfflineLanPlayerName;

		private static ConfigEntry<ushort> OfflineLanPort;

		private static ConfigEntry<int> OfflineLanPanelX;

		private static ConfigEntry<int> OfflineLanPanelY;

		private static readonly ConcurrentDictionary<string, OfflineLanAuthInfo> AcceptedOfflineLanPeers = new ConcurrentDictionary<string, OfflineLanAuthInfo>(StringComparer.OrdinalIgnoreCase);

		private static readonly ConcurrentDictionary<int, string> OfflineLanConnectionNames = new ConcurrentDictionary<int, string>();

		private static OfflineLanRole _offlineLanRole;

		private static UdpClient _offlineAuthListener;

		private static Thread _offlineAuthThread;

		private static volatile bool _offlineAuthRunning;

		private static string _offlineLanStatus = "Ready";

		private static Tugboat _offlineTugboat;

		private static bool _offlineTransportBound;

		private static bool _offlineLeaveInProgress;

		private static bool _offlineLanStarting;

		private static string _cachedLocalIPv4Address = "127.0.0.1";

		private static float _nextLocalIpRefreshAt;

		private static Action<ServerConnectionStateArgs> _serverConnectionDelegate;

		private static Action<RemoteConnectionStateArgs> _remoteConnectionDelegate;

		private static Action<ServerReceivedDataArgs> _serverReceivedDelegate;

		private static Action<ClientConnectionStateArgs> _clientConnectionLogDelegate;

		private GameObject _offlineLanRoot;

		private Text _offlineLanStatusText;

		private InputField _offlineIpInput;

		private InputField _offlinePasscodeInput;

		private InputField _offlinePortInput;

		private InputField _offlineNameInput;

		private GameObject _offlineLeaveButton;

		public const string PluginGuid = "com.local.straftat.p2p";

		public const string PluginName = "STRAFTAT LAN P2P Optimizer";

		public const string PluginVersion = "0.2.0";

		private static readonly string[] LobbyPatchTypes = new string[1] { "SteamLobby" };

		private static ManualLogSource Log;

		private static ConfigEntry<bool> Enabled;

		private static ConfigEntry<RoutingMode> Mode;

		private static ConfigEntry<bool> VerboseSteamNetworkingLogs;

		private static ConfigEntry<bool> AllowUnencryptedSegments;

		private static ConfigEntry<bool> ShowLobbyToggle;

		private static ConfigEntry<int> ToggleX;

		private static ConfigEntry<int> ToggleY;

		private static ConfigFile _configFile;

		private static Harmony _harmony;

		private static Callback<SteamNetConnectionStatusChangedCallback_t> _connectionStatusCallback;

		private static FSteamNetworkingSocketsDebugOutput _debugOutputCallback;

		private static bool _steamCallbacksRegistered;

		private static bool _steamInitSeen;

		private static RoutingMode? _lastAppliedMode;

		private static bool _lastUnencryptedSetting;

		private static Type _steamLobbyType;

		private const int IceEnablePrivateAndPublic = 6;

		private GameObject _uiRoot;

		private Text _statusText;

		private float _nextApplyAttemptAt;

		private static bool _cachedHostMenuVisible;

		private static float _nextHostMenuVisibilityCheckAt;

		private static Plugin _instance;

		private static bool OfflineLanActive => _offlineLanRole != OfflineLanRole.None;

		private static void ConfigureOfflineLan(ConfigFile config)
		{
			OfflineLanEnabled = config.Bind<bool>("Offline LAN", "Enabled", true, "Enable the offline LAN prototype panel and patches.");
			ForceOfflineSteamLobbyStartup = config.Bind<bool>("Offline LAN", "ForceOfflineSteamLobbyStartup", true, "Skip Steam lobby callback setup so the offline prototype can run without Steam matchmaking.");
			RedirectCreateLobbyToOfflineLan = config.Bind<bool>("Offline LAN", "RedirectCreateLobbyToOfflineLan", true, "Make STRAFTAT's normal Create Lobby host button start an offline LAN lobby.");
			ShowOfflineLanPanel = config.Bind<bool>("Offline LAN", "ShowOfflineLanPanel", true, "Show the offline LAN host/join panel on the multiplayer home screen.");
			OfflineLanJoinAddress = config.Bind<string>("Offline LAN", "JoinAddress", "192.168.1.100", "LAN IPv4 address of the host PC.");
			OfflineLanPasscode = config.Bind<string>("Offline LAN", "Passcode", "LAN", "Passcode required before a client can join this offline LAN session.");
			OfflineLanPlayerName = config.Bind<string>("Offline LAN", "PlayerName", Environment.UserName, "Display name used locally by the offline LAN prototype.");
			OfflineLanPort = config.Bind<ushort>("Offline LAN", "Port", (ushort)7770, "UDP port used by FishNet Tugboat. The passcode handshake uses the next port.");
			OfflineLanPanelX = config.Bind<int>("Offline LAN", "PanelX", 775, "Offline LAN panel X position in screen pixels.");
			OfflineLanPanelY = config.Bind<int>("Offline LAN", "PanelY", 610, "Offline LAN panel Y position in screen pixels.");
			if (OfflineLanPanelY.Value == 525)
			{
				OfflineLanPanelY.Value = 610;
			}
		}

		private static void PatchOfflineLanMethods()
		{
			if (OfflineLanEnabled.Value)
			{
				PatchIfPresent("SteamLobby", "Start", typeof(OfflineLanPatches), "SteamLobbyStartPrefix");
				PatchIfPresent("SteamLobby", "Create", typeof(OfflineLanPatches), "SteamLobbyCreatePrefix");
				PatchIfPresent("SteamLobby", "CreateLobbyDirect", typeof(OfflineLanPatches), "SteamLobbyCreateLobbyDirectPrefix");
				PatchIfPresent("SteamLobby", "SetMaxPlayers", typeof(OfflineLanPatches), "SteamLobbySetMaxPlayersPrefix", new Type[1] { typeof(TMP_Dropdown) });
				PatchIfPresent("SteamLobby", "SetAllowMidMatchJoining", typeof(OfflineLanPatches), "SteamLobbySetAllowMidMatchJoiningPrefix", new Type[1] { typeof(bool) });
				PatchIfPresent("SteamLobby", "LeaveLobby", typeof(OfflineLanPatches), "SteamLobbyLeaveLobbyPrefix");
				PatchIfPresent("SteamLobby", "LeaveSteamLobby", typeof(OfflineLanPatches), "SteamLobbyLeaveSteamLobbyPrefix");
				PatchIfPresent("SteamLobby", "ValidateSteamIDisInLobby", typeof(OfflineLanPatches), "ValidateSteamIdPrefix", new Type[1] { typeof(ulong) });
				PatchIfPresent("SteamLobby", "UpdateLobbyType", typeof(OfflineLanPatches), "SkipSteamOnlyMethodPrefix");
				PatchIfPresent("SteamLobby", "SetGamemodeString", typeof(OfflineLanPatches), "SkipSteamOnlyMethodPrefix");
				PatchIfPresent("SteamLobby", "UpdatePlayerCountDisplay", typeof(OfflineLanPatches), "SkipSteamOnlyMethodPrefix");
				PatchIfPresent("LobbyController", "UpdateLobbyName", typeof(OfflineLanPatches), "LobbyControllerUpdateLobbyNamePrefix");
				PatchIfPresent("ClientInstance", "OnStartClient", typeof(OfflineLanPatches), "ClientInstanceOnStartClientPostfix", null, postfix: true);
				PatchIfPresent("ClientInstance", "SetSyncValues", typeof(OfflineLanPatches), "ClientInstanceSetSyncValuesPrefix");
				PatchIfPresent("ClientInstance", "RpcLogic___AddNewPlayer_2166193949", typeof(OfflineLanPatches), "ClientInstanceAddNewPlayerPrefix");
				PatchUnityLogFilter();
			}
		}

		private static void PatchUnityLogFilter()
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("UnityEngine.DebugLogHandler");
			if (!(type == null))
			{
				HarmonyMethod val = new HarmonyMethod(typeof(OfflineLanPatches), "UnityDebugLogFormatPrefix", (Type[])null);
				MethodInfo methodInfo = AccessTools.Method(type, "LogFormat", new Type[4]
				{
					typeof(LogType),
					typeof(Object),
					typeof(string),
					typeof(object[])
				}, (Type[])null);
				MethodInfo methodInfo2 = AccessTools.Method(type, "LogFormat", new Type[5]
				{
					typeof(LogType),
					typeof(LogOption),
					typeof(Object),
					typeof(string),
					typeof(object[])
				}, (Type[])null);
				if (methodInfo != null)
				{
					_harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
				if (methodInfo2 != null)
				{
					_harmony.Patch((MethodBase)methodInfo2, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
		}

		private static void PatchIfPresent(string typeName, string methodName, Type patchType, string patchName, Type[] argumentTypes = null, bool postfix = false)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Expected O, but got Unknown
			Type type = AccessTools.TypeByName(typeName);
			MethodInfo methodInfo = ((type == null) ? null : ((argumentTypes == null) ? AccessTools.Method(type, methodName, (Type[])null, (Type[])null) : AccessTools.Method(type, methodName, argumentTypes, (Type[])null)));
			if (methodInfo == null)
			{
				Log.LogWarning((object)("Offline LAN patch target " + typeName + "." + methodName + " was not found."));
				return;
			}
			HarmonyMethod val = new HarmonyMethod(patchType, patchName, (Type[])null);
			if (postfix)
			{
				_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				_harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			Log.LogInfo((object)("Patched offline LAN method " + typeName + "." + methodName + "."));
		}

		private void TryCreateOfflineLanUi(string reason)
		{
			if (!OfflineLanEnabled.Value || !ShowOfflineLanPanel.Value)
			{
				return;
			}
			try
			{
				CreateOfflineLanUi();
				RefreshOfflineLanUi();
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Failed to create Offline LAN UI during {reason}: {arg}");
			}
		}

		private void CreateOfflineLanUi()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_022e: Unknown result type (might be due to invalid IL or missing references)
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02da: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0317: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_034f: Unknown result type (might be due to invalid IL or missing references)
			//IL_035e: Unknown result type (might be due to invalid IL or missing references)
			//IL_038f: Unknown result type (might be due to invalid IL or missing references)
			//IL_039e: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_03df: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_offlineLanRoot != (Object)null))
			{
				EnsureEventSystem();
				_offlineLanRoot = new GameObject("StraftatLocalP2P_OfflineLanPanel");
				Object.DontDestroyOnLoad((Object)(object)_offlineLanRoot);
				Canvas obj = _offlineLanRoot.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 32766;
				_offlineLanRoot.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)0;
				_offlineLanRoot.AddComponent<GraphicRaycaster>();
				GameObject val = CreateUiObject("Panel", _offlineLanRoot.transform);
				RectTransform obj2 = val.AddComponent<RectTransform>();
				obj2.anchorMin = new Vector2(0f, 1f);
				obj2.anchorMax = new Vector2(0f, 1f);
				obj2.pivot = new Vector2(0f, 1f);
				obj2.anchoredPosition = new Vector2((float)OfflineLanPanelX.Value, (float)(-OfflineLanPanelY.Value));
				obj2.sizeDelta = new Vector2(360f, 238f);
				((Graphic)val.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.68f);
				CreateText(val.transform, "Title", "Offline LAN", new Vector2(12f, -8f), new Vector2(336f, 24f), 16, (FontStyle)1);
				_offlineLanStatusText = CreateText(val.transform, "Status", "", new Vector2(12f, -33f), new Vector2(336f, 32f), 12, (FontStyle)0);
				CreateText(val.transform, "NameLabel", "Name", new Vector2(12f, -72f), new Vector2(80f, 20f), 12, (FontStyle)1);
				_offlineNameInput = CreateInput(val.transform, "NameInput", OfflineLanPlayerName.Value, new Vector2(96f, -70f), new Vector2(252f, 24f));
				CreateText(val.transform, "IpLabel", "Host IP", new Vector2(12f, -102f), new Vector2(80f, 20f), 12, (FontStyle)1);
				_offlineIpInput = CreateInput(val.transform, "IpInput", OfflineLanJoinAddress.Value, new Vector2(96f, -100f), new Vector2(168f, 24f));
				_offlinePortInput = CreateInput(val.transform, "PortInput", OfflineLanPort.Value.ToString(), new Vector2(270f, -100f), new Vector2(78f, 24f));
				CreateText(val.transform, "PasscodeLabel", "Passcode", new Vector2(12f, -132f), new Vector2(80f, 20f), 12, (FontStyle)1);
				_offlinePasscodeInput = CreateInput(val.transform, "PasscodeInput", OfflineLanPasscode.Value, new Vector2(96f, -130f), new Vector2(252f, 24f));
				CreateButton(val.transform, "HostButton", "Host Offline LAN", new Vector2(12f, -166f), new Vector2(160f, 30f), delegate
				{
					HostOfflineLanFromUi();
				});
				CreateButton(val.transform, "JoinButton", "Join IP", new Vector2(188f, -166f), new Vector2(160f, 30f), delegate
				{
					JoinOfflineLanFromUi();
				});
				_offlineLeaveButton = CreateButton(val.transform, "LeaveButton", "Leave Offline LAN", new Vector2(12f, -202f), new Vector2(336f, 28f), delegate
				{
					StopOfflineLanSession(loadMainMenu: true);
				});
				Log.LogInfo((object)"Created Offline LAN panel.");
			}
		}

		private static InputField CreateInput(Transform parent, string name, string value, Vector2 position, Vector2 size)
		{
			//IL_0019: 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_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: 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_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: 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_00fc: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = CreateUiObject(name, parent);
			RectTransform obj = val.AddComponent<RectTransform>();
			obj.anchorMin = new Vector2(0f, 1f);
			obj.anchorMax = new Vector2(0f, 1f);
			obj.pivot = new Vector2(0f, 1f);
			obj.anchoredPosition = position;
			obj.sizeDelta = size;
			Image val2 = val.AddComponent<Image>();
			((Graphic)val2).color = new Color(0.08f, 0.09f, 0.1f, 0.95f);
			GameObject obj2 = CreateUiObject("Text", val.transform);
			RectTransform obj3 = obj2.AddComponent<RectTransform>();
			obj3.anchorMin = Vector2.zero;
			obj3.anchorMax = Vector2.one;
			obj3.offsetMin = new Vector2(6f, 2f);
			obj3.offsetMax = new Vector2(-6f, -2f);
			Text val3 = obj2.AddComponent<Text>();
			val3.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			val3.fontSize = 13;
			val3.alignment = (TextAnchor)3;
			((Graphic)val3).color = Color.white;
			InputField obj4 = val.AddComponent<InputField>();
			((Selectable)obj4).targetGraphic = (Graphic)(object)val2;
			obj4.textComponent = val3;
			obj4.text = value ?? string.Empty;
			obj4.characterLimit = 64;
			return obj4;
		}

		private void OfflineLanUpdate()
		{
			if (OfflineLanEnabled.Value)
			{
				if ((Object)(object)_offlineLanRoot == (Object)null)
				{
					TryCreateOfflineLanUi("Update");
				}
				RefreshOfflineLanUi();
			}
		}

		private void RefreshOfflineLanUi()
		{
			if ((Object)(object)_offlineLanRoot != (Object)null)
			{
				_offlineLanRoot.SetActive(ShowOfflineLanPanel.Value && IsHostMenuVisible());
			}
			if ((Object)(object)_offlineLanStatusText != (Object)null)
			{
				string localIPv4Address = GetLocalIPv4Address();
				string text = ((_offlineLanRole == OfflineLanRole.None) ? "Idle" : _offlineLanRole.ToString());
				_offlineLanStatusText.text = $"{text} | {localIPv4Address}:{OfflineLanPort.Value} | {_offlineLanStatus}";
			}
			if ((Object)(object)_offlineLeaveButton != (Object)null)
			{
				_offlineLeaveButton.SetActive(OfflineLanActive || _offlineLanStarting);
			}
		}

		private void OfflineLanShutdownUi()
		{
			StopOfflineAuthListener();
		}

		private void HostOfflineLanFromUi()
		{
			PullOfflineLanUiValues();
			HostOfflineLan();
		}

		private void JoinOfflineLanFromUi()
		{
			PullOfflineLanUiValues();
			JoinOfflineLan();
		}

		private void PullOfflineLanUiValues()
		{
			if ((Object)(object)_offlineIpInput != (Object)null)
			{
				OfflineLanJoinAddress.Value = _offlineIpInput.text.Trim();
			}
			if ((Object)(object)_offlinePasscodeInput != (Object)null)
			{
				OfflineLanPasscode.Value = _offlinePasscodeInput.text;
			}
			if ((Object)(object)_offlineNameInput != (Object)null)
			{
				OfflineLanPlayerName.Value = (string.IsNullOrWhiteSpace(_offlineNameInput.text) ? Environment.UserName : _offlineNameInput.text.Trim());
			}
			if ((Object)(object)_offlinePortInput != (Object)null && ushort.TryParse(_offlinePortInput.text, out var result))
			{
				OfflineLanPort.Value = result;
			}
			_configFile.Save();
		}

		private static void HostOfflineLan()
		{
			if (_offlineLanStarting)
			{
				_offlineLanStatus = "Already starting";
			}
			else if (OfflineLanActive)
			{
				_offlineLanStatus = "Already active";
			}
			else if ((Object)(object)_instance == (Object)null)
			{
				_offlineLanStatus = "Plugin instance missing";
			}
			else
			{
				((MonoBehaviour)_instance).StartCoroutine(HostOfflineLanRoutine());
			}
		}

		private static IEnumerator HostOfflineLanRoutine()
		{
			_offlineLanStarting = true;
			if (!PrepareOfflineLanTransport())
			{
				_offlineLanStarting = false;
				yield break;
			}
			StopOfflineLanSession(loadMainMenu: false);
			if (!PrepareOfflineLanTransport())
			{
				_offlineLanStarting = false;
				yield break;
			}
			_offlineLanRole = OfflineLanRole.Host;
			_offlineLanStatus = "Starting host";
			AcceptedOfflineLanPeers.Clear();
			WhitelistIp("127.0.0.1", OfflineLanPlayerName.Value);
			WhitelistIp("::1", OfflineLanPlayerName.Value);
			WhitelistIp("localhost", OfflineLanPlayerName.Value);
			foreach (string localIPv4Address in GetLocalIPv4Addresses())
			{
				WhitelistIp(localIPv4Address, OfflineLanPlayerName.Value);
			}
			StartOfflineAuthListener();
			NetworkManager networkManager = InstanceFinder.NetworkManager;
			Transport transport = InstanceFinder.TransportManager.Transport;
			ApplyOfflineHostOptions();
			transport.SetPort(OfflineLanPort.Value);
			transport.SetServerBindAddress(string.Empty, (IPAddressType)0);
			int configuredMaxClients = GetConfiguredMaxClients();
			transport.SetMaximumClients(configuredMaxClients);
			Log.LogInfo((object)$"Offline LAN Tugboat maximum clients set to {configuredMaxClients}.");
			HookOfflineServerEvents(subscribe: true);
			if (!networkManager.ServerManager.StartConnection(OfflineLanPort.Value))
			{
				_offlineLanStatus = "Host failed";
				Log.LogError((object)"Offline LAN host failed to start Tugboat server.");
				CleanupFailedOfflineStart();
				_offlineLanStarting = false;
				yield break;
			}
			float timeoutAt2 = Time.realtimeSinceStartup + 3f;
			while (!networkManager.ServerManager.Started && Time.realtimeSinceStartup < timeoutAt2)
			{
				yield return null;
			}
			if (!networkManager.ServerManager.Started)
			{
				_offlineLanStatus = "Server start timed out";
				Log.LogError((object)"Offline LAN Tugboat server socket opened but FishNet did not report ServerManager.Started.");
				CleanupFailedOfflineStart();
				_offlineLanStarting = false;
				yield break;
			}
			if (!networkManager.ClientManager.StartConnection("127.0.0.1", OfflineLanPort.Value))
			{
				_offlineLanStatus = "Local client failed";
				Log.LogError((object)"Offline LAN host started, but local client failed to connect.");
				CleanupFailedOfflineStart();
				_offlineLanStarting = false;
				yield break;
			}
			timeoutAt2 = Time.realtimeSinceStartup + 3f;
			while (!networkManager.ClientManager.Started && Time.realtimeSinceStartup < timeoutAt2)
			{
				yield return null;
			}
			if (!networkManager.ClientManager.Started)
			{
				_offlineLanStatus = "Local client timed out";
				Log.LogError((object)"Offline LAN local client did not report ClientManager.Started.");
				CleanupFailedOfflineStart();
				_offlineLanStarting = false;
			}
			else
			{
				SetupOfflineLobbyUi(asHost: true);
				BroadcastOfflineMaxPlayers("host started");
				SpawnOfflineSceneMotorIfNeeded();
				_offlineLanStatus = $"Hosting {GetLocalIPv4Address()}:{OfflineLanPort.Value}";
				Log.LogInfo((object)$"Offline LAN host started on {GetLocalIPv4Address()}:{OfflineLanPort.Value}. Auth port={OfflineLanPort.Value + 1}.");
				_offlineLanStarting = false;
			}
		}

		private static void JoinOfflineLan()
		{
			string text = OfflineLanJoinAddress.Value.Trim();
			if (string.IsNullOrEmpty(text))
			{
				_offlineLanStatus = "Host IP required";
			}
			else if (_offlineLanStarting)
			{
				_offlineLanStatus = "Already starting";
			}
			else if (OfflineLanActive)
			{
				_offlineLanStatus = "Already active";
			}
			else if ((Object)(object)_instance == (Object)null)
			{
				_offlineLanStatus = "Plugin instance missing";
			}
			else
			{
				((MonoBehaviour)_instance).StartCoroutine(JoinOfflineLanRoutine(text));
			}
		}

		private static IEnumerator JoinOfflineLanRoutine(string host)
		{
			_offlineLanStarting = true;
			if (!PrepareOfflineLanTransport())
			{
				_offlineLanStarting = false;
				yield break;
			}
			StopOfflineLanSession(loadMainMenu: false);
			if (!PrepareOfflineLanTransport())
			{
				_offlineLanStarting = false;
				yield break;
			}
			_offlineLanRole = OfflineLanRole.Client;
			_offlineLanStatus = "Authenticating";
			if (!SendOfflineAuthRequest(host))
			{
				_offlineLanRole = OfflineLanRole.None;
				_offlineLanStarting = false;
				yield break;
			}
			Log.LogInfo((object)$"Offline LAN passcode accepted by {host}:{OfflineLanPort.Value + 1}.");
			NetworkManager networkManager = InstanceFinder.NetworkManager;
			Transport transport = InstanceFinder.TransportManager.Transport;
			transport.SetPort(OfflineLanPort.Value);
			transport.SetClientAddress(host);
			if (!networkManager.ClientManager.StartConnection(host, OfflineLanPort.Value))
			{
				_offlineLanStatus = "Join failed";
				Log.LogError((object)$"Offline LAN client failed to start connection to {host}:{OfflineLanPort.Value}.");
				CleanupFailedOfflineStart();
				_offlineLanStarting = false;
				yield break;
			}
			_offlineLanStatus = $"Connecting {host}:{OfflineLanPort.Value}";
			Log.LogInfo((object)$"Offline LAN client connecting to {host}:{OfflineLanPort.Value}.");
			float timeoutAt = Time.realtimeSinceStartup + 6f;
			while (!networkManager.ClientManager.Started && Time.realtimeSinceStartup < timeoutAt)
			{
				yield return null;
			}
			if (!networkManager.ClientManager.Started)
			{
				_offlineLanStatus = "Join timed out";
				Log.LogError((object)$"Offline LAN client timed out waiting for FishNet ClientManager.Started at {host}:{OfflineLanPort.Value}. Check Windows Firewall/UDP port {OfflineLanPort.Value} on the host.");
				CleanupFailedOfflineStart();
				_offlineLanStarting = false;
			}
			else
			{
				SetupOfflineLobbyUi(asHost: false);
				_offlineLanStatus = $"Connected {host}:{OfflineLanPort.Value}";
				Log.LogInfo((object)$"Offline LAN client connected to {host}:{OfflineLanPort.Value}.");
				_offlineLanStarting = false;
			}
		}

		private static bool PrepareOfflineLanTransport()
		{
			try
			{
				NetworkManager networkManager = InstanceFinder.NetworkManager;
				TransportManager transportManager = InstanceFinder.TransportManager;
				if ((Object)(object)networkManager == (Object)null || (Object)(object)transportManager == (Object)null)
				{
					_offlineLanStatus = "NetworkManager missing";
					Log.LogWarning((object)"Offline LAN cannot start because FishNet NetworkManager is not ready.");
					return false;
				}
				Tugboat val = (_offlineTugboat = ((Component)networkManager).GetComponent<Tugboat>() ?? ((Component)networkManager).gameObject.AddComponent<Tugboat>());
				if (!_offlineTransportBound || (Object)(object)transportManager.Transport != (Object)(object)val)
				{
					RebindTransport(networkManager, val);
					_offlineTransportBound = true;
				}
				else if (_serverConnectionDelegate == null || _remoteConnectionDelegate == null || _serverReceivedDelegate == null)
				{
					HookServerManagerToTugboat(networkManager, val);
				}
				if (_clientConnectionLogDelegate == null)
				{
					HookClientConnectionLogging(val);
				}
				PauseManager.Instance.nonSteamworksTransport = false;
				return true;
			}
			catch (Exception arg)
			{
				_offlineLanStatus = "Transport exception";
				Log.LogError((object)$"Failed to prepare Tugboat transport: {arg}");
				return false;
			}
		}

		private static void RebindTransport(NetworkManager networkManager, Tugboat tugboat)
		{
			TransportManager transportManager = networkManager.TransportManager;
			InvokeTransportSubscription(networkManager.ClientManager, subscribe: false);
			UnhookServerManagerFromTugboat();
			transportManager.Transport = (Transport)(object)tugboat;
			((Transport)tugboat).Initialize(networkManager, 0);
			ResetTransportManagerBuffers(transportManager);
			InvokeTransportSubscription(networkManager.ClientManager, subscribe: true);
			HookServerManagerToTugboat(networkManager, tugboat);
			HookClientConnectionLogging(tugboat);
			Log.LogInfo((object)"Offline LAN switched FishNet transport to Tugboat.");
		}

		private static void HookServerManagerToTugboat(NetworkManager networkManager, Tugboat tugboat)
		{
			ServerManager serverManager = networkManager.ServerManager;
			MethodInfo serverConnectionMethod = AccessTools.Method(((object)serverManager).GetType(), "Transport_OnServerConnectionState", (Type[])null, (Type[])null);
			MethodInfo remoteConnectionMethod = AccessTools.Method(((object)serverManager).GetType(), "Transport_OnRemoteConnectionState", (Type[])null, (Type[])null);
			MethodInfo serverReceivedMethod = AccessTools.Method(((object)serverManager).GetType(), "Transport_OnServerReceivedData", (Type[])null, (Type[])null);
			if (serverConnectionMethod == null || remoteConnectionMethod == null || serverReceivedMethod == null)
			{
				Log.LogWarning((object)"Offline LAN could not find FishNet ServerManager transport handlers.");
				return;
			}
			_serverConnectionDelegate = delegate(ServerConnectionStateArgs args)
			{
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_000b: 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)
				Log.LogInfo((object)$"Offline LAN Tugboat server state: {args.ConnectionState}.");
				serverConnectionMethod.Invoke(serverManager, new object[1] { args });
			};
			_remoteConnectionDelegate = delegate(RemoteConnectionStateArgs args)
			{
				//IL_0006: 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)
				//IL_002c: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				string arg = NormalizePeerAddress(((Transport)tugboat).GetConnectionAddress(args.ConnectionId));
				Log.LogInfo((object)$"Offline LAN Tugboat remote state: connection={args.ConnectionId}, state={args.ConnectionState}, address={arg}.");
				remoteConnectionMethod.Invoke(serverManager, new object[1] { args });
			};
			_serverReceivedDelegate = delegate(ServerReceivedDataArgs args)
			{
				//IL_0014: Unknown result type (might be due to invalid IL or missing references)
				serverReceivedMethod.Invoke(serverManager, new object[1] { args });
			};
			((Transport)tugboat).OnServerConnectionState += _serverConnectionDelegate;
			((Transport)tugboat).OnRemoteConnectionState += _remoteConnectionDelegate;
			((Transport)tugboat).OnServerReceivedData += _serverReceivedDelegate;
			Log.LogInfo((object)"Offline LAN bound FishNet ServerManager to Tugboat events.");
		}

		private static void HookClientConnectionLogging(Tugboat tugboat)
		{
			if (!((Object)(object)tugboat == (Object)null) && _clientConnectionLogDelegate == null)
			{
				_clientConnectionLogDelegate = delegate(ClientConnectionStateArgs args)
				{
					//IL_000a: Unknown result type (might be due to invalid IL or missing references)
					//IL_000b: Unknown result type (might be due to invalid IL or missing references)
					Log.LogInfo((object)$"Offline LAN Tugboat client state: {args.ConnectionState}.");
				};
				((Transport)tugboat).OnClientConnectionState += _clientConnectionLogDelegate;
			}
		}

		private static void UnhookClientConnectionLogging()
		{
			if ((Object)(object)_offlineTugboat != (Object)null && _clientConnectionLogDelegate != null)
			{
				((Transport)_offlineTugboat).OnClientConnectionState -= _clientConnectionLogDelegate;
			}
			_clientConnectionLogDelegate = null;
		}

		private static void UnhookServerManagerFromTugboat()
		{
			if ((Object)(object)_offlineTugboat != (Object)null)
			{
				if (_serverConnectionDelegate != null)
				{
					((Transport)_offlineTugboat).OnServerConnectionState -= _serverConnectionDelegate;
				}
				if (_remoteConnectionDelegate != null)
				{
					((Transport)_offlineTugboat).OnRemoteConnectionState -= _remoteConnectionDelegate;
				}
				if (_serverReceivedDelegate != null)
				{
					((Transport)_offlineTugboat).OnServerReceivedData -= _serverReceivedDelegate;
				}
			}
			_serverConnectionDelegate = null;
			_remoteConnectionDelegate = null;
			_serverReceivedDelegate = null;
			UnhookClientConnectionLogging();
		}

		private static void InvokeTransportSubscription(object manager, bool subscribe)
		{
			if (manager != null)
			{
				MethodInfo methodInfo = AccessTools.Method(manager.GetType(), "SubscribeToEvents", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					methodInfo.Invoke(manager, new object[1] { subscribe });
				}
			}
		}

		private static void ResetTransportManagerBuffers(object transportManager)
		{
			AccessTools.Field(transportManager.GetType(), "_lowestMtu")?.SetValue(transportManager, new int[2]);
			(AccessTools.Field(transportManager.GetType(), "_toServerBundles")?.GetValue(transportManager) as IList)?.Clear();
			AccessTools.Method(transportManager.GetType(), "InitializeToServerBundles", (Type[])null, (Type[])null)?.Invoke(transportManager, Array.Empty<object>());
		}

		private static int GetConfiguredMaxClients()
		{
			try
			{
				SteamLobby instance = SteamLobby.Instance;
				if ((Object)(object)instance != (Object)null && (Object)(object)instance.MaxPlayersDropdown != (Object)null)
				{
					return Mathf.Max(2, instance.maxPlayers);
				}
			}
			catch
			{
			}
			return 4;
		}

		private static void SetupOfflineLobbyUi(bool asHost)
		{
			SteamLobby instance = SteamLobby.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if ((Object)(object)MenuController.Instance != (Object)null && !MenuController.Instance.playMenu.activeSelf)
				{
					MenuController.Instance.OpenGame();
				}
				instance.inSteamLobby = true;
				instance.CurrentLobbyID = 0uL;
				ApplyOfflineHostOptions();
				if (string.IsNullOrWhiteSpace(instance.lobbyName))
				{
					instance.lobbyName = "Offline LAN";
				}
				if ((Object)(object)instance.LobbyWindow != (Object)null)
				{
					instance.LobbyWindow.SetActive(true);
				}
				if ((Object)(object)instance.LobbiesBrowser != (Object)null)
				{
					instance.LobbiesBrowser.SetActive(false);
				}
				if ((Object)(object)instance.HostButton != (Object)null)
				{
					instance.HostButton.SetActive(!asHost);
				}
				if ((Object)(object)instance.StopButton != (Object)null)
				{
					instance.StopButton.SetActive(asHost);
				}
				if ((Object)(object)instance.LobbyNameText != (Object)null)
				{
					((TMP_Text)instance.LobbyNameText).text = (asHost ? instance.lobbyName : "Offline LAN Client");
				}
				if ((Object)(object)instance.lobbyIdText != (Object)null)
				{
					instance.lobbyIdText.SetLobbyIDText(asHost ? $"{GetLocalIPv4Address()}:{OfflineLanPort.Value}" : $"{OfflineLanJoinAddress.Value}:{OfflineLanPort.Value}");
				}
				instance.SetHUDActive(true);
				if ((Object)(object)PauseManager.Instance != (Object)null)
				{
					PauseManager.Instance.serverStarted = asHost;
					PauseManager.Instance.WriteOfflineLog(asHost ? $"Offline LAN hosting on {GetLocalIPv4Address()}:{OfflineLanPort.Value}" : $"Offline LAN joining {OfflineLanJoinAddress.Value}:{OfflineLanPort.Value}");
				}
			}
		}

		private static void ApplyOfflineHostOptions()
		{
			SteamLobby instance = SteamLobby.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if ((Object)(object)instance.MaxPlayersDropdown != (Object)null)
				{
					instance.maxPlayers = instance.MaxPlayersDropdown.value + 2;
				}
				else if (instance.maxPlayers < 2)
				{
					instance.maxPlayers = 4;
				}
				if ((Object)(object)instance.LobbyTypeDropdownBeforeLobby != (Object)null)
				{
					instance.SetLobbyType(instance.LobbyTypeDropdownBeforeLobby);
				}
				if ((Object)(object)instance.GamemodeDropdown != (Object)null)
				{
					instance.SetGamemode(instance.GamemodeDropdown);
				}
				Transport val = InstanceFinder.TransportManager?.Transport;
				if ((Object)(object)val != (Object)null)
				{
					val.SetMaximumClients(Mathf.Max(2, instance.maxPlayers));
				}
			}
		}

		private static void BroadcastOfflineMaxPlayers(string reason)
		{
			try
			{
				SteamLobby instance = SteamLobby.Instance;
				if (!((Object)(object)instance == (Object)null))
				{
					int num = Mathf.Max(2, instance.maxPlayers);
					Transport obj = InstanceFinder.TransportManager?.Transport;
					if (obj != null)
					{
						obj.SetMaximumClients(num);
					}
					MethodInfo methodInfo = AccessTools.Method(typeof(ClientInstance), "RpcLogic___UpdateObserversMaxPlayers_3316948804", (Type[])null, (Type[])null);
					ClientInstance[] array = Object.FindObjectsOfType<ClientInstance>();
					foreach (ClientInstance obj2 in array)
					{
						methodInfo?.Invoke(obj2, new object[1] { num });
					}
					if ((Object)(object)InstanceFinder.ServerManager != (Object)null && InstanceFinder.ServerManager.Started && (Object)(object)LobbyController.Instance != (Object)null && (Object)(object)LobbyController.Instance.LocalPlayerController != (Object)null)
					{
						LobbyController.Instance.LocalPlayerController.UpdateServerMaxPlayers();
					}
					LobbyController instance2 = LobbyController.Instance;
					if (instance2 != null)
					{
						instance2.UpdatePlayerList();
					}
					Log.LogInfo((object)$"Offline LAN broadcast max players={num} ({reason}).");
				}
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Offline LAN failed to broadcast max players ({reason}): {arg}");
			}
		}

		private static void SpawnOfflineSceneMotorIfNeeded()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!((Object)(object)SceneMotor.Instance != (Object)null))
				{
					SteamLobby instance = SteamLobby.Instance;
					object? obj = AccessTools.Field(typeof(SteamLobby), "_sceneMotorPrefab")?.GetValue(instance);
					GameObject val = (GameObject)((obj is GameObject) ? obj : null);
					if ((Object)(object)val == (Object)null)
					{
						Log.LogWarning((object)"Offline LAN could not find SteamLobby._sceneMotorPrefab; lobby may not be startable.");
						return;
					}
					GameObject val2 = Object.Instantiate<GameObject>(val, ((Component)instance).transform.position, Quaternion.identity);
					InstanceFinder.ServerManager.Spawn(val2, (NetworkConnection)null);
					Log.LogInfo((object)"Offline LAN spawned SceneMotor prefab.");
				}
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Offline LAN failed to spawn SceneMotor: {arg}");
			}
		}

		private static void StopOfflineLanSession(bool loadMainMenu)
		{
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			if (_offlineLeaveInProgress)
			{
				return;
			}
			_offlineLeaveInProgress = true;
			try
			{
				HookOfflineServerEvents(subscribe: false);
				StopOfflineAuthListener();
				UnhookServerManagerFromTugboat();
				NetworkManager networkManager = InstanceFinder.NetworkManager;
				if ((Object)(object)networkManager != (Object)null)
				{
					if ((Object)(object)networkManager.ClientManager != (Object)null && networkManager.ClientManager.Started)
					{
						networkManager.ClientManager.StopConnection();
					}
					if ((Object)(object)networkManager.ServerManager != (Object)null && networkManager.ServerManager.Started)
					{
						networkManager.ServerManager.StopConnection(true);
					}
				}
				SteamLobby instance = SteamLobby.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					instance.inSteamLobby = false;
					instance.CurrentLobbyID = 0uL;
					instance.players.Clear();
					if ((Object)(object)instance.LobbyWindow != (Object)null)
					{
						instance.LobbyWindow.SetActive(false);
					}
					if ((Object)(object)instance.HostButton != (Object)null)
					{
						instance.HostButton.SetActive(true);
					}
					if ((Object)(object)instance.StopButton != (Object)null)
					{
						instance.StopButton.SetActive(false);
					}
					if ((Object)(object)instance.lobbyIdText != (Object)null)
					{
						instance.lobbyIdText.SetLobbyIDText("Not In a Lobby");
					}
					instance.SetHUDActive(false);
				}
				ClientInstance.playerInstances.Clear();
				AcceptedOfflineLanPeers.Clear();
				OfflineLanConnectionNames.Clear();
				if ((Object)(object)PauseManager.Instance != (Object)null)
				{
					PauseManager.Instance.serverStarted = false;
				}
				if (loadMainMenu)
				{
					Scene activeScene = SceneManager.GetActiveScene();
					if (((Scene)(ref activeScene)).name != "MainMenu")
					{
						SceneManager.LoadScene("MainMenu");
					}
				}
				_offlineLanRole = OfflineLanRole.None;
				_offlineLanStarting = false;
				_offlineLanStatus = "Stopped";
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Offline LAN stop failed: {arg}");
			}
			finally
			{
				_offlineLeaveInProgress = false;
			}
		}

		private static void CleanupFailedOfflineStart()
		{
			try
			{
				HookOfflineServerEvents(subscribe: false);
				StopOfflineAuthListener();
				NetworkManager networkManager = InstanceFinder.NetworkManager;
				if ((Object)(object)networkManager != (Object)null)
				{
					if ((Object)(object)networkManager.ClientManager != (Object)null)
					{
						networkManager.ClientManager.StopConnection();
					}
					if ((Object)(object)networkManager.ServerManager != (Object)null)
					{
						networkManager.ServerManager.StopConnection(false);
					}
				}
				_offlineLanRole = OfflineLanRole.None;
				AcceptedOfflineLanPeers.Clear();
				OfflineLanConnectionNames.Clear();
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Offline LAN failed-start cleanup failed: {arg}");
			}
		}

		private static void HookOfflineServerEvents(bool subscribe)
		{
			ServerManager serverManager = InstanceFinder.ServerManager;
			if (!((Object)(object)serverManager == (Object)null))
			{
				serverManager.OnRemoteConnectionState -= OfflineServerRemoteConnectionState;
				if (subscribe)
				{
					serverManager.OnRemoteConnectionState += OfflineServerRemoteConnectionState;
				}
			}
		}

		private static void OfflineServerRemoteConnectionState(NetworkConnection connection, RemoteConnectionStateArgs args)
		{
			//IL_000f: 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_0016: Invalid comparison between Unknown and I4
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: 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_004b: Unknown result type (might be due to invalid IL or missing references)
			if (!OfflineLanActive || _offlineLanRole != OfflineLanRole.Host || (int)args.ConnectionState != 2)
			{
				return;
			}
			string text = NormalizePeerAddress(InstanceFinder.TransportManager.Transport.GetConnectionAddress(args.ConnectionId));
			if (IsWhitelisted(text))
			{
				if (TryGetWhitelistedName(text, out var name))
				{
					OfflineLanConnectionNames[args.ConnectionId] = name;
				}
				Log.LogInfo((object)$"Offline LAN accepted connection {args.ConnectionId} from {text}.");
				BroadcastOfflineMaxPlayers("remote connected");
			}
			else
			{
				Log.LogWarning((object)$"Offline LAN rejected unauthenticated connection {args.ConnectionId} from {text}.");
				InstanceFinder.ServerManager.Kick(args.ConnectionId, (KickReason)0, (LoggingType)3, "Offline LAN passcode was not accepted.");
			}
		}

		private static void StartOfflineAuthListener()
		{
			StopOfflineAuthListener();
			_offlineAuthRunning = true;
			_offlineAuthThread = new Thread(OfflineAuthLoop)
			{
				IsBackground = true,
				Name = "StraftatOfflineLanAuth"
			};
			_offlineAuthThread.Start();
		}

		private static void StopOfflineAuthListener()
		{
			_offlineAuthRunning = false;
			try
			{
				_offlineAuthListener?.Close();
			}
			catch
			{
			}
			_offlineAuthListener = null;
		}

		private static void OfflineAuthLoop()
		{
			try
			{
				_offlineAuthListener = new UdpClient(OfflineLanPort.Value + 1);
				while (_offlineAuthRunning)
				{
					IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
					byte[] bytes = _offlineAuthListener.Receive(ref remoteEP);
					string[] array = Encoding.UTF8.GetString(bytes).Split(new char[1] { '|' });
					bool num = array.Length >= 4 && array[0] == "SLAN1" && ConstantTimeEquals(array[1], OfflineLanPasscode.Value);
					string name = ((array.Length >= 3) ? array[2] : "LAN Player");
					string s = "NO";
					if (num)
					{
						string text = NormalizePeerAddress(remoteEP.Address.ToString());
						name = SanitizeOfflineLanName(name);
						WhitelistIp(text, name);
						s = "OK|STRAFTAT LAN|" + SanitizeOfflineLanName(OfflineLanPlayerName.Value);
						Log.LogInfo((object)$"Offline LAN auth accepted {text}:{remoteEP.Port} as {name}.");
					}
					else
					{
						Log.LogWarning((object)$"Offline LAN auth rejected {NormalizePeerAddress(remoteEP.Address.ToString())}:{remoteEP.Port}.");
					}
					byte[] bytes2 = Encoding.UTF8.GetBytes(s);
					_offlineAuthListener.Send(bytes2, bytes2.Length, remoteEP);
				}
			}
			catch (SocketException)
			{
			}
			catch (ObjectDisposedException)
			{
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Offline LAN auth listener stopped unexpectedly: {arg}");
			}
		}

		private static bool SendOfflineAuthRequest(string host)
		{
			try
			{
				using UdpClient udpClient = new UdpClient();
				udpClient.Client.ReceiveTimeout = 700;
				IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(host), OfflineLanPort.Value + 1);
				byte[] bytes = Encoding.UTF8.GetBytes("SLAN1|" + OfflineLanPasscode.Value + "|" + SanitizeOfflineLanName(OfflineLanPlayerName.Value) + "|" + Application.version);
				for (int i = 1; i <= 3; i++)
				{
					udpClient.Send(bytes, bytes.Length, endPoint);
					try
					{
						IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
						string @string = Encoding.UTF8.GetString(udpClient.Receive(ref remoteEP));
						if (@string.StartsWith("OK|", StringComparison.Ordinal))
						{
							string[] array = @string.Split(new char[1] { '|' });
							if (array.Length >= 3)
							{
								WhitelistIp(host, array[2]);
							}
							_offlineLanStatus = "Passcode accepted";
							return true;
						}
						_offlineLanStatus = "Passcode rejected";
						Log.LogWarning((object)"Offline LAN host rejected the passcode.");
						return false;
					}
					catch (SocketException)
					{
						_offlineLanStatus = $"Auth retry {i}/3";
					}
				}
			}
			catch (Exception ex2)
			{
				_offlineLanStatus = "Auth exception";
				Log.LogWarning((object)("Offline LAN auth request failed: " + ex2.Message));
				return false;
			}
			_offlineLanStatus = "No auth reply";
			return false;
		}

		private static void WhitelistIp(string ip, string name)
		{
			if (!string.IsNullOrWhiteSpace(ip))
			{
				AcceptedOfflineLanPeers[NormalizePeerAddress(ip)] = new OfflineLanAuthInfo
				{
					Name = SanitizeOfflineLanName(name),
					AcceptedAtUtc = DateTime.UtcNow
				};
			}
		}

		private static bool TryGetWhitelistedName(string ip, out string name)
		{
			name = null;
			if (AcceptedOfflineLanPeers.TryGetValue(NormalizePeerAddress(ip), out var value) && !string.IsNullOrWhiteSpace(value.Name))
			{
				name = value.Name;
				return true;
			}
			return false;
		}

		private static string SanitizeOfflineLanName(string name)
		{
			if (string.IsNullOrWhiteSpace(name))
			{
				return "LAN Player";
			}
			name = name.Trim().Replace('|', '/');
			if (name.Length > 32)
			{
				return name.Substring(0, 32);
			}
			return name;
		}

		private static bool IsWhitelisted(string ip)
		{
			ip = NormalizePeerAddress(ip);
			switch (ip)
			{
			case "127.0.0.1":
			case "::1":
			case "localhost":
				return true;
			default:
				return AcceptedOfflineLanPeers.ContainsKey(ip);
			}
		}

		private static string NormalizePeerAddress(string address)
		{
			if (string.IsNullOrWhiteSpace(address))
			{
				return string.Empty;
			}
			address = address.Trim();
			if (address.StartsWith("[", StringComparison.Ordinal))
			{
				int num = address.IndexOf(']');
				if (num <= 1)
				{
					return address;
				}
				return address.Substring(1, num - 1);
			}
			if (address.StartsWith("::ffff:", StringComparison.OrdinalIgnoreCase))
			{
				address = address.Substring("::ffff:".Length);
			}
			int num2 = address.LastIndexOf(':');
			if (num2 > 0 && address.IndexOf(':') == num2)
			{
				address = address.Substring(0, num2);
			}
			return address;
		}

		private static bool ConstantTimeEquals(string left, string right)
		{
			left = left ?? string.Empty;
			right = right ?? string.Empty;
			int num = left.Length ^ right.Length;
			int num2 = Math.Max(left.Length, right.Length);
			for (int i = 0; i < num2; i++)
			{
				char c = ((i < left.Length) ? left[i] : '\0');
				char c2 = ((i < right.Length) ? right[i] : '\0');
				num |= c ^ c2;
			}
			return num == 0;
		}

		private static string GetLocalIPv4Address()
		{
			if (Time.unscaledTime < _nextLocalIpRefreshAt && !string.IsNullOrWhiteSpace(_cachedLocalIPv4Address))
			{
				return _cachedLocalIPv4Address;
			}
			_nextLocalIpRefreshAt = Time.unscaledTime + 5f;
			_cachedLocalIPv4Address = GetLocalIPv4Addresses().FirstOrDefault() ?? "127.0.0.1";
			return _cachedLocalIPv4Address;
		}

		private static IReadOnlyList<string> GetLocalIPv4Addresses()
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			try
			{
				NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
				foreach (NetworkInterface networkInterface in allNetworkInterfaces)
				{
					if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Tunnel)
					{
						continue;
					}
					IPInterfaceProperties iPProperties = networkInterface.GetIPProperties();
					bool flag = iPProperties.GatewayAddresses.Any((GatewayIPAddressInformation g) => g.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.Any.Equals(g.Address));
					foreach (UnicastIPAddressInformation unicastAddress in iPProperties.UnicastAddresses)
					{
						IPAddress address2 = unicastAddress.Address;
						if (address2.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(address2) && !address2.ToString().StartsWith("169.254.", StringComparison.Ordinal))
						{
							(flag ? list : list2).Add(address2.ToString());
						}
					}
				}
			}
			catch
			{
			}
			if (list.Count > 0)
			{
				return list.Distinct().ToList();
			}
			if (list2.Count > 0)
			{
				return list2.Distinct().ToList();
			}
			try
			{
				return (from address in Dns.GetHostEntry(Dns.GetHostName()).AddressList
					where address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.IsLoopback(address)
					select address.ToString()).Distinct().ToList();
			}
			catch
			{
				return new string[1] { "127.0.0.1" };
			}
		}

		private static ulong GetOfflineLanId(NetworkConnection owner, int playerId)
		{
			int num = ((owner == (NetworkConnection)null) ? playerId : owner.ClientId);
			return (ulong)(90000000000000000L + Mathf.Max(0, num + 1));
		}

		private static string GetOfflineLanPlayerName(NetworkConnection owner, int playerId, ulong lanId)
		{
			if (owner != (NetworkConnection)null && owner.IsLocalClient)
			{
				return SanitizeOfflineLanName(OfflineLanPlayerName.Value);
			}
			if (owner != (NetworkConnection)null)
			{
				if (OfflineLanConnectionNames.TryGetValue(owner.ClientId, out var value) && !string.IsNullOrWhiteSpace(value))
				{
					return value;
				}
				TransportManager transportManager = InstanceFinder.TransportManager;
				object address;
				if (transportManager == null)
				{
					address = null;
				}
				else
				{
					Transport transport = transportManager.Transport;
					address = ((transport != null) ? transport.GetConnectionAddress(owner.ClientId) : null);
				}
				if (TryGetWhitelistedName(NormalizePeerAddress((string)address), out var name))
				{
					return name;
				}
			}
			foreach (OfflineLanAuthInfo item in AcceptedOfflineLanPeers.Values.OrderByDescending((OfflineLanAuthInfo v) => v.AcceptedAtUtc))
			{
				if (!string.IsNullOrWhiteSpace(item.Name))
				{
					return item.Name;
				}
			}
			if (playerId != 0)
			{
				return $"LAN Player {playerId + 1}";
			}
			return "LAN Host";
		}

		private static bool ShouldRedirectCreateLobbyToOfflineLan()
		{
			if (!OfflineLanEnabled.Value || !RedirectCreateLobbyToOfflineLan.Value)
			{
				return false;
			}
			if (!ForceOfflineSteamLobbyStartup.Value)
			{
				return OfflineLanActive;
			}
			return true;
		}

		private static void HostOfflineLanFromNormalButton(SteamLobby lobby)
		{
			try
			{
				ApplyOfflineHostOptions();
				Log.LogInfo((object)"Redirecting STRAFTAT Create Lobby button to Offline LAN host.");
				HostOfflineLan();
			}
			catch (Exception arg)
			{
				_offlineLanStatus = "Host button exception";
				Log.LogError((object)$"Offline LAN Create Lobby redirect failed: {arg}");
			}
		}

		private void Awake()
		{
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b5: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_instance = this;
			_configFile = ((BaseUnityPlugin)this).Config;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Master enable switch.");
			Mode = ((BaseUnityPlugin)this).Config.Bind<RoutingMode>("Routing", "Mode", RoutingMode.ObserveOnly, "ObserveOnly = neutral Internet routing. PreferDirect = LAN/direct preferred. ExperimentalNoRelay = test only.");
			VerboseSteamNetworkingLogs = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "VerboseSteamNetworkingLogs", true, "Forward Steam Networking diagnostics to BepInEx logs.");
			AllowUnencryptedSegments = ((BaseUnityPlugin)this).Config.Bind<bool>("Routing", "AllowUnencryptedSegments", false, "Dangerous/experimental. Only used if this Steamworks.NET build exposes the setting.");
			ShowLobbyToggle = ((BaseUnityPlugin)this).Config.Bind<bool>("Lobby UI", "ShowLobbyToggle", false, "Legacy Steam-routing selector. Offline LAN uses its own panel, so this should normally stay off.");
			ToggleX = ((BaseUnityPlugin)this).Config.Bind<int>("Lobby UI", "ToggleX", 775, "Connection mode selector X position in screen pixels.");
			ToggleY = ((BaseUnityPlugin)this).Config.Bind<int>("Lobby UI", "ToggleY", 410, "Connection mode selector Y position in screen pixels.");
			ConfigureOfflineLan(((BaseUnityPlugin)this).Config);
			Mode.SettingChanged += delegate
			{
				ApplyRoutingMode("config changed");
			};
			VerboseSteamNetworkingLogs.SettingChanged += delegate
			{
				RegisterSteamCallbacks("diagnostics setting changed");
			};
			AllowUnencryptedSegments.SettingChanged += delegate
			{
				ApplyRoutingMode("unencrypted setting changed");
			};
			if (!Enabled.Value)
			{
				Log.LogInfo((object)"Plugin disabled by config.");
				return;
			}
			_harmony = new Harmony("com.local.straftat.p2p");
			_harmony.PatchAll(typeof(SteamApiPatches));
			PatchOptionalLobbyMethods();
			PatchFishySteamworksConnectionMethods();
			PatchOfflineLanMethods();
			Log.LogInfo((object)string.Format("{0} {1} loaded in {2} mode.", "STRAFTAT LAN P2P Optimizer", "0.2.0", Mode.Value));
			DiscoverRuntimeTypes();
			ApplyRoutingMode("plugin awake");
			if (ShouldShowLobbyNetworkUi())
			{
				TryCreateLobbyNetworkUi("Awake");
			}
			TryCreateOfflineLanUi("Awake");
		}

		private void Start()
		{
			if (ShouldShowLobbyNetworkUi())
			{
				TryCreateLobbyNetworkUi("Start");
			}
			TryCreateOfflineLanUi("Start");
			RefreshLobbyNetworkUi();
		}

		private void OnDestroy()
		{
			OfflineLanShutdownUi();
			_connectionStatusCallback?.Dispose();
			_connectionStatusCallback = null;
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void Update()
		{
			if (Enabled.Value)
			{
				if (ShouldShowLobbyNetworkUi() && (Object)(object)_uiRoot == (Object)null)
				{
					TryCreateLobbyNetworkUi("Update");
				}
				RefreshLobbyNetworkUi();
				OfflineLanUpdate();
				if (ShouldShowLobbyNetworkUi() && Input.GetKeyDown((KeyCode)289))
				{
					SetLobbyMode((!IsLanMode()) ? RoutingMode.PreferDirect : RoutingMode.ObserveOnly, "F8 hotkey");
				}
				if (Time.unscaledTime >= _nextApplyAttemptAt)
				{
					_nextApplyAttemptAt = Time.unscaledTime + 5f;
					ApplyRoutingMode("periodic retry");
					RegisterSteamCallbacks("periodic retry");
				}
			}
		}

		private void TryCreateLobbyNetworkUi(string reason)
		{
			try
			{
				CreateLobbyNetworkUi();
				RefreshLobbyNetworkUi();
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Failed to create Lobby Network selector UI during {reason}: {arg}");
			}
		}

		private void CreateLobbyNetworkUi()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_020c: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_uiRoot != (Object)null))
			{
				EnsureEventSystem();
				_uiRoot = new GameObject("StraftatLocalP2P_LobbyNetworkSelector");
				Object.DontDestroyOnLoad((Object)(object)_uiRoot);
				Canvas obj = _uiRoot.AddComponent<Canvas>();
				obj.renderMode = (RenderMode)0;
				obj.sortingOrder = 32767;
				_uiRoot.AddComponent<CanvasScaler>().uiScaleMode = (ScaleMode)0;
				_uiRoot.AddComponent<GraphicRaycaster>();
				GameObject val = CreateUiObject("Panel", _uiRoot.transform);
				RectTransform obj2 = val.AddComponent<RectTransform>();
				obj2.anchorMin = new Vector2(0f, 1f);
				obj2.anchorMax = new Vector2(0f, 1f);
				obj2.pivot = new Vector2(0f, 1f);
				obj2.anchoredPosition = new Vector2((float)ToggleX.Value, (float)(-ToggleY.Value));
				obj2.sizeDelta = new Vector2(245f, 102f);
				((Graphic)val.AddComponent<Image>()).color = new Color(0f, 0f, 0f, 0.58f);
				CreateText(val.transform, "Title", "Connection Mode", new Vector2(10f, -8f), new Vector2(225f, 22f), 15, (FontStyle)1);
				_statusText = CreateText(val.transform, "Status", "", new Vector2(10f, -34f), new Vector2(225f, 20f), 12, (FontStyle)0);
				CreateButton(val.transform, "InternetButton", "Internet", new Vector2(10f, -66f), new Vector2(106f, 28f), delegate
				{
					SetLobbyMode(RoutingMode.ObserveOnly, "lobby selector");
				});
				CreateButton(val.transform, "LanButton", "LAN", new Vector2(128f, -66f), new Vector2(106f, 28f), delegate
				{
					SetLobbyMode(RoutingMode.PreferDirect, "lobby selector");
				});
				Log.LogInfo((object)"Created Lobby Network selector UI. Press F8 to toggle LAN/Internet if the selector is hidden.");
			}
		}

		private static void EnsureEventSystem()
		{
			//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_001e: Expected O, but got Unknown
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)EventSystem.current != (Object)null))
			{
				GameObject val = new GameObject("StraftatLocalP2P_EventSystem");
				Object.DontDestroyOnLoad((Object)val);
				val.AddComponent<EventSystem>();
				val.AddComponent<StandaloneInputModule>();
			}
		}

		private static GameObject CreateUiObject(string name, Transform parent)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent, false);
			return val;
		}

		private static Text CreateText(Transform parent, string name, string text, Vector2 position, Vector2 size, int fontSize, FontStyle style)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			GameObject obj = CreateUiObject(name, parent);
			RectTransform obj2 = obj.AddComponent<RectTransform>();
			obj2.anchorMin = new Vector2(0f, 1f);
			obj2.anchorMax = new Vector2(0f, 1f);
			obj2.pivot = new Vector2(0f, 1f);
			obj2.anchoredPosition = position;
			obj2.sizeDelta = size;
			Text obj3 = obj.AddComponent<Text>();
			obj3.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
			obj3.fontSize = fontSize;
			obj3.fontStyle = style;
			obj3.alignment = (TextAnchor)3;
			((Graphic)obj3).color = Color.white;
			obj3.text = text;
			return obj3;
		}

		private static GameObject CreateButton(Transform parent, string name, string text, Vector2 position, Vector2 size, Action onClick)
		{
			//IL_0026: 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_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: 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_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			//IL_00bd: 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)
			GameObject obj = CreateUiObject(name, parent);
			RectTransform obj2 = obj.AddComponent<RectTransform>();
			obj2.anchorMin = new Vector2(0f, 1f);
			obj2.anchorMax = new Vector2(0f, 1f);
			obj2.pivot = new Vector2(0f, 1f);
			obj2.anchoredPosition = position;
			obj2.sizeDelta = size;
			Image val = obj.AddComponent<Image>();
			((Graphic)val).color = new Color(0.16f, 0.18f, 0.2f, 0.95f);
			Button obj3 = obj.AddComponent<Button>();
			((Selectable)obj3).targetGraphic = (Graphic)(object)val;
			((UnityEvent)obj3.onClick).AddListener((UnityAction)delegate
			{
				onClick();
			});
			CreateText(obj.transform, "Label", text, Vector2.zero, size, 14, (FontStyle)1).alignment = (TextAnchor)4;
			return obj;
		}

		private void RefreshLobbyNetworkUi()
		{
			if ((Object)(object)_uiRoot != (Object)null)
			{
				_uiRoot.SetActive(ShouldShowLobbyNetworkUi() && IsHostMenuVisible());
			}
			if ((Object)(object)_statusText != (Object)null)
			{
				_statusText.text = (IsLanMode() ? "This PC: LAN preferred" : "This PC: Internet");
			}
		}

		private static bool IsHostMenuVisible()
		{
			if (Time.unscaledTime < _nextHostMenuVisibilityCheckAt)
			{
				return _cachedHostMenuVisible;
			}
			_nextHostMenuVisibilityCheckAt = Time.unscaledTime + 0.5f;
			try
			{
				bool flag = false;
				bool flag2 = false;
				bool flag3 = false;
				TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
				foreach (TMP_Text val in array)
				{
					if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy)
					{
						continue;
					}
					string text = val.text;
					if (!string.IsNullOrEmpty(text))
					{
						if (text.IndexOf("Create Lobby", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							flag = true;
						}
						else if (text.IndexOf("Lobby Name", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							flag2 = true;
						}
						else if (text.IndexOf("Max Players", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							flag3 = true;
						}
					}
				}
				_cachedHostMenuVisible = flag && flag2 && flag3 && !IsSettingsTextVisible();
				return _cachedHostMenuVisible;
			}
			catch (Exception ex)
			{
				Log.LogDebug((object)("Host menu visibility check failed: " + ex.Message));
				_cachedHostMenuVisible = false;
				return false;
			}
		}

		private static bool ShouldShowLobbyNetworkUi()
		{
			if (Enabled.Value && ShowLobbyToggle.Value)
			{
				if (OfflineLanEnabled != null)
				{
					return !OfflineLanEnabled.Value;
				}
				return true;
			}
			return false;
		}

		private static bool IsSettingsTextVisible()
		{
			TMP_Text[] array = Resources.FindObjectsOfTypeAll<TMP_Text>();
			foreach (TMP_Text val in array)
			{
				if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy)
				{
					string text = val.text;
					if (!string.IsNullOrEmpty(text) && (text.IndexOf("Settings", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Options", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Controls", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Resolution", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("Sensitivity", StringComparison.OrdinalIgnoreCase) >= 0))
					{
						return true;
					}
				}
			}
			return false;
		}

		private static void PatchOptionalLobbyMethods()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			HarmonyMethod prefix = new HarmonyMethod(typeof(LobbyLifecyclePatches), "BeforeLobbyLifecycleEvent", (Type[])null);
			string[] lobbyPatchTypes = LobbyPatchTypes;
			for (int i = 0; i < lobbyPatchTypes.Length; i++)
			{
				Type type = AccessTools.TypeByName(lobbyPatchTypes[i]);
				if (!(type == null))
				{
					_steamLobbyType = type;
					PatchMethodIfPresent(type, "CreateLobbyWithDelay", prefix);
					PatchMethodIfPresent(type, "JoinLobbyWithDelay", prefix);
					PatchMethodIfPresent(type, "JoinRichPresenceLobbyWithDelayyyyy", prefix);
					PatchMethodIfPresent(type, "LeaveSteamLobby", prefix);
				}
			}
		}

		private static void PatchMethodIfPresent(Type type, string methodName, HarmonyMethod prefix)
		{
			MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null);
			if (!(methodInfo == null))
			{
				_harmony.Patch((MethodBase)methodInfo, prefix, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)("Patched lobby lifecycle method " + type.FullName + "." + methodName + "."));
			}
		}

		private static void PatchFishySteamworksConnectionMethods()
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Expected O, but got Unknown
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0122: Expected O, but got Unknown
			Type type = AccessTools.TypeByName("FishySteamworks.Client.ClientSocket");
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "StartConnection", new Type[3]
			{
				typeof(string),
				typeof(ushort),
				typeof(bool)
			}, (Type[])null));
			if (methodInfo == null)
			{
				Log.LogWarning((object)"FishySteamworks.Client.ClientSocket.StartConnection was not found; per-connection ICE patch was not applied.");
				return;
			}
			HarmonyMethod val = new HarmonyMethod(typeof(FishySteamworksPatches), "ClientStartConnectionTranspiler", (Type[])null);
			_harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
			Type type2 = AccessTools.TypeByName("FishySteamworks.Server.ServerSocket");
			MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.Method(type2, "StartConnection", new Type[4]
			{
				typeof(string),
				typeof(ushort),
				typeof(int),
				typeof(bool)
			}, (Type[])null));
			if (methodInfo2 == null)
			{
				Log.LogWarning((object)"FishySteamworks.Server.ServerSocket.StartConnection was not found; listen-socket ICE patch was not applied.");
			}
			else
			{
				HarmonyMethod val2 = new HarmonyMethod(typeof(FishySteamworksPatches), "ServerStartConnectionTranspiler", (Type[])null);
				_harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null);
				Log.LogInfo((object)"Patched FishySteamworks server listen setup so LAN hosts can advertise ICE/direct routing.");
			}
			Log.LogInfo((object)"Patched FishySteamworks connection setup so LAN mode can enable per-connection ICE/direct routing.");
		}

		private static void DiscoverRuntimeTypes()
		{
			_steamLobbyType = _steamLobbyType ?? AccessTools.TypeByName("SteamLobby");
			Log.LogInfo((object)((_steamLobbyType == null) ? "SteamLobby type was not found yet; lobby UI will wait for runtime discovery." : ("Detected STRAFTAT lobby type: " + _steamLobbyType.FullName + ".")));
		}

		private static bool ShouldShowLobbySelector()
		{
			if (_steamLobbyType == null)
			{
				_steamLobbyType = AccessTools.TypeByName("SteamLobby");
			}
			if (_steamLobbyType != null)
			{
				try
				{
					if (Object.FindObjectOfType(_steamLobbyType) != (Object)null)
					{
						return true;
					}
				}
				catch (Exception ex)
				{
					Log.LogDebug((object)("Lobby visibility check failed: " + ex.Message));
				}
			}
			return true;
		}

		internal static void OnSteamApiInitialized()
		{
			_steamInitSeen = true;
			Log.LogInfo((object)"SteamAPI.Init completed; applying routing mode and registering diagnostics.");
			ApplyRoutingMode("SteamAPI.Init");
			RegisterSteamCallbacks("SteamAPI.Init");
		}

		internal static void OnLobbyLifecycleEvent(string methodName)
		{
			Log.LogInfo((object)$"Lobby lifecycle event starting: {methodName}; locking routing mode to {Mode.Value} for this lobby action.");
			ApplyRoutingMode(methodName);
		}

		private static void SetLobbyMode(RoutingMode mode, string reason)
		{
			Mode.Value = mode;
			_configFile.Save();
			Log.LogInfo((object)$"Lobby network mode set to {Mode.Value} ({reason}). Create a new lobby or have players rejoin to use this route choice.");
			ApplyRoutingMode(reason);
			Plugin[] array = Object.FindObjectsOfType<Plugin>();
			for (int i = 0; i < array.Length; i++)
			{
				array[i].RefreshLobbyNetworkUi();
			}
		}

		private static bool IsLanMode()
		{
			if (Mode.Value != RoutingMode.PreferDirect)
			{
				return Mode.Value == RoutingMode.ExperimentalNoRelay;
			}
			return true;
		}

		private static int GetPerConnectionIceEnableValue()
		{
			if (!Enabled.Value || !IsLanMode())
			{
				return 0;
			}
			int num = 6;
			Log.LogInfo((object)$"Applying per-connection P2P_Transport_ICE_Enable={num} for {Mode.Value} mode.");
			return num;
		}

		private static SteamNetworkingConfigValue_t[] GetListenSocketConfigValues()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_006a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			if (!Enabled.Value || !IsLanMode())
			{
				return (SteamNetworkingConfigValue_t[])(object)new SteamNetworkingConfigValue_t[0];
			}
			int num = 6;
			Log.LogInfo((object)$"Applying listen-socket P2P_Transport_ICE_Enable={num} for {Mode.Value} mode.");
			return (SteamNetworkingConfigValue_t[])(object)new SteamNetworkingConfigValue_t[1]
			{
				new SteamNetworkingConfigValue_t
				{
					m_eValue = (ESteamNetworkingConfigValue)104,
					m_eDataType = (ESteamNetworkingConfigDataType)1,
					m_val = new OptionValue
					{
						m_int32 = num
					}
				}
			};
		}

		private static void RegisterSteamCallbacks(string reason)
		{
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			if (!Enabled.Value || !_steamInitSeen || _steamCallbacksRegistered)
			{
				return;
			}
			try
			{
				_connectionStatusCallback = Callback<SteamNetConnectionStatusChangedCallback_t>.Create((DispatchDelegate<SteamNetConnectionStatusChangedCallback_t>)OnConnectionStatusChanged);
				if (VerboseSteamNetworkingLogs.Value)
				{
					object obj = <>O.<3>__OnSteamNetworkingDebugOutput;
					if (obj == null)
					{
						FSteamNetworkingSocketsDebugOutput val = OnSteamNetworkingDebugOutput;
						<>O.<3>__OnSteamNetworkingDebugOutput = val;
						obj = (object)val;
					}
					_debugOutputCallback = (FSteamNetworkingSocketsDebugOutput)obj;
					SteamNetworkingUtils.SetDebugOutputFunction((ESteamNetworkingSocketsDebugOutputType)5, _debugOutputCallback);
				}
				SteamNetworkingUtils.InitRelayNetworkAccess();
				_steamCallbacksRegistered = true;
				Log.LogInfo((object)("Steam Networking diagnostics registered (" + reason + ")."));
				LogRelayStatus();
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Failed to register Steam Networking diagnostics ({reason}): {arg}");
			}
		}

		private static void ApplyRoutingMode(string reason)
		{
			if (!Enabled.Value)
			{
				return;
			}
			if (!_steamInitSeen)
			{
				Log.LogDebug((object)$"Steamworks is not initialized yet; deferring routing mode {Mode.Value} ({reason}).");
			}
			else
			{
				if (_lastAppliedMode == Mode.Value && _lastUnencryptedSetting == AllowUnencryptedSegments.Value)
				{
					return;
				}
				try
				{
					switch (Mode.Value)
					{
					case RoutingMode.ObserveOnly:
						Log.LogInfo((object)("Internet/ObserveOnly routing selected (" + reason + "). Steam routing is left neutral."));
						break;
					case RoutingMode.PreferDirect:
						SetIntConfig((ESteamNetworkingConfigValue)104, 6);
						SetIntConfig((ESteamNetworkingConfigValue)105, 0);
						SetIntConfig((ESteamNetworkingConfigValue)106, 1000);
						TrySetUnencryptedSegments();
						Log.LogInfo((object)("LAN routing selected (" + reason + "). ICE private/public direct P2P is preferred and SDR relay remains available as fallback."));
						break;
					case RoutingMode.ExperimentalNoRelay:
						SetIntConfig((ESteamNetworkingConfigValue)104, 6);
						SetIntConfig((ESteamNetworkingConfigValue)105, 0);
						SetIntConfig((ESteamNetworkingConfigValue)106, 1000000);
						TrySetUnencryptedSegments();
						Log.LogWarning((object)("ExperimentalNoRelay selected (" + reason + "). ICE private/public direct P2P is preferred, SDR relay is heavily penalized, and connections may fail off-LAN."));
						break;
					}
					_lastAppliedMode = Mode.Value;
					_lastUnencryptedSetting = AllowUnencryptedSegments.Value;
					LogRelayStatus();
				}
				catch (Exception arg)
				{
					_lastAppliedMode = null;
					Log.LogWarning((object)$"Failed to apply routing mode {Mode.Value} ({reason}): {arg}");
				}
			}
		}

		private static void TrySetUnencryptedSegments()
		{
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if (AllowUnencryptedSegments.Value)
			{
				if (!Enum.TryParse<ESteamNetworkingConfigValue>("k_ESteamNetworkingConfig_P2P_AllowUnencryptedSegments", out ESteamNetworkingConfigValue result))
				{
					Log.LogWarning((object)"AllowUnencryptedSegments is enabled, but this Steamworks.NET build does not expose P2P_AllowUnencryptedSegments.");
					return;
				}
				SetIntConfig(result, 1);
				Log.LogWarning((object)"AllowUnencryptedSegments was applied. Use only for local testing.");
			}
		}

		private static void SetIntConfig(ESteamNetworkingConfigValue value, int configValue)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			IntPtr intPtr = Marshal.AllocHGlobal(4);
			try
			{
				Marshal.WriteInt32(intPtr, configValue);
				if (!SteamNetworkingUtils.SetConfigValue(value, (ESteamNetworkingConfigScope)1, IntPtr.Zero, (ESteamNetworkingConfigDataType)1, intPtr))
				{
					Log.LogWarning((object)$"SteamNetworkingUtils.SetConfigValue failed for {value}={configValue}.");
				}
				else
				{
					Log.LogDebug((object)$"Applied {value}={configValue}.");
				}
			}
			finally
			{
				Marshal.FreeHGlobal(intPtr);
			}
		}

		private static void LogRelayStatus()
		{
			//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_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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)
			if (!_steamInitSeen)
			{
				return;
			}
			try
			{
				SteamRelayNetworkStatus_t val = default(SteamRelayNetworkStatus_t);
				ESteamNetworkingAvailability relayNetworkStatus = SteamNetworkingUtils.GetRelayNetworkStatus(ref val);
				Log.LogInfo((object)$"Steam relay status: availability={relayNetworkStatus}, relay={val.m_eAvailNetworkConfig}, anyRelay={val.m_eAvailAnyRelay}, msg={((SteamRelayNetworkStatus_t)(ref val)).m_debugMsg}.");
			}
			catch (Exception ex)
			{
				Log.LogDebug((object)("Relay status unavailable: " + ex.Message));
			}
		}

		private static void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t callback)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: 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)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: 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)
			SteamNetConnectionInfo_t info = callback.m_info;
			Log.LogInfo((object)$"Steam connection changed: conn={callback.m_hConn.m_HSteamNetConnection}, old={callback.m_eOldState}, new={info.m_eState}, flags={info.m_nFlags}, relayPOP={info.m_idPOPRelay}, remotePOP={info.m_idPOPRemote}, end={info.m_eEndReason}.");
		}

		private static void OnSteamNetworkingDebugOutput(ESteamNetworkingSocketsDebugOutputType type, StringBuilder message)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			if (VerboseSteamNetworkingLogs.Value && message != null)
			{
				Log.LogInfo((object)$"SteamNetworking[{type}]: {message}");
			}
		}
	}
	internal enum RoutingMode
	{
		ObserveOnly,
		PreferDirect,
		ExperimentalNoRelay
	}
}