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 ListIPv4() { List list = ListAdvertise(); List list2 = new List(list.Count); for (int i = 0; i < list.Count; i++) { list2.Add(list[i].Ip); } return list2; } internal static List ListAdvertise() { List list = new List(); List list2 = new List(); List list3 = new List(); 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 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 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 { 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 DedupeRows(params List[] buckets) { HashSet hashSet = new HashSet(); List list = new List(); foreach (List 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(); 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(); 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(); 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 saveFileExistsHolders = sfm.saveFileExistsHolders; if (saveFileExistsHolders == null) { return -1; } for (int i = 0; i < ((Il2CppArrayBase)(object)saveFileExistsHolders).Length; i++) { GameObject val = ((Il2CppArrayBase)(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() where T : Object { try { return Object.FindFirstObjectByType(); } catch { return Object.FindObjectOfType(); } } } 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 _inbox = new ConcurrentQueue(); 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 list = new List(); 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(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(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 _pendingInit; private static AsyncOperationHandler _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 pendingInitOp = _pendingInitOp; NetworkRunner pendingInitRunner = _pendingInitRunner; Task 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 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 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 val = new Il2CppStructArray((long)array.Length); for (int i = 0; i < array.Length; i++) { ((Il2CppArrayBase)(object)val)[i] = array[i]; } return val; } private static Il2CppStructArray 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 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 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 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)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 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_fusion CreateCloudSocket patched (AccessTools)"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(Simulation), "NetworkInit", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(FusionCloudBypassPatches), "NetworkInitPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion Simulation.NetworkInit→Native patched"); } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion Simulation.NetworkInit missing"); } MethodInfo methodInfo3 = AccessTools.Method(typeof(NetSocketNative), "Bind", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(FusionCloudBypassPatches), "NativeBindPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketNative.Bind probed"); } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion NetSocketNative.Bind missing"); } MethodInfo methodInfo4 = AccessTools.Method(typeof(NetSocketNative), "Receive", (Type[])null, (Type[])null); if (methodInfo4 != null) { harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(FusionCloudBypassPatches), "NativeReceivePrefix", (Type[])null), new HarmonyMethod(typeof(FusionCloudBypassPatches), "NativeReceivePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketNative.Receive probed"); } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion NetSocketNative.Receive missing"); } MethodInfo methodInfo5 = AccessTools.Method(typeof(NetSocketNative), "Send", (Type[])null, (Type[])null); if (methodInfo5 != null) { harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(FusionCloudBypassPatches), "NativeSendPrefix", (Type[])null), new HarmonyMethod(typeof(FusionCloudBypassPatches), "NativeSendPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketNative.Send probed"); } MethodInfo methodInfo6 = AccessTools.Method(typeof(NetSocketNative), "SetupEncryption", (Type[])null, (Type[])null); if (methodInfo6 != null) { harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(FusionCloudBypassPatches), "NativeSetupEncPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketNative.SetupEncryption probed"); } Type type = AccessTools.TypeByName("Fusion.Sockets.NetSocketHybrid"); if (type != null) { MethodInfo methodInfo7 = AccessTools.Method(type, "Receive", (Type[])null, (Type[])null); if (methodInfo7 != null) { harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(typeof(FusionCloudBypassPatches), "HybridReceivePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketHybrid.Receive probed"); } MethodInfo methodInfo8 = AccessTools.Method(type, "Send", (Type[])null, (Type[])null); if (methodInfo8 != null) { harmony.Patch((MethodBase)methodInfo8, new HarmonyMethod(typeof(FusionCloudBypassPatches), "HybridSendPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketHybrid.Send probed"); } } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion NetSocketHybrid type missing"); } Type type2 = AccessTools.TypeByName("Fusion.Sockets.NetSocketRelay"); MethodInfo methodInfo9 = ((type2 != null) ? AccessTools.Method(type2, "Bind", (Type[])null, (Type[])null) : null); if (methodInfo9 != null) { harmony.Patch((MethodBase)methodInfo9, (HarmonyMethod)null, new HarmonyMethod(typeof(FusionCloudBypassPatches), "RelayBindPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetSocketRelay.Bind probed"); } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion NetSocketRelay.Bind missing"); } ApplyConnectionRequestPatches(harmony); ApplyNetPeerGroupProbes(harmony); } private static void ApplyConnectionRequestPatches(Harmony harmony) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown int num = 0; foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(typeof(Simulation))) { if (!(declaredMethod == null) && declaredMethod.Name != null && declaredMethod.Name.IndexOf("OnConnectionRequest", StringComparison.Ordinal) >= 0) { harmony.Patch((MethodBase)declaredMethod, new HarmonyMethod(typeof(FusionCloudBypassPatches), "SimOnConnectionRequestPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion patched Simulation." + declaredMethod.Name)); } } foreach (MethodInfo declaredMethod2 in AccessTools.GetDeclaredMethods(typeof(NetworkRunner))) { if (!(declaredMethod2 == null) && declaredMethod2.Name != null && declaredMethod2.Name.IndexOf("OnConnectionRequest", StringComparison.Ordinal) >= 0) { harmony.Patch((MethodBase)declaredMethod2, new HarmonyMethod(typeof(FusionCloudBypassPatches), "RunnerOnConnectionRequestPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion patched NetworkRunner." + declaredMethod2.Name)); } } if (num == 0) { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion OnConnectionRequest methods missing"); } } private static void ApplyNetPeerGroupProbes(Harmony harmony) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Expected O, but got Unknown Type type = AccessTools.TypeByName("Fusion.Sockets.NetPeerGroup"); if (type == null) { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion NetPeerGroup type missing"); return; } MethodInfo methodInfo = AccessTools.Method(type, "HandleCommandConnect", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FusionCloudBypassPatches), "HandleCommandConnectPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion HandleCommandConnect probed"); } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion HandleCommandConnect missing"); } MethodInfo methodInfo2 = AccessTools.Method(type, "AllocateConnection", (Type[])null, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(FusionCloudBypassPatches), "AllocateConnectionPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion AllocateConnection probed"); } else { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion AllocateConnection missing"); } MethodInfo methodInfo3 = AccessTools.Method(type, "HandlePacketUnconnected", (Type[])null, (Type[])null); if (methodInfo3 != null) { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(FusionCloudBypassPatches), "HandlePacketUnconnectedPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion HandlePacketUnconnected probed"); } } private static bool SimOnConnectionRequestPrefix(Simulation __instance, Il2CppStructArray uniqueid, ref OnConnectionRequestReply __result) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) if (!LanMatch.AllowFusionStart) { return true; } try { if (__instance != null && uniqueid != null && ((Il2CppArrayBase)(object)uniqueid).Length == 8) { int num = ((Il2CppArrayBase)(object)uniqueid)[4]; if (num < 1 || num > 7) { num = 2; } __instance.RegisterUniqueIdPlayerMapping(num, uniqueid, PlayerRef.FromIndex(num)); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion OnConnectionRequest map actor=" + num + " uid0=" + ((Il2CppArrayBase)(object)uniqueid)[0] + ((Il2CppArrayBase)(object)uniqueid)[1] + ((Il2CppArrayBase)(object)uniqueid)[2] + ((Il2CppArrayBase)(object)uniqueid)[3])); } } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion OnConnectionRequest map fail " + ex.GetType().Name + ": " + ex.Message)); } __result = (OnConnectionRequestReply)0; _connReqLogs++; if (_connReqLogs <= 8) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Sim.OnConnectionRequest→Ok #" + _connReqLogs)); } return false; } private static bool RunnerOnConnectionRequestPrefix(ref OnConnectionRequestReply __result) { if (!LanMatch.AllowFusionStart) { return true; } __result = (OnConnectionRequestReply)0; Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion Runner.OnConnectionRequest→Ok"); return false; } private static void HandleCommandConnectPrefix() { if (LanMatch.AllowFusionStart) { _handleConnectLogs++; if (_handleConnectLogs <= 12) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion HandleCommandConnect #" + _handleConnectLogs)); } } } private static void AllocateConnectionPostfix() { if (LanMatch.AllowFusionStart) { _allocConnLogs++; if (_allocConnLogs <= 8) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion AllocateConnection #" + _allocConnLogs)); } } } private static void HandlePacketUnconnectedPrefix() { if (LanMatch.AllowFusionStart && _handleConnectLogs + _allocConnLogs < 4) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion HandlePacketUnconnected"); } } private static void NetworkInitPrefix(ref INetSocket socket) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!LanMatch.AllowFusionStart) { return; } try { string text = DescribeSocket(socket); if (socket != null && ((Il2CppObjectBase)socket).TryCast() != null) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion NetworkInit already Native"); return; } INetSocket val = ((Il2CppObjectBase)new NetSocketNative()).TryCast(); if (val == null) { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion NetworkInit Native cast fail"); return; } socket = val; _networkInitSwaps++; Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion NetworkInit " + text + "→Native swap=" + _networkInitSwaps)); } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion NetworkInit swap fail " + ex.GetType().Name + ": " + ex.Message)); } } private static string DescribeSocket(INetSocket sock) { if (sock == null) { return "null"; } try { if (((Il2CppObjectBase)sock).TryCast() != null) { return "Native"; } string text = ((object)sock).ToString() ?? ""; if (text.IndexOf("Hybrid", StringComparison.OrdinalIgnoreCase) >= 0) { return "Hybrid"; } if (text.IndexOf("Relay", StringComparison.OrdinalIgnoreCase) >= 0) { return "Relay"; } string text2 = ((object)sock).GetType().Name ?? ""; if (text2.IndexOf("Hybrid", StringComparison.OrdinalIgnoreCase) >= 0) { return "Hybrid"; } if (text2.IndexOf("Relay", StringComparison.OrdinalIgnoreCase) >= 0) { return "Relay"; } return (text2.Length > 0) ? text2 : text; } catch { return "?"; } } private static bool CreateCloudSocketPrefix(ref INetSocket __result) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown if (!LanMatch.AllowFusionStart) { return true; } _createCloudSocketHits++; try { NetSocketNative val = new NetSocketNative(); __result = ((Il2CppObjectBase)val).TryCast(); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion CreateCloudSocket→NetSocketNative ok=" + (__result != null) + " hit=" + _createCloudSocketHits)); return false; } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion CreateCloudSocket native fail " + ex.GetType().Name + ": " + ex.Message)); return true; } } private static void NativeBindPostfix(NetAddress __result) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (LanMatch.AllowFusionStart) { _nativeBindLogs++; if (Plugin.VerboseNetworkLog && _nativeBindLogs <= 4) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion NetSocketNative.Bind → " + SafeAddrLog(__result))); } } } private static void NativeReceivePrefix() { if (LanMatch.AllowFusionStart && Plugin.VerboseNetworkLog) { _nativeRecvCalls++; if (_nativeRecvCalls <= 8 || _nativeRecvCalls % 200 == 0) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Native.Receive call=" + _nativeRecvCalls)); } } } private unsafe static void NativeReceivePostfix(int __result, IntPtr buffer) { if (!LanMatch.AllowFusionStart || __result <= 0) { return; } _nativeRecvHits++; _nativeRecvBytes += __result; if (!Plugin.VerboseNetworkLog) { return; } if (_hexDumps < 4 && __result == 152 && buffer != IntPtr.Zero) { _hexDumps++; try { StringBuilder stringBuilder = new StringBuilder(48); byte* ptr = (byte*)buffer.ToPointer(); for (int i = 0; i < 8; i++) { stringBuilder.Append(ptr[i].ToString("x2")); } Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Native.Receive Connect152 hdr=" + stringBuilder?.ToString() + " (want type=05 cmd=01)")); } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion hex dump fail " + ex.GetType().Name)); } } if (_nativeRecvHits <= 12 || _nativeRecvHits % 40 == 0) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Native.Receive bytes=" + __result + " hits=" + _nativeRecvHits + " total=" + _nativeRecvBytes + " handleConnect=" + _handleConnectLogs + " alloc=" + _allocConnLogs + " connReq=" + _connReqLogs)); } } private static void NativeSendPrefix() { if (LanMatch.AllowFusionStart && Plugin.VerboseNetworkLog) { _nativeSendCalls++; if (_nativeSendCalls <= 8 || _nativeSendCalls % 200 == 0) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Native.Send call=" + _nativeSendCalls)); } } } private static void NativeSendPostfix(int __result) { if (LanMatch.AllowFusionStart && __result > 0 && Plugin.VerboseNetworkLog) { _nativeSendHits++; if (_nativeSendHits <= 12 || _nativeSendHits % 40 == 0) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Native.Send bytes=" + __result + " hits=" + _nativeSendHits)); } } } private static void HybridReceivePrefix() { if (LanMatch.AllowFusionStart && Plugin.VerboseNetworkLog) { _hybridRecvCalls++; if (_hybridRecvCalls <= 8 || _hybridRecvCalls % 200 == 0) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Hybrid.Receive call=" + _hybridRecvCalls)); } } } private static void HybridSendPrefix() { if (LanMatch.AllowFusionStart && Plugin.VerboseNetworkLog) { _hybridSendCalls++; if (_hybridSendCalls <= 8 || _hybridSendCalls % 200 == 0) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Hybrid.Send call=" + _hybridSendCalls)); } } } private static void NativeSetupEncPostfix() { if (LanMatch.AllowFusionStart) { _setupEncHits++; if (Plugin.VerboseNetworkLog) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Native.SetupEncryption hit=" + _setupEncHits)); } } } private static void RelayBindPostfix(NetAddress __result) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (LanMatch.AllowFusionStart) { _relayBindLogs++; if (Plugin.VerboseNetworkLog && _relayBindLogs <= 4) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion NetSocketRelay.Bind → " + SafeAddrLog(__result))); } } } private unsafe static string SafeAddrLog(NetAddress a) { try { return ((object)(*(NetAddress*)(&a))/*cast due to .constrained prefix*/).ToString(); } catch { return "?"; } } [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static bool IsCloudReadyPrefix(ref bool __result) { if (!_forceCloudReady) { return true; } __result = true; _readyLogs++; if (_readyLogs <= 4) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion IsCloudReady=true (init-scoped)"); } return false; } [HarmonyPrefix] [HarmonyPatch(/*Could not decode attribute arguments.*/)] private static bool RunnerIsCloudReadyPrefix(ref bool __result) { if (!_forceCloudReady) { return true; } __result = true; return false; } [HarmonyPrefix] [HarmonyPatch(typeof(_ConnectToCloud_d__68), "MoveNext")] private static bool ConnectToCloudMoveNextPrefix(_ConnectToCloud_d__68 __instance) { if (!LanMatch.AllowFusionStart) { return true; } if (__instance.__1__state == -2) { return true; } _parkedConnect = __instance; _connectLogs++; if (_connectLogs <= 4) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion ConnectToCloud park (force yield)"); } return false; } [HarmonyPrefix] [HarmonyPatch(typeof(_Join_d__84), "MoveNext")] private static bool JoinMoveNextPrefix(_Join_d__84 __instance) { if (!LanMatch.AllowFusionStart) { return true; } if (__instance.__1__state == -2) { return true; } PumpParkedConnect(); KickInitializeAfterFakeJoin(__instance.__4__this); CompleteVoidBuilder(__instance.__t__builder, delegate(AsyncTaskMethodBuilder v) { __instance.__t__builder = v; }); __instance.__1__state = -2; _joinLogs++; if (_joinLogs <= 4) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion Join → Initialize kicked"); } return false; } [HarmonyPrefix] [HarmonyPatch(typeof(_StartGameModeCloud_d__436), "MoveNext")] private static void StartGameModeCloudMoveNextPrefix(_StartGameModeCloud_d__436 __instance) { if (LanMatch.AllowFusionStart && __instance.__1__state == 0 && TryInjectCompletedEnterRoomAwaiter(__instance)) { __instance.__1__state = 1; _cloudStartLogs++; if (_cloudStartLogs <= 4) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] lan_fusion StartGameModeCloud 0→1 (fake EnterRoom awaiter)"); } } } private static void KickInitializeAfterFakeJoin(CloudServices cloud) { if (cloud == null) { return; } NetworkRunner runner = cloud._runner; if ((Object)(object)runner == (Object)null) { return; } try { AsyncOperationHandler val = runner._startGameOperation; if (val == null) { val = (runner._startGameOperation = new AsyncOperationHandler((CancellationToken)null, 30f, (string)null)); } CloudServicesMetadata metadata = cloud._metadata; if (metadata == null) { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] lan_fusion Initialize skip (no metadata)"); val.SetResult((ShutdownReason)1); return; } NetworkRunnerInitializeArgs runnerInitializeArgs = metadata.RunnerInitializeArgs; TryDisableEncryption(runner); _pendingInitOp = val; _pendingInitRunner = runner; _forceCloudReady = true; _pendingInit = runner.Initialize(runnerInitializeArgs); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion Initialize() started lanActor=" + ResolveLanActorId() + " createCloudHits=" + _createCloudSocketHits)); } catch (Exception ex) { _forceCloudReady = false; Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion Initialize kick fail " + ex.GetType().Name + ": " + ex.Message)); try { if (runner._startGameOperation != null) { runner._startGameOperation.SetResult((ShutdownReason)1); } } catch { } } } internal static int ResolveLanActorId() { LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null); if (lanSession == null) { return 1; } return LanFusionStart.ResolveLanActorId(lanSession.IsHost, lanSession.LocalSlot); } private static void EnsureClientInitAddress(NetworkRunnerInitializeArgs args) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null); if (args != null && lanSession != null && !lanSession.IsHost) { string text = FusionLanPatches.ClientJoinIp(); if (text.Length != 0) { ushort num = LanFusionStart.HostBindPort(Plugin.JoinPort); NetAddress net = NetAddress.CreateFromIpPort(text, num); FusionLanPatches.WriteInitArgsAddress(args, isPublic: false, net); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_fusion client init Address " + text + ":" + num)); } } } private static bool TryInjectCompletedEnterRoomAwaiter(_StartGameModeCloud_d__436 sm) { try { Task val = Task.FromResult((short)0); sm.__u__2 = val.GetAwaiter(); return true; } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion inject EnterRoom awaiter fail " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static void CompleteVoidBuilder(AsyncTaskMethodBuilder builder, Action writeBack) { try { builder.SetResult(); writeBack(builder); } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] lan_fusion SetResult fail " + ex.GetType().Name + ": " + ex.Message)); } } internal static void SelfCheck() { //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_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) Task val = Task.FromResult((short)0); if (val == null || !((Task)val).IsCompleted) { throw new InvalidOperationException("SatmLanIp FusionCloudBypass Task.FromResult self-check failed"); } NetAddress val2 = NetAddress.Any((ushort)1); if (((NetAddress)(ref val2)).ActorId >= 0) { throw new InvalidOperationException("SatmLanIp expected Any() ActorId unset for Direct"); } NetAddress val3 = NetAddress.FromActorId(1); if (((NetAddress)(ref val3)).ActorId != 1 || !((NetAddress)(ref val3)).IsRelayAddr) { throw new InvalidOperationException("SatmLanIp FromActorId(1) self-check failed"); } PlayerRef val4 = PlayerRef.FromIndex(1); if (!((PlayerRef)(ref val4)).IsRealPlayer) { throw new InvalidOperationException("SatmLanIp PlayerRef.FromIndex(1) not IsRealPlayer"); } Il2CppStructArray val5 = MakeUniqueId(1); if (val5 == null || ((Il2CppArrayBase)(object)val5).Length != 8) { throw new InvalidOperationException("SatmLanIp UniqueId must be 8 bytes (ulong)"); } } } [HarmonyPatch] internal static class FusionLanPatches { private static FieldInfo _cpaFieldInfo; private static FieldInfo _addrFieldInfo; private static FieldInfo _nullableHasValueFieldInfo; private static FieldInfo _nullableValueFieldInfo; private static int _objectHeader = -1; private static int _cpaOffset = -1; private static int _addrOffset = -1; private static int _initAddrOffset = -1; private static int _initPubOffset = -1; private static int _hasValueRel = -1; private static int _valueRel = -1; private static bool _loggedLayout; private static int _connectLogs; private static FieldInfo _initAddrFieldInfo; private static FieldInfo _initPubFieldInfo; private static bool _menuLoadSent; [HarmonyPrefix] [HarmonyPatch(typeof(NetworkRunner), "StartGame", new Type[] { typeof(StartGameArgs) })] private static void StartGamePrefix(StartGameArgs args) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Invalid comparison between Unknown and I4 //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Invalid comparison between Unknown and I4 //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Invalid comparison between Unknown and I4 //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Invalid comparison between Unknown and I4 if (!LanMatch.AllowFusionStart || args == null) { return; } _menuLoadSent = false; try { LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null); bool flag = lanSession?.IsHost ?? false; if ((int)args.GameMode == 1) { args.GameMode = (GameMode)(flag ? 4 : 5); args.SessionName = LanMatch.SessionName; Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_rewrite Single->" + ((object)args.GameMode/*cast due to .constrained prefix*/).ToString() + " session=" + LanMatch.SessionName)); } args.DisableNATPunchthrough = true; int num = ((lanSession != null) ? LanRoom.ClampMax(lanSession.MaxPlayers) : 3); args.PlayerCount = new Nullable(num); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_PlayerCount=" + num)); ushort num2 = LanFusionStart.HostBindPort(Plugin.ListenPort); if (LanFusionStart.ShouldBindHostAddress(flag) && ((int)args.GameMode == 4 || (int)args.GameMode == 3 || (int)args.GameMode == 6)) { NetAddress net = NetAddress.Any(num2); string text = WriteNullableNetAddress(args, isCustomPublic: false, net); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_bind :" + num2 + " " + text)); string text2 = HostLanIp(); if (text2.Length > 0) { NetAddress net2 = NetAddress.CreateFromIpPort(text2, num2); string text3 = WriteNullableNetAddress(args, isCustomPublic: true, net2); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_cpa " + text2 + ":" + num2 + " " + text3)); } } } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] fusion_StartGame fail " + ex.GetType().Name + ": " + ex.Message)); } } [HarmonyPrefix] [HarmonyPatch(typeof(NetworkRunner), "Connect", new Type[] { typeof(NetAddress), typeof(Il2CppStructArray), typeof(Il2CppStructArray) })] private static void ConnectPrefix(ref NetAddress address) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.IsActive) { return; } LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null); if (lanSession == null || lanSession.IsHost) { return; } string text = ClientHostIp(); if (text.Length != 0) { string text2 = SafeAddr(address); string text3 = LanFusionStart.ResolveClientConnectIp(text); ushort num = LanFusionStart.HostBindPort(Plugin.JoinPort); address = NetAddress.CreateFromIpPort(text3, num); _connectLogs++; if (_connectLogs <= 8 || _connectLogs % 20 == 0) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_connect " + text2 + " -> " + text3 + ":" + num + ((text3 != text) ? " (loopback same-PC)" : ""))); } } } internal static bool ShouldForceLanFusionShutdown(bool enabled, bool matchActive, LanState state) { if (!enabled || !matchActive) { return false; } if (state != LanState.Connected && state != LanState.Listen) { return state == LanState.Drop; } return true; } private static bool ShouldForceLanFusionShutdown() { if (!Plugin.Enabled) { return false; } LanSession lanSession = ((Plugin.Transport != null) ? Plugin.Transport.Session : null); if (lanSession == null) { return false; } return ShouldForceLanFusionShutdown(enabled: true, lanSession.MatchActive, lanSession.State); } internal static bool ShouldLoadMenuBeforeDetach(string scene, bool matchActive) { if (!matchActive || string.IsNullOrEmpty(scene)) { return false; } return !LanMatch.IsMenuSceneName(scene); } private static void BeginLanSessionExit(string via) { if (ShouldForceLanFusionShutdown()) { Plugin.Transport?.NotifyMatchLeaving(); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] lan_session_exit via=" + via)); LoadMenuNow(via); } } internal static void LoadMenuNow(string via) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (_menuLoadSent) { return; } string text; try { Scene activeScene = SceneManager.GetActiveScene(); text = ((Scene)(ref activeScene)).name ?? ""; } catch { return; } if (LanMatch.IsMenuSceneName(text)) { return; } _menuLoadSent = true; try { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] load MainMenu via=" + via + " scene=" + text)); SceneManager.LoadScene("MainMenu"); } catch (Exception ex) { Plugin.LogSrc.LogWarning((object)("[SatmLanIp] load MainMenu fail " + ex.GetType().Name + ": " + ex.Message)); _menuLoadSent = false; } } [HarmonyPrefix] [HarmonyPatch(typeof(NetworkRunner), "Shutdown", new Type[] { typeof(bool), typeof(ShutdownReason), typeof(bool) })] private static void ShutdownPrefix(ref bool forceShutdownProcedure) { if (ShouldForceLanFusionShutdown()) { if (!forceShutdownProcedure) { forceShutdownProcedure = true; Plugin.LogSrc.LogInfo((object)"[SatmLanIp] fusion_shutdown force=true (LAN match)"); } BeginLanSessionExit("Shutdown"); LoadMenuNow("Shutdown"); } } internal static void ApplyLeavePatches(Harmony harmony) { if (harmony != null) { int num = PatchAllNamed(harmony, typeof(FusionNetworkManager), "LeaveGame", "LeaveGamePrefix"); num += PatchAllNamed(harmony, typeof(PlatformManager_Steam), "HandleEndSessionReturn", "EndSessionPrefix"); Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_leave patches count~=" + num)); } } private static int PatchAllNamed(Harmony harmony, Type type, string name, string prefix) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown if (type == null) { return 0; } int num = 0; MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!(methodInfo == null) && !(methodInfo.Name != name)) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FusionLanPatches), prefix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } } return num; } private static void LeaveGamePrefix() { BeginLanSessionExit("LeaveGame"); } private static void EndSessionPrefix() { BeginLanSessionExit("HandleEndSessionReturn"); } internal static void LogPatchStatus(Harmony harmony) { int num = 0; foreach (MethodBase patchedMethod in harmony.GetPatchedMethods()) { if (patchedMethod.Name == "StartGame" || patchedMethod.Name == "Connect" || patchedMethod.Name == "Shutdown" || patchedMethod.Name == "LeaveGame" || patchedMethod.Name == "HandleEndSessionReturn") { num++; } } Plugin.LogSrc.LogInfo((object)("[SatmLanIp] Harmony FusionLan patches StartGame/Connect/Shutdown/Leave count~=" + num)); } internal static void SelfCheck() { if (!ShouldForceLanFusionShutdown(enabled: true, matchActive: true, LanState.Connected) || !ShouldForceLanFusionShutdown(enabled: true, matchActive: true, LanState.Listen) || !ShouldForceLanFusionShutdown(enabled: true, matchActive: true, LanState.Drop)) { throw new InvalidOperationException("SatmLanIp force shutdown during LAN match"); } if (ShouldForceLanFusionShutdown(enabled: false, matchActive: true, LanState.Connected) || ShouldForceLanFusionShutdown(enabled: true, matchActive: false, LanState.Connected) || ShouldForceLanFusionShutdown(enabled: true, matchActive: true, LanState.Idle) || ShouldForceLanFusionShutdown(enabled: true, matchActive: true, LanState.Connecting)) { throw new InvalidOperationException("SatmLanIp force shutdown must stay LAN-match only"); } if (!ShouldLoadMenuBeforeDetach("Game", matchActive: true) || ShouldLoadMenuBeforeDetach("MainMenu", matchActive: true) || ShouldLoadMenuBeforeDetach("Game", matchActive: false) || ShouldLoadMenuBeforeDetach("", matchActive: true)) { throw new InvalidOperationException("SatmLanIp load menu before detach gate"); } } internal static string ClientJoinIp() { return ClientHostIp(); } internal unsafe static void WriteInitArgsAddress(NetworkRunnerInitializeArgs args, bool isPublic, NetAddress net) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (args == null) { return; } EnsureInitLayout(); EnsureLayout(net); int num = (isPublic ? _initPubOffset : _initAddrOffset); if (num < 0) { return; } byte* num2 = (byte*)(void*)IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase)(object)args) + num; num2[_hasValueRel] = 1; Unsafe.Write(num2 + _valueRel, net); try { Nullable val = BuildBoxedNullable(net); if (isPublic) { args.PublicAddress = val; } else { args.Address = val; } } catch { } } private static void EnsureInitLayout() { if (_initAddrOffset < 0 || _initPubOffset < 0) { if ((object)_initAddrFieldInfo == null) { _initAddrFieldInfo = AccessTools.Field(typeof(NetworkRunnerInitializeArgs), "NativeFieldInfoPtr_Address") ?? throw new MissingFieldException(typeof(NetworkRunnerInitializeArgs).FullName, "NativeFieldInfoPtr_Address"); } if ((object)_initPubFieldInfo == null) { _initPubFieldInfo = AccessTools.Field(typeof(NetworkRunnerInitializeArgs), "NativeFieldInfoPtr_PublicAddress") ?? throw new MissingFieldException(typeof(NetworkRunnerInitializeArgs).FullName, "NativeFieldInfoPtr_PublicAddress"); } _initAddrOffset = (int)IL2CPP.il2cpp_field_get_offset((IntPtr)_initAddrFieldInfo.GetValue(null)); _initPubOffset = (int)IL2CPP.il2cpp_field_get_offset((IntPtr)_initPubFieldInfo.GetValue(null)); } } private static string HostLanIp() { List list = LanLocalIp.ListIPv4(); if (list.Count <= 0) { return ""; } return list[0]; } private static string ClientHostIp() { string text = (Plugin.JoinAddress ?? "").Trim(); int num = text.IndexOf(':'); if (num > 0) { text = text.Substring(0, num); } return text; } private unsafe static string SafeAddr(NetAddress addr) { try { return ((object)(*(NetAddress*)(&addr))/*cast due to .constrained prefix*/).ToString(); } catch { return "(addr)"; } } private unsafe static string WriteNullableNetAddress(StartGameArgs args, bool isCustomPublic, NetAddress net) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) EnsureLayout(net); IntPtr intPtr = IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase)(object)args); int num = (isCustomPublic ? _cpaOffset : _addrOffset); if (num < 0) { return "no-field"; } byte* ptr = (byte*)(void*)intPtr + num; ptr[_hasValueRel] = 1; Unsafe.Write(ptr + _valueRel, net); try { Nullable val = BuildBoxedNullable(net); if (isCustomPublic) { args.CustomPublicAddress = val; } else { args.Address = val; } } catch (Exception ex) { return "setter-fail:" + ex.GetType().Name; } if (ptr[_hasValueRel] == 0) { return "raw-false"; } try { return "ok-raw:" + ((object)Unsafe.Read(ptr + _valueRel)/*cast due to .constrained prefix*/).ToString(); } catch { return "ok-raw"; } } private unsafe static Nullable BuildBoxedNullable(NetAddress net) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) Nullable obj = new Nullable(net); byte* ptr = (byte*)(void*)IL2CPP.il2cpp_object_unbox(IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase)(object)obj)); ptr[_hasValueRel] = 1; Unsafe.Write(ptr + _valueRel, net); return obj; } private static void EnsureLayout(NetAddress probeNet) { //IL_0174: Unknown result type (might be due to invalid IL or missing references) if (_cpaOffset < 0 || _addrOffset < 0 || _hasValueRel < 0 || _valueRel < 0 || _objectHeader < 0) { if ((object)_cpaFieldInfo == null) { _cpaFieldInfo = AccessTools.Field(typeof(StartGameArgs), "NativeFieldInfoPtr_CustomPublicAddress") ?? throw new MissingFieldException(typeof(StartGameArgs).FullName, "NativeFieldInfoPtr_CustomPublicAddress"); } if ((object)_addrFieldInfo == null) { _addrFieldInfo = AccessTools.Field(typeof(StartGameArgs), "NativeFieldInfoPtr_Address") ?? throw new MissingFieldException(typeof(StartGameArgs).FullName, "NativeFieldInfoPtr_Address"); } if ((object)_nullableHasValueFieldInfo == null) { _nullableHasValueFieldInfo = AccessTools.Field(typeof(Nullable), "NativeFieldInfoPtr_hasValue") ?? throw new MissingFieldException("Il2CppSystem.Nullable", "NativeFieldInfoPtr_hasValue"); } if ((object)_nullableValueFieldInfo == null) { _nullableValueFieldInfo = AccessTools.Field(typeof(Nullable), "NativeFieldInfoPtr_value") ?? throw new MissingFieldException("Il2CppSystem.Nullable", "NativeFieldInfoPtr_value"); } IntPtr intPtr = (IntPtr)_cpaFieldInfo.GetValue(null); IntPtr intPtr2 = (IntPtr)_addrFieldInfo.GetValue(null); IntPtr intPtr3 = (IntPtr)_nullableHasValueFieldInfo.GetValue(null); IntPtr intPtr4 = (IntPtr)_nullableValueFieldInfo.GetValue(null); _cpaOffset = (int)IL2CPP.il2cpp_field_get_offset(intPtr); _addrOffset = (int)IL2CPP.il2cpp_field_get_offset(intPtr2); int num = (int)IL2CPP.il2cpp_field_get_offset(intPtr3); int num2 = (int)IL2CPP.il2cpp_field_get_offset(intPtr4); IntPtr intPtr5 = IL2CPP.Il2CppObjectBaseToPtrNotNull((Il2CppObjectBase)(object)new Nullable(probeNet)); _objectHeader = (int)((long)IL2CPP.il2cpp_object_unbox(intPtr5) - (long)intPtr5); if (_objectHeader < 0 || _objectHeader > 64) { _objectHeader = 16; } _hasValueRel = ((num >= _objectHeader) ? (num - _objectHeader) : num); _valueRel = ((num2 >= _objectHeader) ? (num2 - _objectHeader) : num2); if (!_loggedLayout) { _loggedLayout = true; Plugin.LogSrc.LogInfo((object)("[SatmLanIp] fusion_layout header=" + _objectHeader + " cpa=" + _cpaOffset + " hasValueRel=" + _hasValueRel + " valueRel=" + _valueRel)); } if (_hasValueRel < 0 || _valueRel <= _hasValueRel) { throw new InvalidOperationException("Bad Nullable layout"); } } } } internal static class NoPhotonProbe { private static bool _windowOpen; private static float _windowEnd; private static int _leakStarts; private static bool _logged; public static void OnConnected() { _leakStarts = 0; _logged = false; _windowOpen = true; _windowEnd = Time.unscaledTime + 5f; if (Plugin.Transport != null) { Plugin.Transport.Session.FusionStartsBlocked = 0; } if (Plugin.VerboseNetworkLog) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] NoPhotonProbe window 5s started"); } } public static void NoteFusionStartAttempt() { if (_windowOpen) { _leakStarts++; } } public static void Poll() { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown if (!_windowOpen || _logged || Time.unscaledTime < _windowEnd) { return; } _windowOpen = false; _logged = true; if (!Plugin.VerboseNetworkLog && _leakStarts == 0) { return; } if (_leakStarts == 0) { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] photon_session=none"); return; } ManualLogSource logSrc = Plugin.LogSrc; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(49, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] photon_session=leak FusionStartsSeen="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(_leakStarts); } logSrc.LogWarning(val); } } [HarmonyPatch] internal static class PhotonGuardPatches { [HarmonyPrefix] [HarmonyPatch(typeof(FusionNetworkManager), "StartAsHost")] private static bool StartAsHostPrefix(string sessionName) { return Gate("StartAsHost", sessionName); } [HarmonyPrefix] [HarmonyPatch(typeof(FusionNetworkManager), "StartAsClient")] private static bool StartAsClientPrefix(string sessionName, string region) { return Gate("StartAsClient", sessionName + "/" + region); } [HarmonyPostfix] [HarmonyPatch(typeof(MainMenu), "StartGame")] private static void MenuStartGamePostfix() { Plugin.LogSrc.LogInfo((object)"[SatmLanIp] menu_StartGame fired"); } private static bool Gate(string where, string detail) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown if (LanMatch.AllowFusionStart) { Plugin.LogSrc.LogInfo((object)("[SatmLanIp] allow Fusion " + where + " (" + detail + ")")); return true; } if (!Plugin.IsActive || !Plugin.BlockFusionStart) { NoPhotonProbe.NoteFusionStartAttempt(); return true; } if (Plugin.Transport != null) { Plugin.Transport.Session.FusionStartsBlocked++; } ManualLogSource logSrc = Plugin.LogSrc; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(30, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] blocked Fusion "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(where); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(detail); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")"); } logSrc.LogWarning(val); LanHudBehaviour.NotifyFusionBlocked(where); return false; } internal static void LogPatchStatus(Harmony harmony) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown int num = 0; foreach (MethodBase patchedMethod in harmony.GetPatchedMethods()) { string name = patchedMethod.Name; if (name == "StartAsHost" || name == "StartAsClient") { num++; } } ManualLogSource logSrc = Plugin.LogSrc; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(75, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] Harmony PhotonGuard patches touching StartAsHost/Client count~="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(num); } logSrc.LogInfo(val); } } [BepInPlugin("com.satmlanip", "SatmLanIp", "1.0.2")] public class Plugin : BasePlugin { internal static ManualLogSource LogSrc; internal static bool Enabled; internal static int ListenPort = 37241; internal static string JoinAddress = ""; internal static int JoinPort = 37241; internal static bool BlockFusionStart = true; internal static bool ShowHud = true; internal static bool HideHudInGame = true; internal static bool ShowNativeMenu = true; internal static int ConnectTimeoutSec = 30; internal static bool VerboseNetworkLog; internal static bool IsActive; internal static LanTransport Transport; private static ConfigEntry JoinAddressEntry; private static ConfigEntry ListenPortEntry; private static ConfigEntry JoinPortEntry; private ConfigEntry _enabled; private ConfigEntry _listenPort; private ConfigEntry _joinPort; private ConfigEntry _blockFusion; private ConfigEntry _showHud; private ConfigEntry _hideHudInGame; private ConfigEntry _showNativeMenu; private ConfigEntry _timeout; private ConfigEntry _verboseNetworkLog; private GameObject _hudGo; private static bool _syncingPorts; public override void Load() { //IL_04b6: Unknown result type (might be due to invalid IL or missing references) //IL_04bc: Expected O, but got Unknown //IL_0513: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Expected O, but got Unknown //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Expected O, but got Unknown //IL_04e5: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Expected O, but got Unknown //IL_03e0: Unknown result type (might be due to invalid IL or missing references) //IL_03e6: Expected O, but got Unknown //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_041b: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_043b: Unknown result type (might be due to invalid IL or missing references) //IL_044b: Unknown result type (might be due to invalid IL or missing references) //IL_045b: Unknown result type (might be due to invalid IL or missing references) //IL_046b: Unknown result type (might be due to invalid IL or missing references) //IL_0471: Expected O, but got Unknown //IL_0471: Unknown result type (might be due to invalid IL or missing references) //IL_0477: Expected O, but got Unknown //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_047d: Expected O, but got Unknown //IL_047d: Unknown result type (might be due to invalid IL or missing references) //IL_0483: Expected O, but got Unknown //IL_0483: Unknown result type (might be due to invalid IL or missing references) //IL_0489: Expected O, but got Unknown //IL_0489: Unknown result type (might be due to invalid IL or missing references) //IL_048f: Expected O, but got Unknown //IL_048f: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Expected O, but got Unknown //IL_049a: Expected O, but got Unknown LogSrc = ((BasePlugin)this).Log; if (string.IsNullOrEmpty("1.0.2")) { throw new InvalidOperationException("SatmLanIp PluginInfo self-check failed"); } LanProtocol.SelfCheck(); LanHostParse.SelfCheck(); LanRoom.SelfCheck(); LanPose.SelfCheck(); LanLocalIp.SelfCheck(); LanFusionStart.SelfCheck(); FusionLanPatches.SelfCheck(); LanMatch.SelfCheck(); FusionCloudBypassPatches.SelfCheck(); ConflictGuard.SelfCheck(); LanHudBehaviour.SelfCheck(); LanMenuActions.SelfCheck(); LanMenuFlow.SelfCheck(); LanMenuInjector.SelfCheck(); LanConfig.SelfCheck(); LanCloneUi.SelfCheck(); _enabled = ((BasePlugin)this).Config.Bind("General", "Enabled", true, "Master switch. Default true."); _listenPort = ((BasePlugin)this).Config.Bind("General", "ListenPort", 37241, "Host UDP listen port. UI Port writes the same value to JoinPort."); ListenPortEntry = _listenPort; JoinAddressEntry = ((BasePlugin)this).Config.Bind("General", "JoinAddress", "", "Host IP for joiners only. Default empty."); _joinPort = ((BasePlugin)this).Config.Bind("General", "JoinPort", 37241, "Join UDP port. Synced with UI Port and ListenPort."); JoinPortEntry = _joinPort; _blockFusion = ((BasePlugin)this).Config.Bind("General", "BlockFusionStart", true, "While LAN Active, block FusionNetworkManager StartAsHost / StartAsClient (plugin still starts Fusion when ready)."); _showHud = ((BasePlugin)this).Config.Bind("General", "ShowHud", true, "Show LAN lobby / status UI."); _hideHudInGame = ((BasePlugin)this).Config.Bind("General", "HideHudInGame", true, "Hide this mod's HUD after entering the Game scene."); _showNativeMenu = ((BasePlugin)this).Config.Bind("General", "ShowNativeMenu", true, "Inject 局域网联机 into the play menu list (above Solo)."); _timeout = ((BasePlugin)this).Config.Bind("General", "ConnectTimeoutSec", 30, "Client connect timeout in seconds. Default 30."); _verboseNetworkLog = ((BasePlugin)this).Config.Bind("General", "VerboseNetworkLog", false, "Verbose Fusion socket / packet-header logs. Off by default; redact IPs before sharing."); try { ((BasePlugin)this).Config.Remove(new ConfigDefinition("General", "ConfigRevision")); } catch { } Enabled = _enabled.Value; ListenPort = NormalizePortEntry(ListenPortEntry); JoinAddress = JoinAddressEntry.Value ?? ""; JoinPort = NormalizePortEntry(JoinPortEntry); BlockFusionStart = _blockFusion.Value; ShowHud = _showHud.Value; HideHudInGame = _hideHudInGame.Value; ShowNativeMenu = _showNativeMenu.Value; ConnectTimeoutSec = _timeout.Value; VerboseNetworkLog = _verboseNetworkLog.Value; _enabled.SettingChanged += delegate { Enabled = _enabled.Value; }; ListenPortEntry.SettingChanged += delegate { if (!_syncingPorts) { SetLanPort(NormalizePortEntry(ListenPortEntry)); } }; JoinAddressEntry.SettingChanged += delegate { JoinAddress = JoinAddressEntry.Value ?? ""; }; JoinPortEntry.SettingChanged += delegate { if (!_syncingPorts) { SetLanPort(NormalizePortEntry(JoinPortEntry)); } }; _blockFusion.SettingChanged += delegate { BlockFusionStart = _blockFusion.Value; }; _showHud.SettingChanged += delegate { ShowHud = _showHud.Value; }; _hideHudInGame.SettingChanged += delegate { HideHudInGame = _hideHudInGame.Value; }; _showNativeMenu.SettingChanged += delegate { ShowNativeMenu = _showNativeMenu.Value; }; _timeout.SettingChanged += delegate { ConnectTimeoutSec = _timeout.Value; }; _verboseNetworkLog.SettingChanged += delegate { VerboseNetworkLog = _verboseNetworkLog.Value; }; Transport = new LanTransport(); ConflictGuard.Refresh(); bool flag = default(bool); if (ConflictGuard.ConflictsPresent) { ManualLogSource logSrc = LogSrc; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(80, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] conflict: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ConflictGuard.ConflictSummary); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; LAN Active disabled until those DLLs are renamed/removed"); } logSrc.LogError(val); } try { Harmony val2 = new Harmony("com.satmlanip"); val2.PatchAll(typeof(PhotonGuardPatches)); val2.PatchAll(typeof(FusionLanPatches)); val2.PatchAll(typeof(FusionCloudBypassPatches)); val2.PatchAll(typeof(LanMenuInjector)); val2.PatchAll(typeof(LanMenuPanel)); FusionCloudBypassPatches.ApplySimulationLocalPlayerPatches(val2); FusionCloudBypassPatches.ApplySocketPatches(val2); FusionLanPatches.ApplyLeavePatches(val2); PhotonGuardPatches.LogPatchStatus(val2); FusionLanPatches.LogPatchStatus(val2); FusionCloudBypassPatches.LogPatchStatus(val2); LanMenuInjector.LogPatchStatus(val2); LanMenuPanel.LogPatchStatus(val2); LogSrc.LogInfo((object)"[SatmLanIp] LAN Fusion hooks + native menu active"); } catch (Exception ex) { ManualLogSource logSrc2 = LogSrc; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(28, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] Harmony failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex); } logSrc2.LogError(val); } try { ClassInjector.RegisterTypeInIl2Cpp(); _hudGo = new GameObject("SatmLanIpHud"); Object.DontDestroyOnLoad((Object)(object)_hudGo); _hudGo.AddComponent(); } catch (Exception ex2) { ManualLogSource logSrc3 = LogSrc; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[SatmLanIp] HUD failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted(ex2); } logSrc3.LogError(val); } try { Application.runInBackground = true; } catch (Exception ex3) { LogSrc.LogWarning((object)("[SatmLanIp] runInBackground: " + ex3.Message)); } LogSrc.LogInfo((object)($"[SatmLanIp] {"SatmLanIp"} {"1.0.2"} loaded (Enabled={Enabled}; " + "ShowNativeMenu=" + ShowNativeMenu + "). 菜单:局域网联机→创建/加入(创建房间后再选档/模式)。")); } internal static void SetJoinAddress(string ip) { JoinAddress = ip ?? ""; if (JoinAddressEntry != null) { JoinAddressEntry.Value = JoinAddress; } } internal static void SetLanPort(int port) { if (!LanConfig.IsValidPort(port) || (ListenPort == port && JoinPort == port && (ListenPortEntry == null || ListenPortEntry.Value == port) && (JoinPortEntry == null || JoinPortEntry.Value == port))) { return; } _syncingPorts = true; try { ListenPort = port; JoinPort = port; if (ListenPortEntry != null) { ListenPortEntry.Value = port; } if (JoinPortEntry != null) { JoinPortEntry.Value = port; } } finally { _syncingPorts = false; } } private static int NormalizePortEntry(ConfigEntry entry) { int num = LanConfig.NormalizePort(entry.Value); if (entry.Value != num) { entry.Value = num; } return num; } } internal static class PluginInfo { public const string GUID = "com.satmlanip"; public const string Name = "SatmLanIp"; public const string Version = "1.0.2"; } internal static class LanCloneUi { internal const string CreateName = "SatmLanIp_CreatePanel"; internal const string LobbyName = "SatmLanIp_LobbyPanel"; internal const string JoinName = "SatmLanIp_JoinBtn"; private static GameObject _create; private static GameObject _lobby; private static Button _joinBtn; private static Button _startBtn; private static Button _readyBtn; private static Button _leaveBtn; private static TMP_Text _lobbyTitle; private static TMP_Text _lobbyStatus; private static TMP_Text _createHint; private static GameObject _noticeRoot; private static GameObject _joinPrompt; private static TMP_InputField _joinIpField; private static string _createNotice = ""; private static int _maxPlayers = 3; private static bool _dumpedCreate; internal static bool HasCreateUi => (Object)(object)_create != (Object)null; internal static bool HasJoinPrompt => (Object)(object)_joinPrompt != (Object)null; internal static int ReadMaxPlayers() { int num = TryReadMaxFromClone(); if (num > 0) { _maxPlayers = LanRoom.ClampMax(num); } return _maxPlayers; } internal static void ShowCreate() { HideJoinPrompt(); HideCreate(); MainMenu val = FindMenu(); if ((Object)(object)val == (Object)null || (Object)(object)val.createLobbySettingsMenu == (Object)null) { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] clone create skip (no createLobbySettingsMenu)"); ShowOverlayCreate(); return; } GameObject createLobbySettingsMenu = val.createLobbySettingsMenu; bool activeSelf = createLobbySettingsMenu.activeSelf; try { if (!activeSelf) { createLobbySettingsMenu.SetActive(true); } GameObject val2 = Object.Instantiate(createLobbySettingsMenu, createLobbySettingsMenu.transform.parent); ((Object)val2).name = "SatmLanIp_CreatePanel"; DumpTmp(val2, "create-raw"); StripPhotonBits(val2); DestroySaveCopy(val, createLobbySettingsMenu, val2); Button val3 = FindButton(val2, IsCreateRoomLabel); Button val4 = FindButton(val2, IsBackLabel); if ((Object)(object)val3 == (Object)null) { val3 = FindPrimaryActionButton(val2); } DestroyLabeledGroups(val2); KillLanguageText(val2); if ((Object)(object)val3 == (Object)null) { Plugin.LogSrc.LogWarning((object)"[SatmLanIp] clone create: no 创建房间 button"); DumpTmp(val2, "create"); Object.Destroy((Object)(object)val2); ShowOverlayCreate(); return; } StripClicks(((Component)val3).gameObject); ApplyLabel(((Component)val3).gameObject, "创建房间"); ((UnityEvent)val3.onClick).AddListener(UnityAction.op_Implicit((Action)OnCreateClicked)); GameObject val5 = Object.Instantiate(((Component)val3).gameObject, ((Component)val3).transform.parent); ((Object)val5).name = "SatmLanIp_JoinBtn"; KillLanguageText(val5); ApplyLabel(val5, "加入房间"); StripClicks(val5); Button val6 = val5.GetComponent