Decompiled source of SatmLanIp v1.0.2
SatmLanIp.dll
Decompiled 5 hours ago
The result has been truncated due to the large size, download it to view full contents!
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