Decompiled source of SatmLanIp v1.0.2

SatmLanIp.dll

Decompiled 5 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Fusion;
using Fusion.Async;
using Fusion.Sockets;
using HarmonyLib;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.Injection;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Runtime.CompilerServices;
using Il2CppSystem.Threading;
using Il2CppSystem.Threading.Tasks;
using Microsoft.CodeAnalysis;
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: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("古月木兆")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("LAN / VPN IP co-op for Shift at Midnight (raw UDP room + Fusion Direct)")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+8067f72597f41e5f0e12ec465a19eb5b308a1041")]
[assembly: AssemblyProduct("SatmLanIp")]
[assembly: AssemblyTitle("SatmLanIp")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.2.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
	internal sealed class NullableAttribute : Attribute
	{
		public NullableAttribute(byte _)
		{
		}

		public NullableAttribute(byte[] _)
		{
		}
	}
	internal sealed class NullableContextAttribute : Attribute
	{
		public NullableContextAttribute(byte _)
		{
		}
	}
}
namespace SatmLanIp
{
	internal static class LanConfig
	{
		public const bool DefaultEnabled = true;

		public const int DefaultPort = 37241;

		public const int MaxPort = 65534;

		public const int DefaultTimeoutSec = 30;

		public const string DefaultJoinAddress = "";

		public static bool IsValidPort(int port)
		{
			if (port >= 1)
			{
				return port <= 65534;
			}
			return false;
		}

		public static int NormalizePort(int port)
		{
			if (!IsValidPort(port))
			{
				return 37241;
			}
			return port;
		}

		internal static void SelfCheck()
		{
			if (!IsValidPort(1) || !IsValidPort(65534) || IsValidPort(0) || IsValidPort(65535) || NormalizePort(37241) != 37241 || NormalizePort(0) != 37241 || NormalizePort(65535) != 37241)
			{
				throw new InvalidOperationException("SatmLanIp LanConfig ports");
			}
		}
	}
	internal static class LanFusionStart
	{
		public static bool ShouldSkipPhoton(bool allowFusionStart, bool pluginActive)
		{
			return allowFusionStart && pluginActive;
		}

		public static bool ShouldBindHostAddress(bool isHost)
		{
			return isHost;
		}

		public static ushort HostBindPort(int listenPort)
		{
			int num = listenPort;
			if (!LanConfig.IsValidPort(num))
			{
				num = 37241;
			}
			int num2 = num + 1;
			if (num2 > 65535)
			{
				num2 = 37242;
			}
			return (ushort)num2;
		}

		public static string ResolveClientConnectIp(string joinIp)
		{
			if (string.IsNullOrEmpty(joinIp))
			{
				return "";
			}
			if (LanLocalIp.IsOwnLanIp(joinIp))
			{
				return "127.0.0.1";
			}
			return joinIp;
		}

		public static int SessionPortAfterJoinParse(int parsedPort)
		{
			if (!LanConfig.IsValidPort(parsedPort))
			{
				return 37241;
			}
			return parsedPort;
		}

		public static bool TryClientTarget(string joinAddress, int joinPort, out string ip, out ushort port)
		{
			ip = "";
			port = 0;
			if (!LanHostParse.TryParseHostPort(joinAddress, joinPort, out var host, out var port2, out var _))
			{
				return false;
			}
			if (host.Length == 0 || !LanConfig.IsValidPort(port2))
			{
				return false;
			}
			ip = ResolveClientConnectIp(host);
			port = HostBindPort(port2);
			return true;
		}

		public static int ResolveLanActorId(bool isHost, int localSlot)
		{
			if (isHost)
			{
				return 1;
			}
			int num = localSlot + 1;
			if (num < 2)
			{
				return 2;
			}
			if (num > 6)
			{
				return 6;
			}
			return num;
		}

		public static int HostPremapPeerActorHi(int maxPlayers)
		{
			int num = LanRoom.ClampMax(maxPlayers);
			if (num < 2)
			{
				num = 2;
			}
			return num;
		}

		internal static void SelfCheck()
		{
			if (ShouldSkipPhoton(allowFusionStart: false, pluginActive: true) || ShouldSkipPhoton(allowFusionStart: true, pluginActive: false) || !ShouldSkipPhoton(allowFusionStart: true, pluginActive: true))
			{
				throw new InvalidOperationException("SatmLanIp skip-photon self-check failed");
			}
			if (!ShouldBindHostAddress(isHost: true) || ShouldBindHostAddress(isHost: false))
			{
				throw new InvalidOperationException("SatmLanIp host-bind self-check failed");
			}
			if (HostBindPort(37241) != 37242 || HostBindPort(0) != 37242)
			{
				throw new InvalidOperationException("SatmLanIp bind-port self-check failed");
			}
			if (HostBindPort(65535) != 37242)
			{
				throw new InvalidOperationException("SatmLanIp bind-port overflow self-check failed");
			}
			if (SessionPortAfterJoinParse(27015) != 27015 || SessionPortAfterJoinParse(0) != 37241 || SessionPortAfterJoinParse(65535) != 37241)
			{
				throw new InvalidOperationException("SatmLanIp session-port-after-join self-check failed");
			}
			if (ResolveClientConnectIp("127.0.0.1") != "127.0.0.1")
			{
				throw new InvalidOperationException("SatmLanIp loopback resolve self-check failed");
			}
			if (!TryClientTarget("10.0.0.2:27015", 37241, out var ip, out var port) || ip != "10.0.0.2" || port != 27016)
			{
				throw new InvalidOperationException("SatmLanIp client-target port self-check failed");
			}
			if (TryClientTarget("", 37241, out var _, out var _))
			{
				throw new InvalidOperationException("SatmLanIp client-target empty self-check failed");
			}
			if (ResolveLanActorId(isHost: true, 0) != 1 || ResolveLanActorId(isHost: false, 1) != 2 || ResolveLanActorId(isHost: false, 2) != 3 || ResolveLanActorId(isHost: false, 5) != 6 || ResolveLanActorId(isHost: false, 0) != 2)
			{
				throw new InvalidOperationException("SatmLanIp ResolveLanActorId self-check failed");
			}
			if (HostPremapPeerActorHi(6) != 6 || HostPremapPeerActorHi(3) != 3 || HostPremapPeerActorHi(1) != 2)
			{
				throw new InvalidOperationException("SatmLanIp HostPremapPeerActorHi clamp");
			}
			if (HostPremapPeerActorHi(2) != 2)
			{
				throw new InvalidOperationException("SatmLanIp premap 2p");
			}
			if (HostPremapPeerActorHi(3) != 3)
			{
				throw new InvalidOperationException("SatmLanIp premap 3p");
			}
			if (HostPremapPeerActorHi(6) != 6)
			{
				throw new InvalidOperationException("SatmLanIp premap 6p");
			}
		}
	}
	internal static class LanHostParse
	{
		internal static bool TryParseHostPort(string raw, int defaultPort, out string host, out int port, out string error)
		{
			host = "";
			port = defaultPort;
			error = "";
			string text = (raw ?? "").Trim();
			if (text.Length == 0)
			{
				error = "JoinAddress empty";
				return false;
			}
			string text2 = defaultPort.ToString();
			while (text.EndsWith(":" + text2 + ":" + text2))
			{
				text = text.Substring(0, text.Length - (text2.Length + 1));
			}
			int num = text.LastIndexOf(':');
			if (num >= 0 && text.IndexOf(':') == num)
			{
				string s = text.Substring(num + 1).Trim();
				string text3 = text.Substring(0, num).Trim();
				if (text3.Length == 0)
				{
					error = "JoinAddress empty host";
					return false;
				}
				if (!int.TryParse(s, out var result) || !LanConfig.IsValidPort(result))
				{
					error = "invalid JoinPort";
					return false;
				}
				host = text3;
				port = result;
				return true;
			}
			host = text;
			port = defaultPort;
			return true;
		}

		internal static void SelfCheck()
		{
			if (!TryParseHostPort("192.168.1.10", 37241, out var host, out var port, out var error) || host != "192.168.1.10" || port != 37241)
			{
				throw new InvalidOperationException("SatmLanIp parse ip-only failed");
			}
			if (!TryParseHostPort("192.168.1.10:37241", 37241, out var host2, out var port2, out error) || host2 != "192.168.1.10" || port2 != 37241)
			{
				throw new InvalidOperationException("SatmLanIp parse ip:port failed");
			}
			if (!TryParseHostPort("192.168.1.10:37241:37241", 37241, out var host3, out var port3, out error) || host3 != "192.168.1.10" || port3 != 37241)
			{
				throw new InvalidOperationException("SatmLanIp parse double-port failed");
			}
			if (TryParseHostPort("", 37241, out error, out var port4, out var error2) || error2 != "JoinAddress empty")
			{
				throw new InvalidOperationException("SatmLanIp parse empty failed");
			}
			if (TryParseHostPort(":37241", 37241, out error, out port4, out var error3) || error3 != "JoinAddress empty host")
			{
				throw new InvalidOperationException("SatmLanIp parse empty host failed");
			}
			if (TryParseHostPort("10.0.0.1:65535", 37241, out error, out port4, out var error4) || error4 != "invalid JoinPort")
			{
				throw new InvalidOperationException("SatmLanIp parse reserved Fusion port failed");
			}
		}
	}
	internal static class LanLocalIp
	{
		internal struct AdvertiseAddr
		{
			public string Kind;

			public string Ip;
		}

		internal const string KindLan = "局域网";

		internal const string KindOverlay = "组网";

		internal const string KindOther = "其他";

		public static List<string> ListIPv4()
		{
			List<AdvertiseAddr> list = ListAdvertise();
			List<string> list2 = new List<string>(list.Count);
			for (int i = 0; i < list.Count; i++)
			{
				list2.Add(list[i].Ip);
			}
			return list2;
		}

		internal static List<AdvertiseAddr> ListAdvertise()
		{
			List<AdvertiseAddr> list = new List<AdvertiseAddr>();
			List<AdvertiseAddr> list2 = new List<AdvertiseAddr>();
			List<AdvertiseAddr> list3 = new List<AdvertiseAddr>();
			try
			{
				NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
				foreach (NetworkInterface networkInterface in allNetworkInterfaces)
				{
					if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback)
					{
						continue;
					}
					string name = networkInterface.Name ?? "";
					string desc = networkInterface.Description ?? "";
					if (IsJunkAdvertiseAdapter(name, desc))
					{
						continue;
					}
					bool flag = IsOverlayAdvertiseAdapter(name, desc);
					bool flag2 = IsPhysicalAdvertiseAdapter(networkInterface.NetworkInterfaceType) && !flag;
					string kind = (flag ? "组网" : (flag2 ? "局域网" : "其他"));
					foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses)
					{
						if (unicastAddress.Address.AddressFamily != AddressFamily.InterNetwork)
						{
							continue;
						}
						string ip = unicastAddress.Address.ToString();
						if (!ShouldSkipAdvertiseIp(ip))
						{
							AdvertiseAddr item = new AdvertiseAddr
							{
								Kind = kind,
								Ip = ip
							};
							if (flag)
							{
								list2.Add(item);
							}
							else if (flag2)
							{
								list.Add(item);
							}
							else
							{
								list3.Add(item);
							}
						}
					}
				}
			}
			catch (Exception)
			{
			}
			return DedupeRows(list, list2, list3);
		}

		public static string FormatAdvertise(int port)
		{
			return FormatAdvertiseRows(ListAdvertise(), port);
		}

		internal static string FormatAdvertiseRows(List<AdvertiseAddr> rows, int port)
		{
			if (rows == null || rows.Count == 0)
			{
				return "(no LAN IPv4 found) port=" + port;
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < rows.Count; i++)
			{
				if (i > 0)
				{
					stringBuilder.Append('\n');
				}
				stringBuilder.Append(PadKind(rows[i].Kind));
				stringBuilder.Append(rows[i].Ip);
				stringBuilder.Append(':');
				stringBuilder.Append(port.ToString());
			}
			return stringBuilder.ToString();
		}

		internal static bool ShouldSkipAdvertiseIp(string ip)
		{
			if (string.IsNullOrEmpty(ip))
			{
				return true;
			}
			if (ip.StartsWith("127.") || ip.StartsWith("169.254."))
			{
				return true;
			}
			if (ip.StartsWith("198.18.") || ip.StartsWith("198.19."))
			{
				return true;
			}
			return false;
		}

		internal static bool IsJunkAdvertiseAdapter(string name, string desc)
		{
			string text = name + " " + desc;
			if (text.Length == 0)
			{
				return false;
			}
			return ContainsAny(text, "vEthernet", "Hyper-V", "WSL");
		}

		internal static bool IsOverlayAdvertiseAdapter(string name, string desc)
		{
			string text = name + " " + desc;
			if (text.Length == 0)
			{
				return false;
			}
			return ContainsAny(text, "Wintun", "WireGuard", "ZeroTier", "Tailscale", "Hamachi", "SteamVPN", "TAP-Windows", "TAP-Win32", "OpenVPN", "Radmin VPN", "SoftEther");
		}

		internal static bool IsPhysicalAdvertiseAdapter(NetworkInterfaceType t)
		{
			if (t != NetworkInterfaceType.Ethernet && t != NetworkInterfaceType.Wireless80211 && t != NetworkInterfaceType.GigabitEthernet && t != NetworkInterfaceType.FastEthernetT)
			{
				return t == NetworkInterfaceType.FastEthernetFx;
			}
			return true;
		}

		public static bool IsOwnLanIp(string ip)
		{
			if (string.IsNullOrEmpty(ip))
			{
				return false;
			}
			if (ip == "127.0.0.1" || ip.StartsWith("127."))
			{
				return true;
			}
			List<string> list = ListIPv4();
			for (int i = 0; i < list.Count; i++)
			{
				if (string.Equals(list[i], ip, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		internal static void SelfCheck()
		{
			if (!ShouldSkipAdvertiseIp("198.18.0.1") || !ShouldSkipAdvertiseIp("198.19.1.2") || ShouldSkipAdvertiseIp("192.168.1.10"))
			{
				throw new InvalidOperationException("SatmLanIp Fake-IP filter self-check failed");
			}
			if (!IsJunkAdvertiseAdapter("vEthernet (WSL (Hyper-V firewall))", "Hyper-V Virtual Ethernet Adapter"))
			{
				throw new InvalidOperationException("SatmLanIp junk adapter WSL self-check failed");
			}
			if (!IsJunkAdvertiseAdapter("vEthernet (Default Switch)", "Hyper-V Virtual Ethernet Adapter"))
			{
				throw new InvalidOperationException("SatmLanIp junk adapter Default Switch self-check failed");
			}
			if (IsJunkAdvertiseAdapter("WLAN", "Intel(R) Wi-Fi 6") || IsJunkAdvertiseAdapter("本地连接 2", "TAP-Windows Adapter V9") || IsJunkAdvertiseAdapter("Wintun", "Wintun Userspace Tunnel") || IsJunkAdvertiseAdapter("ZeroTier One", "ZeroTier Virtual Port") || IsJunkAdvertiseAdapter("以太网", "Intel(R) Ethernet Connection"))
			{
				throw new InvalidOperationException("SatmLanIp usable adapter marked junk");
			}
			if (!IsOverlayAdvertiseAdapter("本地连接 2", "TAP-Windows Adapter V9") || !IsOverlayAdvertiseAdapter("Wintun", "Wintun Userspace Tunnel"))
			{
				throw new InvalidOperationException("SatmLanIp overlay adapter self-check failed");
			}
			string text = FormatAdvertiseRows(new List<AdvertiseAddr>
			{
				new AdvertiseAddr
				{
					Kind = "局域网",
					Ip = "192.168.1.10"
				},
				new AdvertiseAddr
				{
					Kind = "组网",
					Ip = "10.10.0.2"
				},
				new AdvertiseAddr
				{
					Kind = "其他",
					Ip = "100.64.1.2"
				}
			}, 37241);
			string text2 = PadKind("局域网") + "192.168.1.10:37241\n" + PadKind("组网") + "10.10.0.2:37241\n" + PadKind("其他") + "100.64.1.2:37241";
			if (text != text2)
			{
				throw new InvalidOperationException("SatmLanIp FormatAdvertiseRows: " + text);
			}
		}

		internal static string PadKind(string kind)
		{
			if (kind == null)
			{
				kind = "其他";
			}
			if (kind.Length >= 3)
			{
				return kind + "  ";
			}
			return kind + "\u3000\u3000";
		}

		private static bool ContainsAny(string hay, params string[] needles)
		{
			for (int i = 0; i < needles.Length; i++)
			{
				if (hay.IndexOf(needles[i], StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return true;
				}
			}
			return false;
		}

		private static List<AdvertiseAddr> DedupeRows(params List<AdvertiseAddr>[] buckets)
		{
			HashSet<string> hashSet = new HashSet<string>();
			List<AdvertiseAddr> list = new List<AdvertiseAddr>();
			foreach (List<AdvertiseAddr> list2 in buckets)
			{
				for (int j = 0; j < list2.Count; j++)
				{
					if (hashSet.Add(list2[j].Ip))
					{
						list.Add(list2[j]);
					}
				}
			}
			return list;
		}
	}
	internal static class LanMatch
	{
		private enum Phase
		{
			Idle,
			Playing,
			Waiting,
			Done,
			Failed
		}

		public static bool AllowFusionStart;

		public static string SessionName = "satm37241";

		private static Phase _phase;

		private static float _since;

		private static bool _playFired;

		private static bool _clientFired;

		private static bool _movedToGame;

		private static string _lastScene = "";

		private static float _nextStuckLog;

		private static bool _stockLeaveRequested;

		public static void Reset()
		{
			_phase = Phase.Idle;
			_since = 0f;
			_playFired = false;
			_clientFired = false;
			_movedToGame = false;
			AllowFusionStart = false;
			_lastScene = "";
			_nextStuckLog = 0f;
			_stockLeaveRequested = false;
			FusionCloudBypassPatches.Reset();
			((Plugin.Transport != null) ? Plugin.Transport.Session : null)?.ClearMatchActive();
		}

		internal static void RequestStockLeave()
		{
			if (_stockLeaveRequested)
			{
				return;
			}
			_stockLeaveRequested = true;
			try
			{
				Plugin.LogSrc.LogInfo((object)"[SatmLanIp] stock leave via LAN goodbye");
				PlatformManager_Steam val = Object.FindObjectOfType<PlatformManager_Steam>();
				if ((Object)(object)val != (Object)null)
				{
					((PlatformManager)val).HandleEndSessionReturn((NetworkErrors)2);
				}
				else
				{
					FusionNetworkManager instance = FusionNetworkManager.Instance;
					if ((Object)(object)instance != (Object)null)
					{
						instance.LeaveGame();
					}
				}
				FusionLanPatches.LoadMenuNow("client-goodbye");
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] stock leave fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		public static void TryBegin(string why)
		{
			LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null);
			if (lanSession == null)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] TryBegin skip via=" + why + " (no session)"));
				return;
			}
			if (lanSession.State != LanState.Connected)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] TryBegin skip via=" + why + " state=" + lanSession.State));
				return;
			}
			SessionName = "satm" + (lanSession.IsHost ? Plugin.ListenPort : Plugin.JoinPort);
			if (!lanSession.MatchActive)
			{
				lanSession.MatchActive = true;
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_start via=" + why + " session=" + SessionName));
			}
			string text = ActiveSceneName();
			if (!IsMenuSceneName(text) && text != "Lobby")
			{
				_phase = Phase.Done;
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_enter already scene=" + text));
				return;
			}
			if (_phase == Phase.Waiting)
			{
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_enter 4 while Waiting via=" + why));
				TryMoveToGameScene();
				return;
			}
			_phase = Phase.Playing;
			_playFired = false;
			_clientFired = false;
			_movedToGame = false;
			_since = Time.unscaledTime;
			_lastScene = text;
		}

		public static void Tick()
		{
			FusionCloudBypassPatches.Pump();
			LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null);
			bool flag = lanSession != null && lanSession.MatchActive && lanSession.State == LanState.Connected;
			string text = ActiveSceneName();
			if (ShouldTearDownMatchOnMenuReturn(_phase) && (!flag || IsMenuSceneName(text)))
			{
				EndMatchToMenu(lanSession);
				return;
			}
			if (!flag)
			{
				if (_phase != Phase.Idle)
				{
					Reset();
				}
				return;
			}
			if (text != _lastScene)
			{
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_scene from=" + _lastScene + " to=" + text));
				_lastScene = text;
			}
			if (!IsMenuSceneName(text))
			{
				if (_phase != Phase.Done)
				{
					_phase = Phase.Done;
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_enter ok scene=" + text));
				}
			}
			else if (ShouldTearDownMatchOnMenuReturn(_phase))
			{
				EndMatchToMenu(lanSession);
			}
			else
			{
				if (_phase == Phase.Idle || _phase == Phase.Failed)
				{
					return;
				}
				float unscaledTime = Time.unscaledTime;
				if (!_playFired)
				{
					DoPlay();
					_playFired = true;
					_since = unscaledTime;
					_phase = Phase.Waiting;
				}
				else
				{
					if (_phase != Phase.Waiting)
					{
						return;
					}
					LanSession session = Plugin.Transport.Session;
					if (session != null && !session.IsHost && !_clientFired)
					{
						float num = 1.5f + (float)Math.Max(0, session.LocalSlot - 1) * 0.5f;
						if (unscaledTime - _since >= num)
						{
							TryStartClient();
							FusionCloudBypassPatches.Pump();
						}
					}
					if (!_movedToGame && unscaledTime - _since >= 6f)
					{
						_movedToGame = true;
						TryMoveToGameScene();
					}
					if (unscaledTime - _since >= 10f && unscaledTime >= _nextStuckLog)
					{
						_nextStuckLog = unscaledTime + 10f;
						Plugin.LogSrc.LogWarning((object)("[SatmLanIp] match_enter stuck scene=" + text));
					}
				}
			}
		}

		private static bool ShouldTearDownMatchOnMenuReturn(Phase phase)
		{
			return phase == Phase.Done;
		}

		private static void EndMatchToMenu(LanSession s)
		{
			AllowFusionStart = false;
			_phase = Phase.Failed;
			if (s != null)
			{
				s.MatchActive = false;
			}
			Plugin.LogSrc.LogInfo((object)"[SatmLanIp] match_end → main menu (no lobby overlay)");
			LanMenuPanel.Back();
			Reset();
		}

		internal static bool IsMenuSceneName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return true;
			}
			string text = name.Trim();
			if (!text.Equals("MainMenu", StringComparison.OrdinalIgnoreCase) && !text.Equals("Splash", StringComparison.OrdinalIgnoreCase) && !text.Equals("SplashScreen", StringComparison.OrdinalIgnoreCase))
			{
				return text.Equals("Boot", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		internal static void SelfCheck()
		{
			if (!IsMenuSceneName("MainMenu") || !IsMenuSceneName("") || IsMenuSceneName("Game") || IsMenuSceneName("Lobby"))
			{
				throw new InvalidOperationException("SatmLanIp LanMatch scene-name self-check failed");
			}
			if (!ShouldTearDownMatchOnMenuReturn(Phase.Done) || ShouldTearDownMatchOnMenuReturn(Phase.Waiting) || ShouldTearDownMatchOnMenuReturn(Phase.Idle))
			{
				throw new InvalidOperationException("SatmLanIp tear down lobby overlay only after in-game");
			}
		}

		private static string ActiveSceneName()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Scene activeScene = SceneManager.GetActiveScene();
				return ((Scene)(ref activeScene)).name ?? "";
			}
			catch
			{
				return "";
			}
		}

		private static void DoPlay()
		{
			try
			{
				MainMenu val = Find<MainMenu>();
				if ((Object)(object)val == (Object)null)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] match_enter skip (no MainMenu)");
					return;
				}
				val.SetAsSoloMode(false);
				if ((Object)(object)val.selectSaveFileMenu != (Object)null)
				{
					val.selectSaveFileMenu.SetActive(true);
				}
				int num = EnsureSaveSlot();
				if (val.started)
				{
					val.started = false;
				}
				if ((Object)(object)val.selectSaveFileMenu != (Object)null)
				{
					val.selectSaveFileMenu.SetActive(false);
				}
				FusionNetworkManager instance = FusionNetworkManager.Instance;
				if ((Object)(object)instance != (Object)null)
				{
					NetworkRunner val2 = null;
					try
					{
						val2 = instance.GetRunner();
					}
					catch
					{
					}
					if ((Object)(object)val2 != (Object)null && (_phase == Phase.Waiting || AllowFusionStart))
					{
						bool flag = false;
						try
						{
							flag = val2.IsRunning;
						}
						catch
						{
						}
						Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_enter reuse runner IsRunning=" + flag));
						if (flag)
						{
							TryMoveToGameScene();
						}
						else
						{
							Plugin.LogSrc.LogInfo((object)"[SatmLanIp] match_enter wait runner (no Abort) — do not spam 4");
						}
						return;
					}
					if ((Object)(object)val2 != (Object)null)
					{
						Plugin.LogSrc.LogInfo((object)"[SatmLanIp] match_enter abort leftover runner");
						instance.AbortStartGame();
					}
				}
				AllowFusionStart = true;
				LanSession session = Plugin.Transport.Session;
				if (session != null && session.IsHost)
				{
					if ((Object)(object)instance == (Object)null)
					{
						Plugin.LogSrc.LogWarning((object)"[SatmLanIp] fusion_host skip (no FusionNetworkManager)");
						return;
					}
					ushort num2 = LanFusionStart.HostBindPort(Plugin.ListenPort);
					instance.StartAsHost(SessionName);
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_host skip_cloud session=" + SessionName + " lan=" + Plugin.ListenPort + " fusion=" + num2 + " slot=" + num));
				}
				else
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_client wait session=" + SessionName + " slot=" + num));
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] match_enter fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static int EnsureSaveSlot()
		{
			int num = 0;
			try
			{
				num = SaveManager.CurrentSaveSlot;
			}
			catch
			{
				num = 0;
			}
			SaveFileManager val = Find<SaveFileManager>();
			int num2 = (((Object)(object)val != (Object)null) ? FirstExistingSlot(val) : (-1));
			int num3 = LanSession.ResolveSaveSlot(LanMenuFlow.HostSaveSlot, num, num2);
			if ((Object)(object)val == (Object)null)
			{
				try
				{
					SaveManager.CurrentSaveSlot = num3;
				}
				catch
				{
				}
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] save_select skip (no SaveFileManager) slot=" + num3));
				return num3;
			}
			if (LanMenuFlow.HostSaveSlot >= 0)
			{
				try
				{
					SaveManager.CurrentSaveSlot = num3;
				}
				catch
				{
				}
				try
				{
					val.SelectSaveFile(num3);
				}
				catch (Exception ex)
				{
					Plugin.LogSrc.LogWarning((object)("[SatmLanIp] save_reselect fail " + ex.GetType().Name + ": " + ex.Message));
				}
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] save_select host_picked=" + num3));
				return num3;
			}
			if (num2 >= 0)
			{
				val.SelectSaveFile(num3);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] save_select existing=" + num3));
				return num3;
			}
			val.CreateNewSave(num3);
			val.ConfirmCreateNewSave(false);
			try
			{
				SaveManager.CurrentSaveSlot = num3;
			}
			catch
			{
			}
			Plugin.LogSrc.LogInfo((object)("[SatmLanIp] save_create slot=" + num3 + " story"));
			return num3;
		}

		private static void TryStartClient()
		{
			_clientFired = true;
			try
			{
				AllowFusionStart = true;
				FusionNetworkManager instance = FusionNetworkManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] fusion_client skip (no FusionNetworkManager)");
					return;
				}
				instance.StartAsClient(SessionName, "");
				LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null);
				int num = lanSession?.LocalSlot ?? 0;
				int num2 = lanSession?.MaxPlayers ?? 0;
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_client session=" + SessionName + " slot=" + num + "/" + num2));
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] fusion_client fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static int FirstExistingSlot(SaveFileManager sfm)
		{
			try
			{
				Il2CppReferenceArray<GameObject> saveFileExistsHolders = sfm.saveFileExistsHolders;
				if (saveFileExistsHolders == null)
				{
					return -1;
				}
				for (int i = 0; i < ((Il2CppArrayBase<GameObject>)(object)saveFileExistsHolders).Length; i++)
				{
					GameObject val = ((Il2CppArrayBase<GameObject>)(object)saveFileExistsHolders)[i];
					if ((Object)(object)val != (Object)null && val.activeSelf)
					{
						return i;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] save_scan fail " + ex.GetType().Name + ": " + ex.Message));
			}
			return -1;
		}

		private static void TryMoveToGameScene()
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				FusionNetworkManager instance = FusionNetworkManager.Instance;
				if ((Object)(object)instance == (Object)null)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] match_enter move=skip (no FusionNetworkManager)");
					return;
				}
				NetworkRunner val = null;
				try
				{
					val = instance.GetRunner();
				}
				catch
				{
				}
				if ((Object)(object)val == (Object)null)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] match_enter move=skip (no runner)");
					return;
				}
				bool flag = false;
				try
				{
					flag = val.IsRunning;
				}
				catch
				{
				}
				if (!flag)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] match_enter move=skip (runner not running — Join/Init still incomplete)");
					return;
				}
				SceneRef gameScene = instance._gameScene;
				instance.MoveToScene(gameScene);
				Plugin.LogSrc.LogInfo((object)"[SatmLanIp] match_enter via=MoveToScene _gameScene");
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] match_enter move=fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static T Find<T>() where T : Object
		{
			try
			{
				return Object.FindFirstObjectByType<T>();
			}
			catch
			{
				return Object.FindObjectOfType<T>();
			}
		}
	}
	internal static class LanPose
	{
		public const int PayloadSize = 16;

		public static void Write(byte[] buf, int offset, float x, float y, float z, float yaw)
		{
			WriteF32(buf, offset, x);
			WriteF32(buf, offset + 4, y);
			WriteF32(buf, offset + 8, z);
			WriteF32(buf, offset + 12, yaw);
		}

		public static bool TryRead(byte[] buf, int len, int offset, out float x, out float y, out float z, out float yaw)
		{
			x = (y = (z = (yaw = 0f)));
			if (buf == null || len < offset + 16)
			{
				return false;
			}
			x = ReadF32(buf, offset);
			y = ReadF32(buf, offset + 4);
			z = ReadF32(buf, offset + 8);
			yaw = ReadF32(buf, offset + 12);
			return true;
		}

		public static void SelfCheck()
		{
			byte[] array = new byte[16];
			Write(array, 0, 1.5f, -2.25f, 8f, 90f);
			if (!TryRead(array, array.Length, 0, out var x, out var y, out var z, out var yaw))
			{
				throw new InvalidOperationException("SatmLanIp LanPose read");
			}
			if (Math.Abs(x - 1.5f) > 0.0001f || Math.Abs(y + 2.25f) > 0.0001f || Math.Abs(z - 8f) > 0.0001f || Math.Abs(yaw - 90f) > 0.0001f)
			{
				throw new InvalidOperationException("SatmLanIp LanPose roundtrip");
			}
		}

		private static void WriteF32(byte[] buf, int offset, float v)
		{
			int num = BitConverter.SingleToInt32Bits(v);
			if (!BitConverter.IsLittleEndian)
			{
				buf[offset] = (byte)((num >> 24) & 0xFF);
				buf[offset + 1] = (byte)((num >> 16) & 0xFF);
				buf[offset + 2] = (byte)((num >> 8) & 0xFF);
				buf[offset + 3] = (byte)(num & 0xFF);
			}
			else
			{
				buf[offset] = (byte)(num & 0xFF);
				buf[offset + 1] = (byte)((num >> 8) & 0xFF);
				buf[offset + 2] = (byte)((num >> 16) & 0xFF);
				buf[offset + 3] = (byte)((num >> 24) & 0xFF);
			}
		}

		private static float ReadF32(byte[] buf, int offset)
		{
			int value = (BitConverter.IsLittleEndian ? (buf[offset] | (buf[offset + 1] << 8) | (buf[offset + 2] << 16) | (buf[offset + 3] << 24)) : ((buf[offset] << 24) | (buf[offset + 1] << 16) | (buf[offset + 2] << 8) | buf[offset + 3]));
			return BitConverter.Int32BitsToSingle(value);
		}
	}
	internal enum LanPacketType : byte
	{
		Hello = 1,
		HelloAck,
		Heartbeat,
		Goodbye,
		Ready,
		RoomSnap,
		StartMatch,
		Pose,
		RoomFull,
		MatchBusy
	}
	internal static class LanProtocol
	{
		public const uint Magic = 1397508432u;

		public const byte Version = 4;

		public const int PacketSize = 16;

		public static byte[] Encode(LanPacketType type, ushort seq, long unixMs)
		{
			byte[] array = new byte[16];
			WriteHeader(array, 0, type, seq, unixMs);
			return array;
		}

		public static void WriteHeader(byte[] buf, int offset, LanPacketType type, ushort seq, long unixMs)
		{
			buf[offset] = 83;
			buf[offset + 1] = 76;
			buf[offset + 2] = 73;
			buf[offset + 3] = 80;
			buf[offset + 4] = 4;
			buf[offset + 5] = (byte)type;
			buf[offset + 6] = (byte)(seq & 0xFF);
			buf[offset + 7] = (byte)((seq >> 8) & 0xFF);
			buf[offset + 8] = (byte)(unixMs & 0xFF);
			buf[offset + 9] = (byte)((unixMs >>> 8) & 0xFF);
			buf[offset + 10] = (byte)((unixMs >>> 16) & 0xFF);
			buf[offset + 11] = (byte)((unixMs >>> 24) & 0xFF);
			buf[offset + 12] = (byte)((unixMs >>> 32) & 0xFF);
			buf[offset + 13] = (byte)((unixMs >>> 40) & 0xFF);
			buf[offset + 14] = (byte)((unixMs >>> 48) & 0xFF);
			buf[offset + 15] = (byte)((unixMs >>> 56) & 0xFF);
		}

		public static int WritePosePacket(byte[] buf, ushort seq, float x, float y, float z, float yaw)
		{
			WriteHeader(buf, 0, LanPacketType.Pose, seq, 0L);
			LanPose.Write(buf, 16, x, y, z, yaw);
			return 32;
		}

		public static int WriteRoomSnapPacket(byte[] buf, int maxPlayers, int playerCount, int readyMask, int occupiedMask)
		{
			WriteHeader(buf, 0, LanPacketType.RoomSnap, 0, 0L);
			LanRoom.WriteSnap(buf, 16, maxPlayers, playerCount, readyMask, occupiedMask);
			return 20;
		}

		public static byte[] EncodePose(ushort seq, float x, float y, float z, float yaw)
		{
			byte[] array = new byte[32];
			WritePosePacket(array, seq, x, y, z, yaw);
			return array;
		}

		public static byte[] EncodeRoomSnap(int maxPlayers, int playerCount, int readyMask, int occupiedMask)
		{
			byte[] array = new byte[20];
			WriteRoomSnapPacket(array, maxPlayers, playerCount, readyMask, occupiedMask);
			return array;
		}

		public static int DrainPriority(LanPacketType type)
		{
			switch (type)
			{
			case LanPacketType.Hello:
			case LanPacketType.HelloAck:
			case LanPacketType.Goodbye:
			case LanPacketType.Ready:
			case LanPacketType.StartMatch:
			case LanPacketType.RoomFull:
			case LanPacketType.MatchBusy:
				return 0;
			case LanPacketType.Heartbeat:
			case LanPacketType.RoomSnap:
				return 1;
			default:
				return 2;
			}
		}

		public static bool TryParse(byte[] buf, int len, out LanPacketType type, out ushort seq, out long unixMs)
		{
			type = (LanPacketType)0;
			seq = 0;
			unixMs = 0L;
			if (buf == null || len < 16)
			{
				return false;
			}
			if (buf[0] != 83 || buf[1] != 76 || buf[2] != 73 || buf[3] != 80)
			{
				return false;
			}
			if (buf[4] != 4)
			{
				return false;
			}
			byte b = buf[5];
			if (b < 1 || b > 10)
			{
				return false;
			}
			type = (LanPacketType)b;
			seq = (ushort)(buf[6] | (buf[7] << 8));
			ulong num = buf[8] | ((ulong)buf[9] << 8) | ((ulong)buf[10] << 16) | ((ulong)buf[11] << 24) | ((ulong)buf[12] << 32) | ((ulong)buf[13] << 40) | ((ulong)buf[14] << 48) | ((ulong)buf[15] << 56);
			unixMs = (long)num;
			return true;
		}

		public static void SelfCheck()
		{
			byte[] array = Encode(LanPacketType.Hello, 1, 1700000000000L);
			if (array.Length != 16)
			{
				throw new InvalidOperationException("SatmLanIp protocol len");
			}
			if (array[0] != 83 || array[1] != 76 || array[2] != 73 || array[3] != 80)
			{
				throw new InvalidOperationException("SatmLanIp protocol magic");
			}
			if (array[4] != 4 || array[5] != 1)
			{
				throw new InvalidOperationException("SatmLanIp protocol ver/type");
			}
			if (!TryParse(array, array.Length, out var type, out var seq, out var unixMs) || type != LanPacketType.Hello || seq != 1 || unixMs != 1700000000000L)
			{
				throw new InvalidOperationException("SatmLanIp protocol roundtrip");
			}
			if (TryParse(new byte[16], 16, out var type2, out var seq2, out var unixMs2))
			{
				throw new InvalidOperationException("SatmLanIp protocol junk accepted");
			}
			byte[] array2 = EncodeRoomSnap(3, 2, 1, 3);
			if (!TryParse(array2, array2.Length, out var type3, out seq2, out unixMs2) || type3 != LanPacketType.RoomSnap)
			{
				throw new InvalidOperationException("SatmLanIp RoomSnap parse");
			}
			if (!LanRoom.TryReadSnap(array2, array2.Length, 16, out var maxPlayers, out var playerCount, out var readyMask, out var occupiedMask) || maxPlayers != 3 || playerCount != 2 || readyMask != 1 || occupiedMask != 3)
			{
				throw new InvalidOperationException("SatmLanIp RoomSnap payload");
			}
			byte[] array3 = EncodePose(9, 1.5f, 2f, 3f, 45f);
			if (array3.Length != 32 || !TryParse(array3, array3.Length, out var type4, out var seq3, out unixMs2) || type4 != LanPacketType.Pose || seq3 != 9)
			{
				throw new InvalidOperationException("SatmLanIp Pose header");
			}
			if (!LanPose.TryRead(array3, array3.Length, 16, out var x, out var _, out var _, out var yaw) || Math.Abs(x - 1.5f) > 0.0001f || Math.Abs(yaw - 45f) > 0.0001f)
			{
				throw new InvalidOperationException("SatmLanIp Pose payload");
			}
			byte[] array4 = Encode(LanPacketType.Hello, 1, 1L);
			array4[4] = 3;
			if (TryParse(array4, array4.Length, out type2, out seq2, out unixMs2))
			{
				throw new InvalidOperationException("SatmLanIp v3 datagram accepted");
			}
			byte[] array5 = Encode(LanPacketType.RoomFull, 0, 0L);
			if (!TryParse(array5, array5.Length, out var type5, out seq2, out unixMs2) || type5 != LanPacketType.RoomFull)
			{
				throw new InvalidOperationException("SatmLanIp RoomFull");
			}
			byte[] array6 = Encode(LanPacketType.MatchBusy, 0, 0L);
			if (!TryParse(array6, array6.Length, out var type6, out seq2, out unixMs2) || type6 != LanPacketType.MatchBusy)
			{
				throw new InvalidOperationException("SatmLanIp MatchBusy");
			}
			if (DrainPriority(LanPacketType.Pose) <= DrainPriority(LanPacketType.Hello) || DrainPriority(LanPacketType.Ready) != 0)
			{
				throw new InvalidOperationException("SatmLanIp DrainPriority");
			}
		}
	}
	internal static class LanRoom
	{
		public const int SlotCap = 6;

		public const int SnapPayloadSize = 4;

		public const float ClientIdleTimeoutSec = 45f;

		public static bool ShouldEvictIdleClient(float lastRxUnscaled, float nowUnscaled, float timeoutSec)
		{
			if (lastRxUnscaled <= 0f || timeoutSec <= 0f)
			{
				return false;
			}
			return nowUnscaled - lastRxUnscaled >= timeoutSec;
		}

		public static bool AllowIdleEviction(bool matchActive)
		{
			return !matchActive;
		}

		public static bool ShouldClientLobbyKeepalive(bool isHost, LanState state, int localSlot)
		{
			if (isHost || state != LanState.Connected)
			{
				return false;
			}
			if (localSlot >= 1)
			{
				return localSlot < 6;
			}
			return false;
		}

		public static int ClampMax(int n)
		{
			if (n >= 6)
			{
				return 6;
			}
			if (n >= 3)
			{
				return 3;
			}
			return 2;
		}

		public static bool SlotReady(int mask, int slot)
		{
			if (slot < 0 || slot >= 6)
			{
				return false;
			}
			return (mask & (1 << slot)) != 0;
		}

		public static int SetSlotReady(int mask, int slot, bool ready)
		{
			if (slot < 0 || slot >= 6)
			{
				return mask;
			}
			int num = 1 << slot;
			if (ready)
			{
				return mask | num;
			}
			return mask & ~num;
		}

		public static bool AllOccupiedReady(int readyMask, int occupiedMask)
		{
			int num = occupiedMask & 0x3F;
			if (num <= 1)
			{
				return false;
			}
			for (int i = 0; i < 6; i++)
			{
				int num2 = 1 << i;
				if ((num & num2) != 0 && (readyMask & num2) == 0)
				{
					return false;
				}
			}
			return true;
		}

		public static int CountOccupiedReady(int readyMask, int occupiedMask)
		{
			int num = 0;
			int num2 = occupiedMask & 0x3F;
			for (int i = 0; i < 6; i++)
			{
				int num3 = 1 << i;
				if ((num2 & num3) != 0 && (readyMask & num3) != 0)
				{
					num++;
				}
			}
			return num;
		}

		public static int PopCount6(int mask)
		{
			int num = 0;
			for (int num2 = mask & 0x3F; num2 != 0; num2 >>= 1)
			{
				num += num2 & 1;
			}
			return num;
		}

		public static int SeatedMask(int occupiedMask, int playerCount, int maxPlayers)
		{
			int num = ClampMax((maxPlayers < 2) ? 2 : maxPlayers);
			int num2 = (1 << num) - 1;
			int num3 = occupiedMask & num2;
			int num4 = playerCount;
			if (num4 < 1)
			{
				num4 = 1;
			}
			if (num4 > num)
			{
				num4 = num;
			}
			if (PopCount6(num3) == num4)
			{
				return num3;
			}
			return num2 & ((1 << num4) - 1);
		}

		public static int CountSeatedReady(int readyMask, int occupiedMask, int playerCount, int maxPlayers, bool localReady, int localSlot, out int seated)
		{
			int num = SeatedMask(occupiedMask, playerCount, maxPlayers);
			seated = PopCount6(num);
			if (seated < 1)
			{
				seated = 1;
			}
			int num2 = CountOccupiedReady(readyMask, num);
			if (localReady && localSlot >= 0 && localSlot < 6)
			{
				int num3 = 1 << localSlot;
				if ((num & num3) != 0 && (readyMask & num3) == 0)
				{
					num2++;
				}
			}
			if (num2 > seated)
			{
				num2 = seated;
			}
			return num2;
		}

		public static void WriteSnap(byte[] buf, int offset, int maxPlayers, int playerCount, int readyMask, int occupiedMask)
		{
			buf[offset] = (byte)ClampMax(maxPlayers);
			int num = playerCount;
			if (num < 1)
			{
				num = 1;
			}
			if (num > 6)
			{
				num = 6;
			}
			buf[offset + 1] = (byte)num;
			buf[offset + 2] = (byte)(readyMask & 0x3F);
			buf[offset + 3] = (byte)(occupiedMask & 0x3F);
		}

		public static bool TryReadSnap(byte[] buf, int len, int offset, out int maxPlayers, out int playerCount, out int readyMask, out int occupiedMask)
		{
			maxPlayers = 2;
			playerCount = 1;
			readyMask = 0;
			occupiedMask = 1;
			if (buf == null || offset < 0 || len < offset + 4)
			{
				return false;
			}
			maxPlayers = ClampMax(buf[offset]);
			playerCount = buf[offset + 1];
			if (playerCount < 1)
			{
				playerCount = 1;
			}
			if (playerCount > 6)
			{
				playerCount = 6;
			}
			readyMask = buf[offset + 2] & 0x3F;
			occupiedMask = buf[offset + 3] & 0x3F;
			if (occupiedMask == 0)
			{
				occupiedMask = 1;
			}
			return true;
		}

		public static ushort PackReady(bool ready)
		{
			return PackReady(ready, 0);
		}

		public static ushort PackReady(bool ready, int slot)
		{
			int num = slot;
			if (num < 0)
			{
				num = 0;
			}
			if (num >= 6)
			{
				num = 5;
			}
			return (ushort)((uint)(num << 8) | (ready ? 1u : 0u));
		}

		public static bool UnpackReady(ushort seq)
		{
			return (seq & 0xFF) != 0;
		}

		public static int UnpackReadySlot(ushort seq)
		{
			return (seq >> 8) & 0xFF;
		}

		public static string FormatRoomLine(string role, int playerCount, int maxPlayers, int readyMask)
		{
			int num = ClampMax(maxPlayers);
			int num2 = ((playerCount < 1) ? 1 : playerCount);
			return "LAN  " + role + "  ROOM  " + num2 + "/" + num + "  ready=" + readyMask.ToString("X2");
		}

		public static void SelfCheck()
		{
			if (ClampMax(1) != 2 || ClampMax(2) != 2 || ClampMax(3) != 3 || ClampMax(4) != 3 || ClampMax(6) != 6)
			{
				throw new InvalidOperationException("SatmLanIp ClampMax");
			}
			if (ShouldEvictIdleClient(0f, 20f, 15f) || ShouldEvictIdleClient(10f, 20f, 15f) || !ShouldEvictIdleClient(5f, 20f, 15f))
			{
				throw new InvalidOperationException("SatmLanIp ShouldEvictIdleClient");
			}
			if (ShouldClientLobbyKeepalive(isHost: true, LanState.Connected, 0) || !ShouldClientLobbyKeepalive(isHost: false, LanState.Connected, 2) || ShouldClientLobbyKeepalive(isHost: false, LanState.Connecting, 1))
			{
				throw new InvalidOperationException("SatmLanIp ShouldClientLobbyKeepalive");
			}
			if (!AllowIdleEviction(matchActive: false) || AllowIdleEviction(matchActive: true))
			{
				throw new InvalidOperationException("SatmLanIp AllowIdleEviction");
			}
			byte[] array = new byte[4];
			int readyMask = SetSlotReady(1, 1, ready: true);
			WriteSnap(array, 0, 3, 2, readyMask, 3);
			if (!TryReadSnap(array, array.Length, 0, out var maxPlayers, out var playerCount, out var readyMask2, out var occupiedMask) || maxPlayers != 3 || playerCount != 2 || occupiedMask != 3)
			{
				throw new InvalidOperationException("SatmLanIp WriteSnap roundtrip");
			}
			if (!SlotReady(readyMask2, 0) || !SlotReady(readyMask2, 1) || SlotReady(readyMask2, 2))
			{
				throw new InvalidOperationException("SatmLanIp ready bits");
			}
			if (!AllOccupiedReady(readyMask2, 3) || AllOccupiedReady(readyMask2, 7))
			{
				throw new InvalidOperationException("SatmLanIp AllOccupiedReady");
			}
			if (AllOccupiedReady(readyMask, 5))
			{
				throw new InvalidOperationException("SatmLanIp AllOccupiedReady hole");
			}
			if (CountOccupiedReady(readyMask2, 3) != 2 || CountOccupiedReady(readyMask, 5) != 1)
			{
				throw new InvalidOperationException("SatmLanIp CountOccupiedReady");
			}
			if (SeatedMask(63, 3, 3) != 7)
			{
				throw new InvalidOperationException("SatmLanIp SeatedMask wide occ");
			}
			if (CountSeatedReady(1, 63, 3, 3, localReady: true, 0, out var seated) != 1 || seated != 3)
			{
				throw new InvalidOperationException("SatmLanIp CountSeatedReady host-only");
			}
			if (CountSeatedReady(7, 63, 3, 3, localReady: true, 0, out var seated2) != 3 || seated2 != 3)
			{
				throw new InvalidOperationException("SatmLanIp CountSeatedReady all-ready");
			}
			if (!AllOccupiedReady(7, SeatedMask(63, 3, 3)))
			{
				throw new InvalidOperationException("SatmLanIp AllOccupiedReady seated");
			}
			if (PackReady(ready: true) != 1 || PackReady(ready: false) != 0 || !UnpackReady(1) || UnpackReady(0))
			{
				throw new InvalidOperationException("SatmLanIp PackReady");
			}
			if (PackReady(ready: true, 3) != 769 || UnpackReadySlot(769) != 3 || !UnpackReady(769))
			{
				throw new InvalidOperationException("SatmLanIp PackReady slot");
			}
			string text = FormatRoomLine("HOST", 2, 3, 1);
			if (text != "LAN  HOST  ROOM  2/3  ready=01")
			{
				throw new InvalidOperationException("SatmLanIp FormatRoomLine: " + text);
			}
			string text2 = FormatSlotLines(3, 1, 1);
			if (text2.IndexOf("空", StringComparison.Ordinal) < 0 || text2.IndexOf("房主", StringComparison.Ordinal) < 0 || text2.IndexOf("已准备", StringComparison.Ordinal) < 0)
			{
				throw new InvalidOperationException("SatmLanIp FormatSlotLines: " + text2);
			}
			if (FormatStartHint(isHost: false, allReady: false, 1, 3) != "" || FormatStartHint(isHost: true, allReady: true, 2, 3) != "" || FormatStartHint(isHost: true, allReady: false, 1, 3).IndexOf("2", StringComparison.Ordinal) < 0)
			{
				throw new InvalidOperationException("SatmLanIp FormatStartHint");
			}
		}

		public static string FormatSlotLines(int maxPlayers, int occupiedMask, int readyMask)
		{
			int num = ClampMax(maxPlayers);
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < num; i++)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Append('\n');
				}
				int num2 = 1 << i;
				string value = ((i == 0) ? "房主" : ("玩家" + (i + 1)));
				stringBuilder.Append("槽").Append(i + 1).Append("  ")
					.Append(value)
					.Append("  ");
				if ((occupiedMask & num2) == 0)
				{
					stringBuilder.Append("空");
				}
				else
				{
					stringBuilder.Append(SlotReady(readyMask, i) ? "已准备" : "未准备");
				}
			}
			return stringBuilder.ToString();
		}

		public static string FormatStartHint(bool isHost, bool allReady, int playerCount, int maxPlayers)
		{
			if (!isHost || allReady)
			{
				return "";
			}
			if (playerCount < 2)
			{
				return "开始:至少 2 人且全员准备";
			}
			return "开始:等待全员准备";
		}
	}
	internal enum LanState
	{
		Idle,
		Listen,
		Connecting,
		Connected,
		Fail,
		Drop
	}
	internal sealed class LanSession
	{
		public LanState State;

		public bool IsHost;

		public string PeerEndPoint = "";

		public int LastRttMs = -1;

		public string FailReason = "";

		public int FusionStartsBlocked;

		public int MaxPlayers = 2;

		public int PlayerCount = 1;

		public int ReadyMask;

		public int OccupiedMask = 1;

		public int LocalSlot;

		public bool LocalReady;

		public bool MatchActive;

		public bool HasRemotePose;

		public float RemoteX;

		public float RemoteY;

		public float RemoteZ;

		public float RemoteYaw;

		public readonly bool[] PeerPoseHas = new bool[6];

		public readonly float[] PeerPoseX = new float[6];

		public readonly float[] PeerPoseY = new float[6];

		public readonly float[] PeerPoseZ = new float[6];

		public readonly float[] PeerPoseYaw = new float[6];

		public bool HostReady => LanRoom.SlotReady(ReadyMask, 0);

		public bool ClientReady
		{
			get
			{
				int num = LanRoom.SeatedMask(OccupiedMask, PlayerCount, MaxPlayers);
				for (int i = 1; i < 6; i++)
				{
					if ((num & (1 << i)) != 0 && LanRoom.SlotReady(ReadyMask, i))
					{
						return true;
					}
				}
				return false;
			}
		}

		public bool AllReady => LanRoom.AllOccupiedReady(ReadyMask, LanRoom.SeatedMask(OccupiedMask, PlayerCount, MaxPlayers));

		public bool InRoom
		{
			get
			{
				if (State != LanState.Listen && State != LanState.Connecting)
				{
					return State == LanState.Connected;
				}
				return true;
			}
		}

		public void MarkHostDrop()
		{
			MatchActive = false;
			State = LanState.Drop;
		}

		public void ClearMatchActive()
		{
			MatchActive = false;
		}

		public void SetPeerPose(int slot, float x, float y, float z, float yaw)
		{
			if (slot < 0 || slot >= 6)
			{
				slot = 0;
			}
			PeerPoseHas[slot] = true;
			PeerPoseX[slot] = x;
			PeerPoseY[slot] = y;
			PeerPoseZ[slot] = z;
			PeerPoseYaw[slot] = yaw;
			HasRemotePose = true;
			RemoteX = x;
			RemoteY = y;
			RemoteZ = z;
			RemoteYaw = yaw;
		}

		public bool TryGetPeerPose(int slot, out float x, out float y, out float z, out float yaw)
		{
			x = (y = (z = (yaw = 0f)));
			if (slot < 0 || slot >= 6 || !PeerPoseHas[slot])
			{
				return false;
			}
			x = PeerPoseX[slot];
			y = PeerPoseY[slot];
			z = PeerPoseZ[slot];
			yaw = PeerPoseYaw[slot];
			return true;
		}

		public void ClearPeerPoses()
		{
			for (int i = 0; i < 6; i++)
			{
				PeerPoseHas[i] = false;
			}
			HasRemotePose = false;
			RemoteX = 0f;
			RemoteY = 0f;
			RemoteZ = 0f;
			RemoteYaw = 0f;
		}

		public static int ResolveSaveSlot(int hostPickedSlot, int currentSlot, int firstExistingOrNeg1)
		{
			if (hostPickedSlot >= 0)
			{
				return hostPickedSlot;
			}
			if (firstExistingOrNeg1 >= 0)
			{
				return firstExistingOrNeg1;
			}
			if (currentSlot >= 0)
			{
				return currentSlot;
			}
			return 0;
		}
	}
	internal sealed class LanTransport
	{
		private sealed class RxPkt
		{
			public byte[] Data;

			public IPEndPoint Remote;
		}

		private const float ProbeIntervalSec = 0.5f;

		private const float EchoWaitTimeoutSec = 1.5f;

		private const float HelloIntervalSec = 0.5f;

		private const float SnapIntervalSec = 0.5f;

		private const int DrainCap = 64;

		private readonly LanSession _session = new LanSession();

		private readonly IPEndPoint[] _clients = new IPEndPoint[6];

		private readonly float[] _clientLastRx = new float[6];

		private readonly byte[] _pkt16 = new byte[16];

		private readonly byte[] _pktPose = new byte[32];

		private readonly byte[] _pktSnap = new byte[20];

		private readonly RxPkt[] _drainBuf = new RxPkt[64];

		private UdpClient _udp;

		private IPEndPoint _hostEp;

		private IPEndPoint _clientTarget;

		private string _clientHost = "";

		private int _clientPort;

		private readonly ConcurrentQueue<RxPkt> _inbox = new ConcurrentQueue<RxPkt>();

		private Thread _rxThread;

		private Thread _kaThread;

		private volatile bool _rxRun;

		private int _rxLogLeft;

		private bool _loggedFirstHello;

		private float _nextHelloOrHb;

		private float _connectDeadline;

		private ushort _seq;

		private ushort _pendingProbeSeq;

		private bool _awaitingEcho;

		private float _echoDeadline;

		private float _nextProbe;

		private float _nextSnap;

		private float _nextReadySend;

		public LanSession Session => _session;

		public int ConnectSecondsLeft()
		{
			if (_session.State != LanState.Connecting)
			{
				return -1;
			}
			float num = _connectDeadline - Time.unscaledTime;
			if (num < 0f)
			{
				num = 0f;
			}
			return (int)Math.Ceiling(num);
		}

		public void StartHost(int port, int maxPlayers)
		{
			DisconnectSocketsOnly();
			try
			{
				_udp = OpenUdp4(port);
				StartRxThread();
				_session.IsHost = true;
				_session.State = LanState.Listen;
				_session.PeerEndPoint = "";
				_session.LastRttMs = -1;
				_session.FailReason = "";
				_session.MaxPlayers = LanRoom.ClampMax(maxPlayers);
				_session.LocalSlot = 0;
				ClearClients();
				_awaitingEcho = false;
				ResetRoomKeepMax();
				_session.PlayerCount = 1;
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host listen :" + port + " max=" + _session.MaxPlayers + " local=" + FormatEp(_udp.Client.LocalEndPoint as IPEndPoint)));
				SendSelfProbe(port);
			}
			catch (SocketException ex)
			{
				_session.State = LanState.Fail;
				_session.FailReason = "port in use / bind failed: " + ex.SocketErrorCode;
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] StartHost failed: " + _session.FailReason));
				DisposeUdp();
			}
			catch (Exception ex2)
			{
				_session.State = LanState.Fail;
				_session.FailReason = ex2.GetType().Name + ": " + ex2.Message;
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] StartHost failed: " + _session.FailReason));
				DisposeUdp();
			}
		}

		public void StartHost(int port)
		{
			StartHost(port, (_session.MaxPlayers > 0) ? _session.MaxPlayers : 2);
		}

		public void StartClient(string ip, int port, int timeoutSec)
		{
			DisconnectSocketsOnly();
			_session.IsHost = false;
			if (!LanHostParse.TryParseHostPort(ip, port, out var host, out var port2, out var error))
			{
				_session.State = LanState.Fail;
				_session.FailReason = error;
				_session.PeerEndPoint = "";
				_session.LastRttMs = -1;
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] StartClient aborted: " + error + " raw='" + ip + "'"));
				return;
			}
			if (!IPAddress.TryParse(host, out IPAddress address))
			{
				_session.State = LanState.Fail;
				_session.FailReason = "invalid JoinAddress";
				_session.PeerEndPoint = "";
				_session.LastRttMs = -1;
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] StartClient aborted: invalid IP '" + host + "'"));
				return;
			}
			try
			{
				_udp = OpenUdp4(0);
				StartRxThread();
				_clientHost = host;
				_clientPort = port2;
				Plugin.SetLanPort(LanFusionStart.SessionPortAfterJoinParse(port2));
				_clientTarget = new IPEndPoint(address, port2);
				_hostEp = _clientTarget;
				_session.IsHost = false;
				_session.State = LanState.Connecting;
				_session.PeerEndPoint = host + ":" + port2;
				_session.LastRttMs = -1;
				_session.FailReason = "";
				_session.LocalSlot = 0;
				_awaitingEcho = false;
				_loggedFirstHello = false;
				_connectDeadline = Time.unscaledTime + (float)Math.Max(1, timeoutSec);
				_nextHelloOrHb = 0f;
				ClearClients();
				ResetRoomKeepMax();
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Client connecting " + host + ":" + port2 + " sessionPort=" + Plugin.JoinPort));
			}
			catch (Exception ex)
			{
				_session.State = LanState.Fail;
				_session.FailReason = ex.GetType().Name + ": " + ex.Message;
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] StartClient failed: " + _session.FailReason));
				DisposeUdp();
			}
		}

		public void Disconnect()
		{
			if (_udp != null && (_session.State == LanState.Connected || _session.State == LanState.Listen || _session.State == LanState.Connecting))
			{
				try
				{
					byte[] array = LanProtocol.Encode(LanPacketType.Goodbye, NextSeq(), NowMs());
					if (_session.IsHost)
					{
						BroadcastRaw(array);
					}
					else if (_hostEp != null)
					{
						_udp.Send(array, array.Length, _hostEp);
					}
				}
				catch
				{
				}
			}
			DisconnectSocketsOnly();
			_session.State = LanState.Idle;
			_session.PeerEndPoint = "";
			_session.LastRttMs = -1;
			_session.FailReason = "";
			_session.IsHost = false;
			_session.LocalSlot = 0;
			ResetRoomKeepMax();
			Plugin.LogSrc.LogInfo((object)"[SatmLanIp] Disconnect -> Idle");
		}

		public void NotifyMatchLeaving()
		{
			if (_udp == null || (_session.State != LanState.Connected && _session.State != LanState.Listen))
			{
				return;
			}
			try
			{
				LanProtocol.WriteHeader(_pkt16, 0, LanPacketType.Goodbye, NextSeq(), NowMs());
				if (_session.IsHost)
				{
					BroadcastRaw(_pkt16, 16);
				}
				else if (_hostEp != null)
				{
					_udp.Send(_pkt16, 16, _hostEp);
				}
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_leave goodbye host=" + _session.IsHost));
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] match_leave goodbye " + ex.GetType().Name));
			}
		}

		public void Poll()
		{
			if (_udp == null)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			if (_awaitingEcho && unscaledTime >= _echoDeadline)
			{
				_awaitingEcho = false;
			}
			DrainRecv();
			if (_udp == null)
			{
				return;
			}
			if (_session.State == LanState.Connecting && unscaledTime >= _connectDeadline)
			{
				_session.State = LanState.Fail;
				_session.FailReason = "connect timeout";
				Plugin.LogSrc.LogWarning((object)"[SatmLanIp] Client connect timeout");
				DisposeUdp();
				return;
			}
			if (_session.State == LanState.Connecting)
			{
				if (unscaledTime >= _nextHelloOrHb)
				{
					_nextHelloOrHb = unscaledTime + 0.5f;
					SendTo(_clientHost, _clientPort, LanPacketType.Hello, NextSeq(), NowMs());
					if (!_loggedFirstHello)
					{
						_loggedFirstHello = true;
						Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Client Hello -> " + _clientHost + ":" + _clientPort));
					}
				}
				return;
			}
			if (_session.State == LanState.Connected && _hostEp != null && !_session.IsHost && !_awaitingEcho && unscaledTime >= _nextProbe)
			{
				_nextProbe = unscaledTime + 0.5f;
				_pendingProbeSeq = NextSeq();
				_awaitingEcho = true;
				_echoDeadline = unscaledTime + 1.5f;
				ushort seq = (ushort)((_session.LocalSlot << 8) | (_pendingProbeSeq & 0xFF));
				SendTo(_hostEp, LanPacketType.Heartbeat, seq, NowMs());
			}
			if (_session.State == LanState.Connected && _hostEp != null && !_session.IsHost && unscaledTime >= _nextReadySend)
			{
				_nextReadySend = unscaledTime + 0.5f;
				SendTo(_hostEp, LanPacketType.Ready, LanRoom.PackReady(_session.LocalReady, _session.LocalSlot), NowMs());
			}
			if (_session.IsHost && _session.InRoom && ClientCount() > 0 && unscaledTime >= _nextSnap)
			{
				_nextSnap = unscaledTime + 0.5f;
				SendSnap();
			}
			if (_session.IsHost && _session.InRoom)
			{
				EvictIdleClients(unscaledTime);
			}
		}

		private void HandlePacket(byte[] data, IPEndPoint remote)
		{
			if (!LanProtocol.TryParse(data, data.Length, out var type, out var seq, out var unixMs))
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] drop pkt len=" + data.Length + " from=" + FormatEp(remote)));
				return;
			}
			if (_rxLogLeft > 0)
			{
				_rxLogLeft--;
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] rx type=" + type.ToString() + " from=" + FormatEp(remote) + " len=" + data.Length));
			}
			switch (type)
			{
			case LanPacketType.Hello:
				HandleHello(remote, seq, unixMs);
				break;
			case LanPacketType.HelloAck:
				if (_session.State == LanState.Connecting && !_session.IsHost)
				{
					_hostEp = remote;
					_session.PeerEndPoint = remote.ToString();
					_session.State = LanState.Connected;
					_session.LocalSlot = ((seq <= 0 || seq >= 6) ? 1 : seq);
					_nextProbe = Time.unscaledTime + 0.5f;
					_session.PlayerCount = 1;
					_session.OccupiedMask = 1 << _session.LocalSlot;
					if (_session.LocalSlot == 0)
					{
						_session.OccupiedMask = 1;
					}
					_session.LocalReady = false;
					_session.ReadyMask = 0;
					_session.MatchActive = false;
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Client CONNECTED peer=" + _session.PeerEndPoint + " slot=" + _session.LocalSlot));
					NoPhotonProbe.OnConnected();
				}
				break;
			case LanPacketType.RoomFull:
				if (_session.State == LanState.Connecting && !_session.IsHost)
				{
					_session.State = LanState.Fail;
					_session.FailReason = "room full";
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] Client room full");
					DisposeUdp();
				}
				break;
			case LanPacketType.MatchBusy:
				if (_session.State == LanState.Connecting && !_session.IsHost)
				{
					_session.State = LanState.Fail;
					_session.FailReason = "match already started";
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] Client match already started");
					DisposeUdp();
				}
				break;
			case LanPacketType.Heartbeat:
				HandleHeartbeat(remote, seq, unixMs);
				break;
			case LanPacketType.Ready:
			{
				if (!_session.IsHost || !_session.InRoom)
				{
					break;
				}
				int num = LanRoom.UnpackReadySlot(seq);
				int num2 = ((num >= 1 && num < 6) ? num : FindClientSlot(remote));
				if (num2 >= 0)
				{
					BindClientSlot(num2, remote, Time.unscaledTime);
					bool ready = LanRoom.UnpackReady(seq);
					int num3 = LanRoom.SetSlotReady(_session.ReadyMask, num2, ready);
					if (num3 != _session.ReadyMask)
					{
						_session.ReadyMask = num3;
						Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host saw slot " + num2 + " Ready=" + ready));
						SendSnap();
					}
				}
				break;
			}
			case LanPacketType.RoomSnap:
			{
				if (!_session.IsHost && _session.State == LanState.Connected && EndPointEquals(_hostEp, remote) && LanRoom.TryReadSnap(data, data.Length, 16, out var maxPlayers, out var playerCount, out var readyMask, out var occupiedMask))
				{
					_session.MaxPlayers = maxPlayers;
					_session.PlayerCount = playerCount;
					_session.ReadyMask = readyMask;
					_session.OccupiedMask = occupiedMask;
				}
				break;
			}
			case LanPacketType.StartMatch:
				if (_session.State == LanState.Connected && !_session.IsHost && EndPointEquals(_hostEp, remote))
				{
					LanMatch.TryBegin("peer");
				}
				break;
			case LanPacketType.Pose:
				HandlePose(data, remote);
				break;
			case LanPacketType.Goodbye:
				HandleGoodbye(remote);
				break;
			}
		}

		private void HandleHello(IPEndPoint remote, ushort seq, long unixMs)
		{
			if (!_session.IsHost || !_session.InRoom)
			{
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host ignore Hello from " + FormatEp(remote) + " isHost=" + _session.IsHost + " state=" + _session.State));
				return;
			}
			if (_session.MatchActive)
			{
				SendTo(remote, LanPacketType.MatchBusy, 0, 0L);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host reject MatchBusy from " + FormatEp(remote)));
				return;
			}
			int num = FindClientSlot(remote);
			if (num > 0)
			{
				BindClientSlot(num, remote, Time.unscaledTime);
				SendTo(remote, LanPacketType.HelloAck, (ushort)num, unixMs);
				SendSnap();
				return;
			}
			int num2 = FirstFreeSlot();
			if (num2 < 0)
			{
				SendTo(remote, LanPacketType.RoomFull, 0, 0L);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host reject full from " + FormatEp(remote)));
				return;
			}
			BindClientSlot(num2, remote, Time.unscaledTime);
			RecountPlayers();
			_session.State = LanState.Connected;
			RefreshPeerSummary();
			SendTo(remote, LanPacketType.HelloAck, (ushort)num2, unixMs);
			SendSnap();
			Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host accepted slot=" + num2 + " peer=" + FormatEp(remote) + " count=" + _session.PlayerCount + "/" + _session.MaxPlayers));
			NoPhotonProbe.OnConnected();
		}

		private void HandleHeartbeat(IPEndPoint remote, ushort seq, long unixMs)
		{
			if (_session.IsHost && _session.State == LanState.Listen)
			{
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] self-probe rx from=" + FormatEp(remote)));
			}
			else
			{
				if (_session.State != LanState.Connected)
				{
					return;
				}
				if (!_session.IsHost)
				{
					if (EndPointEquals(_hostEp, remote) && _awaitingEcho && (seq & 0xFF) == (_pendingProbeSeq & 0xFF))
					{
						int lastRttMs = (int)Math.Max(0L, NowMs() - unixMs);
						_session.LastRttMs = lastRttMs;
						_awaitingEcho = false;
					}
					return;
				}
				int num = LanRoom.UnpackReadySlot(seq);
				if (num < 1 || num >= 6)
				{
					num = FindClientSlot(remote);
				}
				if (num >= 0)
				{
					BindClientSlot(num, remote, Time.unscaledTime);
					SendTo(remote, LanPacketType.Heartbeat, seq, unixMs);
				}
			}
		}

		private void HandlePose(byte[] data, IPEndPoint remote)
		{
			if (_session.State != LanState.Connected || !LanProtocol.TryParse(data, data.Length, out var _, out var seq, out var _) || !LanPose.TryRead(data, data.Length, 16, out var x, out var y, out var z, out var yaw))
			{
				return;
			}
			if (_session.IsHost)
			{
				int num = FindClientSlot(remote);
				if (num >= 0)
				{
					BindClientSlot(num, remote, Time.unscaledTime);
					ApplyPose(num, x, y, z, yaw);
					RelayPoseExcept(data, num, remote);
				}
			}
			else if (EndPointEquals(_hostEp, remote))
			{
				int slot = ((seq < 6) ? seq : 0);
				ApplyPose(slot, x, y, z, yaw);
			}
		}

		private void HandleGoodbye(IPEndPoint remote)
		{
			if (_session.IsHost)
			{
				int num = FindClientSlot(remote);
				if (num >= 0)
				{
					DropHostClientSlot(num, "goodbye");
				}
			}
			else if (_session.State == LanState.Connected || _session.State == LanState.Connecting)
			{
				Plugin.LogSrc.LogInfo((object)"[SatmLanIp] host Goodbye -> Drop");
				if (_session.MatchActive)
				{
					LanMatch.RequestStockLeave();
				}
				_session.MarkHostDrop();
				DisposeUdp();
			}
		}

		private void EvictIdleClients(float now)
		{
			if (!LanRoom.AllowIdleEviction(_session.MatchActive))
			{
				return;
			}
			for (int i = 1; i < 6; i++)
			{
				if (_clients[i] != null && LanRoom.ShouldEvictIdleClient(_clientLastRx[i], now, 45f))
				{
					DropHostClientSlot(i, "idle-timeout");
				}
			}
		}

		private void DropHostClientSlot(int slot, string why)
		{
			if (slot < 0 || slot >= 6 || _clients[slot] == null)
			{
				return;
			}
			IPEndPoint endPoint = _clients[slot];
			try
			{
				LanProtocol.WriteHeader(_pkt16, 0, LanPacketType.Goodbye, NextSeq(), NowMs());
				_udp?.Send(_pkt16, 16, endPoint);
			}
			catch
			{
			}
			_clients[slot] = null;
			_clientLastRx[slot] = 0f;
			_session.ReadyMask = LanRoom.SetSlotReady(_session.ReadyMask, slot, ready: false);
			RecountPlayers();
			RefreshPeerSummary();
			Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host slot " + slot + " left via=" + why + " count=" + _session.PlayerCount));
			if (!_session.MatchActive)
			{
				if (ClientCount() == 0)
				{
					_session.State = LanState.Listen;
					_awaitingEcho = false;
				}
				else
				{
					SendSnap();
				}
			}
		}

		private void BindClientSlot(int slot, IPEndPoint remote, float now)
		{
			if (slot >= 0 && slot < 6 && remote != null)
			{
				_clients[slot] = new IPEndPoint(remote.Address, remote.Port);
				TouchClientRx(slot, now);
			}
		}

		private void TouchClientRx(int slot, float now)
		{
			if (slot >= 0 && slot < 6)
			{
				_clientLastRx[slot] = now;
			}
		}

		public void ToggleLocalReady()
		{
			if (_udp != null && _session.InRoom && ((_session.IsHost && _session.State == LanState.Listen) || _session.State == LanState.Connected || (_session.IsHost && _session.State == LanState.Listen)))
			{
				_session.LocalReady = !_session.LocalReady;
				if (_session.IsHost)
				{
					_session.ReadyMask = LanRoom.SetSlotReady(_session.ReadyMask, 0, _session.LocalReady);
					SendSnap();
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Host Ready=" + _session.LocalReady));
				}
				else if (_hostEp != null)
				{
					SendTo(_hostEp, LanPacketType.Ready, LanRoom.PackReady(_session.LocalReady, _session.LocalSlot), NowMs());
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Client Ready=" + _session.LocalReady));
				}
			}
		}

		public void SendStartMatch()
		{
			if (_session.IsHost && _udp != null && _session.AllReady)
			{
				Broadcast(LanPacketType.StartMatch, NextSeq(), 0L);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] StartMatch broadcast clients=" + ClientCount()));
			}
		}

		public void SendPose(float x, float y, float z, float yaw)
		{
			if (_session.State != LanState.Connected || _udp == null)
			{
				return;
			}
			try
			{
				int num = LanProtocol.WritePosePacket(_pktPose, (ushort)_session.LocalSlot, x, y, z, yaw);
				if (_session.IsHost)
				{
					BroadcastRaw(_pktPose, num);
				}
				else if (_hostEp != null)
				{
					_udp.Send(_pktPose, num, _hostEp);
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] pose send: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void ApplyPose(int slot, float px, float py, float pz, float pyaw)
		{
			bool num = !_session.HasRemotePose;
			_session.SetPeerPose(slot, px, py, pz, pyaw);
			if (num)
			{
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] match_pose=ok slot=" + slot));
			}
		}

		private void RelayPoseExcept(byte[] pkt, int slot, IPEndPoint except)
		{
			pkt[6] = (byte)(slot & 0xFF);
			pkt[7] = (byte)((slot >> 8) & 0xFF);
			for (int i = 1; i < 6; i++)
			{
				IPEndPoint iPEndPoint = _clients[i];
				if (iPEndPoint != null && !EndPointEquals(iPEndPoint, except))
				{
					try
					{
						_udp.Send(pkt, pkt.Length, iPEndPoint);
					}
					catch
					{
					}
				}
			}
		}

		private void ResetRoomKeepMax()
		{
			int maxPlayers = LanRoom.ClampMax(_session.MaxPlayers);
			_session.PlayerCount = 1;
			_session.ReadyMask = 0;
			_session.OccupiedMask = 1;
			_session.LocalReady = false;
			_session.MatchActive = false;
			_session.ClearPeerPoses();
			_session.MaxPlayers = maxPlayers;
			_nextSnap = 0f;
			_nextReadySend = 0f;
		}

		private void SendSnap()
		{
			if (!_session.IsHost || _udp == null)
			{
				return;
			}
			try
			{
				int len = LanProtocol.WriteRoomSnapPacket(_pktSnap, _session.MaxPlayers, _session.PlayerCount, _session.ReadyMask, _session.OccupiedMask);
				BroadcastRaw(_pktSnap, len);
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] snap send: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void Broadcast(LanPacketType type, ushort seq, long unixMs)
		{
			LanProtocol.WriteHeader(_pkt16, 0, type, seq, unixMs);
			BroadcastRaw(_pkt16, 16);
		}

		private void BroadcastRaw(byte[] pkt)
		{
			BroadcastRaw(pkt, (pkt != null) ? pkt.Length : 0);
		}

		private void BroadcastRaw(byte[] pkt, int len)
		{
			if (_udp == null || pkt == null || len <= 0)
			{
				return;
			}
			for (int i = 1; i < 6; i++)
			{
				IPEndPoint iPEndPoint = _clients[i];
				if (iPEndPoint != null)
				{
					try
					{
						_udp.Send(pkt, len, iPEndPoint);
					}
					catch (Exception ex)
					{
						Plugin.LogSrc.LogWarning((object)("[SatmLanIp] send failed: " + ex.GetType().Name + ": " + ex.Message));
					}
				}
			}
		}

		private void SendTo(IPEndPoint ep, LanPacketType type, ushort seq, long unixMs)
		{
			if (ep != null)
			{
				string host = ((ep.Address != null) ? ep.Address.ToString() : "");
				SendTo(host, ep.Port, type, seq, unixMs);
			}
		}

		private void SendTo(string host, int port, LanPacketType type, ushort seq, long unixMs)
		{
			if (_udp == null || host == null || host.Length == 0 || port < 1)
			{
				return;
			}
			try
			{
				LanProtocol.WriteHeader(_pkt16, 0, type, seq, unixMs);
				int num = _udp.Send(_pkt16, 16, host, port);
				if (!_loggedFirstHello && type == LanPacketType.Hello)
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] send Hello n=" + num + " -> " + host + ":" + port));
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] send failed: " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private void SendSelfProbe(int port)
		{
			if (_udp == null)
			{
				return;
			}
			try
			{
				LanProtocol.WriteHeader(_pkt16, 0, LanPacketType.Heartbeat, 0, 0L);
				int num = _udp.Send(_pkt16, 16, "127.0.0.1", port);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] self-probe send n=" + num + " -> 127.0.0.1:" + port));
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] self-probe send " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private int FindClientSlot(IPEndPoint remote)
		{
			for (int i = 1; i < 6; i++)
			{
				if (EndPointEquals(_clients[i], remote))
				{
					return i;
				}
			}
			return -1;
		}

		private int FirstFreeSlot()
		{
			int num = _session.MaxPlayers;
			if (num < 2)
			{
				num = 2;
			}
			if (num > 6)
			{
				num = 6;
			}
			for (int i = 1; i < num; i++)
			{
				if (_clients[i] == null)
				{
					return i;
				}
			}
			return -1;
		}

		private int ClientCount()
		{
			int num = 0;
			for (int i = 1; i < 6; i++)
			{
				if (_clients[i] != null)
				{
					num++;
				}
			}
			return num;
		}

		private void RecountPlayers()
		{
			int num = _session.MaxPlayers;
			if (num < 2)
			{
				num = 2;
			}
			if (num > 6)
			{
				num = 6;
			}
			int num2 = 1;
			int num3 = 0;
			for (int i = 1; i < num; i++)
			{
				if (_clients[i] != null)
				{
					num2 |= 1 << i;
					num3++;
				}
			}
			for (int j = num; j < 6; j++)
			{
				_clients[j] = null;
			}
			_session.OccupiedMask = num2;
			_session.PlayerCount = 1 + num3;
		}

		private void RefreshPeerSummary()
		{
			if (ClientCount() == 0)
			{
				_session.PeerEndPoint = "";
				return;
			}
			for (int i = 1; i < 6; i++)
			{
				if (_clients[i] != null)
				{
					_session.PeerEndPoint = _clients[i].ToString();
					break;
				}
			}
		}

		private void ClearClients()
		{
			for (int i = 0; i < _clients.Length; i++)
			{
				_clients[i] = null;
				_clientLastRx[i] = 0f;
			}
			_hostEp = null;
			_clientTarget = null;
		}

		private ushort NextSeq()
		{
			_seq++;
			return _seq;
		}

		private static long NowMs()
		{
			return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
		}

		private static string FormatEp(IPEndPoint ep)
		{
			if (ep == null)
			{
				return "null";
			}
			IPAddress address = ep.Address;
			return ((address != null) ? address.ToString() : "") + ":" + ep.Port;
		}

		private static bool EndPointEquals(IPEndPoint a, IPEndPoint b)
		{
			if (a == null || b == null)
			{
				return false;
			}
			if (a.Port == b.Port)
			{
				return a.Address.Equals(b.Address);
			}
			return false;
		}

		private static UdpClient OpenUdp4(int port)
		{
			UdpClient udpClient = new UdpClient(AddressFamily.InterNetwork);
			udpClient.Client.ReceiveTimeout = 200;
			udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port));
			return udpClient;
		}

		private void StartRxThread()
		{
			_rxLogLeft = 8;
			RxPkt result;
			while (_inbox.TryDequeue(out result))
			{
			}
			_rxRun = true;
			_rxThread = new Thread(RxLoop);
			_rxThread.IsBackground = true;
			_rxThread.Name = "SatmLanIpRx";
			_rxThread.Start();
			_kaThread = new Thread(KeepaliveLoop);
			_kaThread.IsBackground = true;
			_kaThread.Name = "SatmLanIpKa";
			_kaThread.Start();
			Plugin.LogSrc.LogInfo((object)"[SatmLanIp] rx thread start");
		}

		private void RxLoop()
		{
			while (_rxRun)
			{
				UdpClient udp = _udp;
				if (udp == null)
				{
					break;
				}
				try
				{
					IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0);
					byte[] array = udp.Receive(ref remoteEP);
					if (array != null && array.Length != 0)
					{
						IPEndPoint remote = ((remoteEP == null) ? null : new IPEndPoint(remoteEP.Address, remoteEP.Port));
						_inbox.Enqueue(new RxPkt
						{
							Data = array,
							Remote = remote
						});
					}
				}
				catch (SocketException)
				{
					if (!_rxRun)
					{
						break;
					}
				}
				catch (ObjectDisposedException)
				{
					break;
				}
				catch (Exception ex3)
				{
					Plugin.LogSrc.LogWarning((object)("[SatmLanIp] rx thread " + ex3.GetType().Name + ": " + ex3.Message));
					if (!_rxRun)
					{
						break;
					}
				}
			}
		}

		private void KeepaliveLoop()
		{
			while (_rxRun)
			{
				try
				{
					ClientLobbyKeepaliveTick();
				}
				catch
				{
				}
				Thread.Sleep(500);
			}
		}

		private void ClientLobbyKeepaliveTick()
		{
			if (LanRoom.ShouldClientLobbyKeepalive(_session.IsHost, _session.State, _session.LocalSlot))
			{
				IPEndPoint hostEp = _hostEp;
				UdpClient udp = _udp;
				if (hostEp != null && udp != null)
				{
					ushort seq = LanRoom.PackReady(_session.LocalReady, _session.LocalSlot);
					byte[] array = LanProtocol.Encode(LanPacketType.Ready, seq, NowMs());
					udp.Send(array, array.Length, hostEp);
				}
			}
		}

		private void DrainRecv()
		{
			int num = 0;
			RxPkt result;
			while (num < 64 && _inbox.TryDequeue(out result))
			{
				_drainBuf[num++] = result;
			}
			for (int i = 0; i <= 2; i++)
			{
				for (int j = 0; j < num; j++)
				{
					RxPkt rxPkt = _drainBuf[j];
					if (rxPkt == null || rxPkt.Data == null)
					{
						continue;
					}
					int num2 = 2;
					if (rxPkt.Data.Length >= 16)
					{
						byte b = rxPkt.Data[5];
						if (b >= 1 && b <= 10)
						{
							num2 = LanProtocol.DrainPriority((LanPacketType)b);
						}
					}
					if (num2 == i)
					{
						HandlePacket(rxPkt.Data, rxPkt.Remote);
						_drainBuf[j] = null;
					}
				}
			}
			for (int k = 0; k < num; k++)
			{
				_drainBuf[k] = null;
			}
		}

		private void DisconnectSocketsOnly()
		{
			DisposeUdp();
			ClearClients();
			_awaitingEcho = false;
		}

		private void DisposeUdp()
		{
			_rxRun = false;
			UdpClient udp = _udp;
			_udp = null;
			try
			{
				udp?.Close();
			}
			catch
			{
			}
			Thread rxThread = _rxThread;
			_rxThread = null;
			if (rxThread != null && rxThread.IsAlive)
			{
				rxThread.Join(500);
			}
			Thread kaThread = _kaThread;
			_kaThread = null;
			if (kaThread != null && kaThread.IsAlive)
			{
				kaThread.Join(500);
			}
			RxPkt result;
			while (_inbox.TryDequeue(out result))
			{
			}
		}
	}
	internal static class ConflictGuard
	{
		private static readonly string[] ConflictFileNames = new string[3] { "SatmForceDirect.dll", "SatmPhotonSwap.dll", "SatmRegionForce.dll" };

		public static bool ConflictsPresent { get; private set; }

		public static string ConflictSummary { get; private set; } = "";

		public static void SelfCheck()
		{
			if (ConflictFileNames.Length != 3 || ConflictFileNames[0] != "SatmForceDirect.dll")
			{
				throw new InvalidOperationException("SatmLanIp ConflictGuard file list");
			}
		}

		public static void Refresh()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			List<string> list = new List<string>();
			try
			{
				string pluginPath = Paths.PluginPath;
				if (!string.IsNullOrEmpty(pluginPath) && Directory.Exists(pluginPath))
				{
					string[] conflictFileNames = ConflictFileNames;
					foreach (string text in conflictFileNames)
					{
						if (File.Exists(Path.Combine(pluginPath, text)))
						{
							list.Add(text);
						}
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource logSrc = Plugin.LogSrc;
				if (logSrc != null)
				{
					bool flag = default(bool);
					BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(39, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] ConflictGuard file scan: ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
					}
					logSrc.LogWarning(val);
				}
			}
			ConflictsPresent = list.Count > 0;
			ConflictSummary = ((list.Count == 0) ? "" : string.Join("; ", list));
		}

		public static bool CanActivate()
		{
			if (Plugin.Enabled)
			{
				return !ConflictsPresent;
			}
			return false;
		}
	}
	[HarmonyPatch]
	internal static class FusionCloudBypassPatches
	{
		private const int AsyncCompleted = -2;

		private static int _connectLogs;

		private static int _joinLogs;

		private static int _cloudStartLogs;

		private static int _readyLogs;

		private static bool _forceCloudReady;

		private static _ConnectToCloud_d__68 _parkedConnect;

		private static bool _pumpLogged;

		private static Task<bool> _pendingInit;

		private static AsyncOperationHandler<ShutdownReason> _pendingInitOp;

		private static NetworkRunner _pendingInitRunner;

		private static int _fromActorLogs;

		private static int _localPlayerLogs;

		private static int _createCloudSocketHits;

		private static int _nativeBindLogs;

		private static int _relayBindLogs;

		private static int _nativeRecvHits;

		private static int _nativeRecvBytes;

		private static int _nativeSendHits;

		private static int _setupEncHits;

		private static int _nativeRecvCalls;

		private static int _nativeSendCalls;

		private static int _hybridRecvCalls;

		private static int _hybridSendCalls;

		private static int _networkInitSwaps;

		private static int _connReqLogs;

		private static int _handleConnectLogs;

		private static int _allocConnLogs;

		private static int _hexDumps;

		internal static void Reset()
		{
			_connectLogs = 0;
			_joinLogs = 0;
			_cloudStartLogs = 0;
			_readyLogs = 0;
			_fromActorLogs = 0;
			_localPlayerLogs = 0;
			_createCloudSocketHits = 0;
			_nativeBindLogs = 0;
			_relayBindLogs = 0;
			_nativeRecvHits = 0;
			_nativeRecvBytes = 0;
			_nativeSendHits = 0;
			_setupEncHits = 0;
			_nativeRecvCalls = 0;
			_nativeSendCalls = 0;
			_hybridRecvCalls = 0;
			_hybridSendCalls = 0;
			_networkInitSwaps = 0;
			_connReqLogs = 0;
			_handleConnectLogs = 0;
			_allocConnLogs = 0;
			_hexDumps = 0;
			_forceCloudReady = false;
			_parkedConnect = null;
			_pumpLogged = false;
			_pendingInit = null;
			_pendingInitOp = null;
			_pendingInitRunner = null;
		}

		internal static void Pump()
		{
			if (LanMatch.AllowFusionStart)
			{
				PumpParkedConnect();
				PumpPendingInitialize();
			}
		}

		private static void PumpParkedConnect()
		{
			if (_parkedConnect == null)
			{
				return;
			}
			_ConnectToCloud_d__68 parked = _parkedConnect;
			_parkedConnect = null;
			try
			{
				CompleteVoidBuilder(parked.__t__builder, delegate(AsyncTaskMethodBuilder v)
				{
					parked.__t__builder = v;
				});
				parked.__1__state = -2;
				if (!_pumpLogged)
				{
					_pumpLogged = true;
					Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion ConnectToCloud parked→complete");
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion pump fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static void PumpPendingInitialize()
		{
			if (_pendingInit == null || _pendingInitOp == null || !((Task)_pendingInit).IsCompleted)
			{
				return;
			}
			AsyncOperationHandler<ShutdownReason> pendingInitOp = _pendingInitOp;
			NetworkRunner pendingInitRunner = _pendingInitRunner;
			Task<bool> pendingInit = _pendingInit;
			_pendingInit = null;
			_pendingInitOp = null;
			_pendingInitRunner = null;
			_forceCloudReady = false;
			try
			{
				bool flag = false;
				try
				{
					flag = pendingInit.Result;
				}
				catch (Exception ex)
				{
					Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Initialize threw " + ex.GetType().Name + ": " + ex.Message));
				}
				bool flag2 = false;
				try
				{
					if ((Object)(object)pendingInitRunner != (Object)null)
					{
						flag2 = pendingInitRunner.IsRunning;
					}
				}
				catch
				{
				}
				pendingInitOp.SetResult((ShutdownReason)(!flag));
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Initialize done ok=" + flag + " IsRunning=" + flag2 + " LocalPlayer=" + SafeLocalPlayer(pendingInitRunner) + " Mode=" + SafeMode(pendingInitRunner) + " Socket=" + SafeSocket(pendingInitRunner) + " createCloudHits=" + _createCloudSocketHits + " nativeBind=" + _nativeBindLogs + " relayBind=" + _relayBindLogs + " enc=" + SafeEncryption(pendingInitRunner) + " initSwap=" + _networkInitSwaps + " nRecv=" + _nativeRecvCalls + "/" + _nativeRecvHits + " hRecv=" + _hybridRecvCalls));
				if (flag && flag2)
				{
					EnsureSimulationMode(pendingInitRunner);
					Il2CppStructArray<byte> uniqueId = EnsurePlayerMapping(pendingInitRunner);
					TryClientConnectAfterInit(pendingInitRunner, uniqueId);
				}
			}
			catch (Exception ex2)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Initialize complete fail " + ex2.GetType().Name + ": " + ex2.Message));
				try
				{
					pendingInitOp.SetResult((ShutdownReason)1);
				}
				catch
				{
				}
			}
		}

		private static string SafeLocalPlayer(NetworkRunner runner)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if ((Object)(object)runner == (Object)null)
				{
					return "null";
				}
				PlayerRef localPlayer = runner.LocalPlayer;
				return "idx=" + ((PlayerRef)(ref localPlayer)).PlayerId + " real=" + ((PlayerRef)(ref localPlayer)).IsRealPlayer;
			}
			catch (Exception ex)
			{
				return ex.GetType().Name;
			}
		}

		private static string SafeMode(NetworkRunner runner)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Simulation val = (((Object)(object)runner != (Object)null) ? runner._simulation : null);
				return (val != null) ? ((object)val.Mode/*cast due to .constrained prefix*/).ToString() : "no-sim";
			}
			catch (Exception ex)
			{
				return ex.GetType().Name;
			}
		}

		private static string SafeSocket(NetworkRunner runner)
		{
			try
			{
				Simulation val = (((Object)(object)runner != (Object)null) ? runner._simulation : null);
				return DescribeSocket((val != null) ? val._netSocket : null);
			}
			catch (Exception ex)
			{
				return ex.GetType().Name;
			}
		}

		private unsafe static void EnsureSimulationMode(NetworkRunner runner)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: 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_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Simulation simulation = runner._simulation;
				if (simulation == null)
				{
					return;
				}
				SimulationModes val = (SimulationModes)((ResolveLanActorId() == 1) ? 2 : 4);
				SimulationModes mode = simulation.Mode;
				if (mode == val)
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Mode ok=" + ((object)(*(SimulationModes*)(&mode))/*cast due to .constrained prefix*/).ToString()));
					return;
				}
				FieldInfo fieldInfo = AccessTools.Field(typeof(Simulation), "_mode");
				if (fieldInfo == null)
				{
					Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Mode=" + ((object)(*(SimulationModes*)(&mode))/*cast due to .constrained prefix*/).ToString() + " want=" + ((object)(*(SimulationModes*)(&val))/*cast due to .constrained prefix*/).ToString() + " (no _mode field)"));
				}
				else
				{
					fieldInfo.SetValue(simulation, val);
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Mode " + ((object)(*(SimulationModes*)(&mode))/*cast due to .constrained prefix*/).ToString() + "→" + ((object)(*(SimulationModes*)(&val))/*cast due to .constrained prefix*/).ToString()));
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Mode fix fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		private static Il2CppStructArray<byte> MakeUniqueId(int actorId)
		{
			int num = Plugin.ListenPort;
			if (num < 1 || num > 65535)
			{
				num = 37241;
			}
			byte[] array = new byte[8]
			{
				83,
				65,
				84,
				77,
				(byte)actorId,
				(byte)(num & 0xFF),
				(byte)((num >> 8) & 0xFF),
				167
			};
			Il2CppStructArray<byte> val = new Il2CppStructArray<byte>((long)array.Length);
			for (int i = 0; i < array.Length; i++)
			{
				((Il2CppArrayBase<byte>)(object)val)[i] = array[i];
			}
			return val;
		}

		private static Il2CppStructArray<byte> EnsurePlayerMapping(NetworkRunner runner)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			int num = ResolveLanActorId();
			Il2CppStructArray<byte> val = MakeUniqueId(num);
			try
			{
				Simulation simulation = runner._simulation;
				if (simulation == null)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion mapping skip (no Simulation)");
					return val;
				}
				PlayerRef val2 = PlayerRef.FromIndex(num);
				simulation.RegisterUniqueIdPlayerMapping(num, val, val2);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion RegisterUniqueIdPlayerMapping actor=" + num + " player=" + ((PlayerRef)(ref val2)).PlayerId + " LocalPlayer=" + SafeLocalPlayer(runner)));
				if (num == 1)
				{
					int num2 = LanFusionStart.HostPremapPeerActorHi(((Plugin.Transport != null) ? Plugin.Transport.Session : null)?.MaxPlayers ?? 3);
					int num3 = 0;
					for (int i = 2; i <= num2; i++)
					{
						Il2CppStructArray<byte> val3 = MakeUniqueId(i);
						simulation.RegisterUniqueIdPlayerMapping(i, val3, PlayerRef.FromIndex(i));
						num3++;
					}
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion peer premap actors=2.." + num2 + " count=" + num3 + " maxPlayers=" + num2));
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion mapping fail " + ex.GetType().Name + ": " + ex.Message));
			}
			return val;
		}

		private static void TryClientConnectAfterInit(NetworkRunner runner, Il2CppStructArray<byte> uniqueId)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null);
			if (lanSession == null || lanSession.IsHost)
			{
				return;
			}
			try
			{
				string text = FusionLanPatches.ClientJoinIp();
				if (text.Length == 0)
				{
					Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion client Connect skip (no JoinAddress)");
					return;
				}
				string text2 = LanFusionStart.ResolveClientConnectIp(text);
				ushort num = LanFusionStart.HostBindPort(Plugin.JoinPort);
				NetAddress val = NetAddress.CreateFromIpPort(text2, num);
				runner.Connect(val, (Il2CppStructArray<byte>)null, uniqueId ?? MakeUniqueId(ResolveLanActorId()));
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion client Connect " + text2 + ":" + num + " +UniqueId" + ((text2 != text) ? (" via=" + text) : "")));
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion client Connect fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static void LogPatchStatus(Harmony harmony)
		{
			int num = 0;
			foreach (MethodBase patchedMethod in harmony.GetPatchedMethods())
			{
				if (patchedMethod.Name == "MoveNext" && patchedMethod.DeclaringType != null && (patchedMethod.DeclaringType.Name.Contains("ConnectToCloud") || patchedMethod.DeclaringType.Name.Contains("StartGameModeCloud") || patchedMethod.DeclaringType.Name.Contains("_Join_d__")))
				{
					num++;
				}
			}
			Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Harmony FusionCloudBypass MoveNext patches count~=" + num));
		}

		internal static void ApplySimulationLocalPlayerPatches(Harmony harmony)
		{
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			string[] array = new string[2] { "Fusion.Simulation+Server", "Fusion.Simulation+Client" };
			for (int i = 0; i < array.Length; i++)
			{
				Type type = AccessTools.TypeByName(array[i]);
				if (type == null)
				{
					Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion type missing " + array[i]));
					continue;
				}
				MethodInfo methodInfo = AccessTools.PropertyGetter(type, "LocalPlayer");
				if (methodInfo == null)
				{
					Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion LocalPlayer getter missing " + array[i]));
					continue;
				}
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(FusionCloudBypassPatches), "SimLocalPlayerPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion patched " + array[i] + ".LocalPlayer"));
			}
		}

		private static void SimLocalPlayerPostfix(ref PlayerRef __result)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if (LanMatch.AllowFusionStart && !((PlayerRef)(ref __result)).IsRealPlayer)
			{
				__result = PlayerRef.FromIndex(ResolveLanActorId());
				_localPlayerLogs++;
				if (_localPlayerLogs <= 8)
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Sim.LocalPlayer→FromIndex(" + ResolveLanActorId() + ")"));
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(/*Could not decode attribute arguments.*/)]
		private static void LocalPlayerPostfix(ref PlayerRef __result)
		{
			SimLocalPlayerPostfix(ref __result);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(NetworkObject), "AssignInputAuthority")]
		private static void AssignInputAuthorityPrefix(ref PlayerRef player)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			if (LanMatch.AllowFusionStart && !((PlayerRef)(ref player)).IsRealPlayer)
			{
				player = PlayerRef.FromIndex(ResolveLanActorId());
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(NetAddress), "FromActorId")]
		private static void FromActorIdPrefix(ref int actorId)
		{
			if (LanMatch.AllowFusionStart && _forceCloudReady && actorId < 0)
			{
				int num = ResolveLanActorId();
				_fromActorLogs++;
				if (_fromActorLogs <= 6)
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion FromActorId " + actorId + "→" + num + " (init-only)"));
				}
				actorId = num;
			}
		}

		private static string SafeEncryption(NetworkRunner runner)
		{
			try
			{
				NetworkProjectConfig val = (((Object)(object)runner != (Object)null) ? runner.Config : null);
				EncryptionConfig val2 = ((val != null) ? val.EncryptionConfig : null);
				if (val2 == null)
				{
					return "null";
				}
				return val2.EnableEncryption ? "on" : "off";
			}
			catch
			{
				return "?";
			}
		}

		private static void TryDisableEncryption(NetworkRunner runner)
		{
			try
			{
				NetworkProjectConfig val = (((Object)(object)runner != (Object)null) ? runner.Config : null);
				EncryptionConfig val2 = ((val != null) ? val.EncryptionConfig : null);
				if (val2 != null && val2.EnableEncryption)
				{
					val2.EnableEncryption = false;
					Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion EncryptionConfig.EnableEncryption→false");
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion enc disable fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(FusionNetworkManager), "OnConnectFailed")]
		private unsafe static void OnConnectFailedPostfix(NetAddress remoteAddress, NetConnectFailedReason reason)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (LanMatch.AllowFusionStart)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion OnConnectFailed " + ((object)(*(NetConnectFailedReason*)(&reason))/*cast due to .constrained prefix*/).ToString() + " remote=" + SafeAddrLog(remoteAddress) + " nRecv=" + _nativeRecvCalls + "/" + _nativeRecvHits + " nSend=" + _nativeSendCalls + "/" + _nativeSendHits + " hRecv=" + _hybridRecvCalls + " hSend=" + _hybridSendCalls + " initSwap=" + _networkInitSwaps + " handleConnect=" + _handleConnectLogs + " alloc=" + _allocConnLogs + " connReq=" + _connReqLogs));
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(FusionNetworkManager), "OnPlayerJoined")]
		private static bool OnPlayerJoinedPrefix(NetworkRunner runner, PlayerRef player)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (!LanMatch.AllowFusionStart)
			{
				return true;
			}
			try
			{
				Scene activeScene = SceneManager.GetActiveScene();
				string text = ((Scene)(ref activeScene)).name ?? "";
				if (text == "MainMenu" || text == "Lobby")
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion skip OnPlayerJoined scene=" + text + " player=" + ((PlayerRef)(ref player)).PlayerId + " (avoid menu kick)"));
					return false;
				}
			}
			catch
			{
			}
			return true;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(FusionNetworkManager), "OnConnectRequest")]
		private static void OnConnectRequestPostfix(NetworkRunner runner, ConnectRequest request, Il2CppStructArray<byte> token)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			if (!LanMatch.AllowFusionStart || request == null)
			{
				return;
			}
			try
			{
				if (Plugin.VerboseNetworkLog)
				{
					Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion OnConnectRequest remote=" + ((object)request.RemoteAddress/*cast due to .constrained prefix*/).ToString() + " result=" + (request.Result.HasValue ? ((object)request.Result.Value/*cast due to .constrained prefix*/).ToString() : "unset")));
				}
				if (!request.Result.HasValue)
				{
					request.Accept();
					if (Plugin.VerboseNetworkLog)
					{
						Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion OnConnectRequest Accept()");
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion OnConnectRequest fail " + ex.GetType().Name + ": " + ex.Message));
			}
		}

		internal static void ApplySocketPatches(Harmony harmony)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Expected O, but got Unknown
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Expected O, but got Unknown
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0177: Expected O, but got Unknown
			//IL_0177: Expected O, but got Unknown
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ef: Expected O, but got Unknown
			//IL_01ef: Expected O, but got Unknown
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Expected O, but got Unknown
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a6: Expected O, but got Unknown
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f1: Expected O, but got Unknown
			//IL_0359: Unknown result type (might be due to invalid IL or missing references)
			//IL_0366: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(NetworkRunner), "CreateCloudSocket", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion CreateCloudSocket method missing");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FusionCloudBypassPatches), "CreateCloudSocketPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fu