Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of REPO Steam P2P v1.4.0
BepInEx\plugins\RepoSteamTransport.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using Photon.Voice; using Photon.Voice.PUN; using Photon.Voice.Unity; using Platforms; using RepoSteamTransport.Patches; using RepoSteamTransport.Transport; using Steamworks; using Steamworks.Data; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Makfly")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Replaces REPO's Photon transport with Steamworks SDR P2P (host = in-process virtual LoadBalancing server).")] [assembly: AssemblyFileVersion("1.4.0.0")] [assembly: AssemblyInformationalVersion("1.4.0+d21b707937412ba23d857cbe52e578e60946378c")] [assembly: AssemblyProduct("RepoSteamTransport")] [assembly: AssemblyTitle("RepoSteamTransport")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.4.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RepoSteamTransport { public static class ModConfig { public static ConfigEntry<bool> Enabled; public static ConfigEntry<string> Role; public static ConfigEntry<string> HostSteamId; public static ConfigEntry<string> HostAddress; public static ConfigEntry<bool> ZeroTierAuto; public static ConfigEntry<bool> ForceVoiceTransmit; public static ConfigEntry<bool> PhotonCompat; public static ConfigEntry<bool> Verbose; public static ConfigEntry<int> VirtualPort; public static ConfigEntry<bool> LobbyAuthCheck; private static ConfigFile _file; private static FieldInfo _currentLobbyField; private static int _lastLobbyReadFailedTick = -100000; public static void Initialize(ConfigFile file) { _file = file; Enabled = file.Bind<bool>("General", "Enabled", true, "Master switch for the Steam SDR transport."); Role = file.Bind<string>("General", "Role", "Auto", "Auto (resolve from Steam lobby) | Host | Client"); HostSteamId = file.Bind<string>("General", "HostSteamId", "", "SteamId64 of the host when Role=Client (or Auto fallback)."); HostAddress = file.Bind<string>("General", "HostAddress", "", "Direct host IP (e.g. 127.0.0.1 for a local two-instance test, or a LAN IP). Uses plain UDP instead of Steam relay."); ZeroTierAuto = file.Bind<bool>("General", "ZeroTierAuto", true, "Fallback step 2/3 of the connection ladder: try the host's ZeroTier IP published in the Steam lobby (direct UDP tunnel) before the Steam relay. The ladder: Steam P2P (LAN direct) → ZeroTier → Steam relay."); ForceVoiceTransmit = file.Bind<bool>("General", "ForceVoiceTransmit", false, "When true: open microphone (always transmit in a room, ignoring PushToTalk/mute). When false: REPO's native voice logic (Push to Talk setting in game options works normally)."); PhotonCompat = file.Bind<bool>("General", "PhotonCompat", true, "Photon compatibility shim (merged from Self_Hosted_Photon): forces GpBinaryV16 serialization our virtual server is tuned for, blanks Photon cloud AppIds, overrides empty lobby Region to 'eu'. Keep enabled."); Verbose = file.Bind<bool>("General", "Verbose", false, "Detailed transport logging (every datagram, operation and voice event). Only enable for diagnostics — very spammy."); VirtualPort = file.Bind<int>("General", "VirtualPort", 0, "SteamNetworkingSockets virtual port for the SDR session."); LobbyAuthCheck = file.Bind<bool>("General", "LobbyAuthCheck", true, "Allow connections to the virtual server only for members of the host's current Steam lobby. If the host's lobby cannot be read, connections are allowed (fail open). Peers without a known SteamId (loopback, LAN/UDP) are always allowed."); _currentLobbyField = typeof(SteamLobbyManager).GetField("currentLobby", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } public static bool TryGetLobby(out Lobby lobby) { bool reflectionFailed; return TryGetLobby(out lobby, out reflectionFailed); } public static bool TryGetLobby(out Lobby lobby, out bool reflectionFailed) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) lobby = default(Lobby); reflectionFailed = false; try { SteamLobbyManager instance = SteamLobbyManager.instance; if ((Object)(object)instance == (Object)null) { return false; } if (_currentLobbyField == null) { reflectionFailed = true; WarnLobbyReadFailed("field handle not resolved"); return false; } lobby = (Lobby)_currentLobbyField.GetValue(instance); SteamId id = ((Lobby)(ref lobby)).Id; return ((SteamId)(ref id)).IsValid; } catch (Exception ex) { reflectionFailed = true; WarnLobbyReadFailed(ex.Message); return false; } } private static void WarnLobbyReadFailed(string reason) { int tickCount = Environment.TickCount; if (tickCount - _lastLobbyReadFailedTick >= 30000) { _lastLobbyReadFailedTick = tickCount; ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[config] SteamLobbyManager.currentLobby read failed (" + reason + ") - lobby-derived values are degraded")); } } } public static string GetZeroTierIp() { try { NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up) { continue; } string text = networkInterface.Name + " " + networkInterface.Description; if (text.IndexOf("ZeroTier", StringComparison.OrdinalIgnoreCase) < 0) { continue; } foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) { if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) { return unicastAddress.Address.ToString(); } } } } catch (Exception ex) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[config] ZeroTier NIC scan failed: " + ex.Message)); } } } return null; } public static bool IsHostRole() { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) string text = (Role?.Value ?? "Auto").Trim().ToLowerInvariant(); if (!(text == "host")) { if (text == "client") { return false; } if (!TryGetLobby(out var lobby, out var reflectionFailed)) { if (reflectionFailed) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[config] role resolution degraded to the Auto default (no lobby owner available)"); } } return true; } try { return SteamId.op_Implicit(((Lobby)(ref lobby)).Owner.Id) == SteamId.op_Implicit(SteamClient.SteamId); } catch (Exception ex) { if (Plugin.Verbose) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[config] lobby owner / local SteamId compare failed: " + ex.Message)); } } return true; } } return true; } public static ulong ResolveHostSteamId() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) if (TryGetLobby(out var lobby)) { SteamId id = ((Lobby)(ref lobby)).Id; if (((SteamId)(ref id)).IsValid) { try { ulong num = SteamId.op_Implicit(((Lobby)(ref lobby)).Owner.Id); if (num != 0L) { return num; } } catch (Exception ex) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[config] lobby owner read failed: " + ex.Message)); } } } } } string s = (HostSteamId?.Value ?? "").Trim(); if (ulong.TryParse(s, out var result) && result != 0L) { return result; } return 0uL; } public static string GetZeroTierFallbackIp() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) ConfigEntry<bool> zeroTierAuto = ZeroTierAuto; if (zeroTierAuto != null && !zeroTierAuto.Value) { return null; } if (TryGetLobby(out var lobby)) { SteamId id = ((Lobby)(ref lobby)).Id; if (((SteamId)(ref id)).IsValid) { try { string data = ((Lobby)(ref lobby)).GetData("ztip"); if (!string.IsNullOrEmpty(data)) { return data; } } catch (Exception ex) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[config] lobby ztip read failed: " + ex.Message)); } } } } } return null; } public static string ResolveTargetAddress() { string text = (HostAddress?.Value ?? "").Trim(); if (text.Length > 0) { return text; } ulong num = ResolveHostSteamId(); if (num != 0L) { return num.ToString(); } string zeroTierFallbackIp = GetZeroTierFallbackIp(); if (!string.IsNullOrEmpty(zeroTierFallbackIp)) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[config] no SteamId available -> ZeroTier fallback: " + zeroTierFallbackIp)); } } return zeroTierFallbackIp; } return "0"; } } internal static class ObjectLetExtensions { public static void Let<T>(this T obj, Action<T> action) { if (obj != null) { action(obj); } } } [BepInPlugin("local.repoversteam.transport", "REPO Steam Transport", "1.4.0")] public class Plugin : BaseUnityPlugin { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static LogCallback <>9__17_0; public static SerializeMethod <>9__32_0; public static DeserializeMethod <>9__32_1; public static SerializeStreamMethod <>9__32_2; public static DeserializeStreamMethod <>9__32_3; internal void <Awake>b__17_0(string msg, string _, LogType type) { //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Invalid comparison between Unknown and I4 if (msg == null) { return; } if (msg.Contains("Unity microphone input source creation failure")) { MicStormGuardPatch.NotifyMicFailure(); } if (!Verbose || (!msg.Contains("PhotonVoice") && !msg.Contains("VoiceClient") && !msg.Contains("Voice room") && !msg.Contains("Voice client") && !msg.Contains("OnVoiceStateChanged") && !msg.Contains("FollowLeader") && !msg.Contains("voice room") && !msg.Contains("Recorder") && !msg.Contains("Photon") && !msg.Contains("Join") && !msg.Contains("scene") && !msg.Contains("Level") && !msg.Contains("Property") && !msg.Contains("index"))) { return; } string text = ((msg.Length > 220) ? msg.Substring(0, 220) : msg); if ((int)type == 0 || (int)type == 4) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[pv] " + text)); } } else { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)("[pv] " + text)); } } } internal byte[] <RegisterVoiceCustomTypes>b__32_0(object obj) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ByteArraySlice val = (ByteArraySlice)obj; byte[] array = new byte[val.Count]; Buffer.BlockCopy(val.Buffer, val.Offset, array, 0, val.Count); return array; } internal object <RegisterVoiceCustomTypes>b__32_1(byte[] buffer) { return buffer; } internal short <RegisterVoiceCustomTypes>b__32_2(StreamBuffer dout, object obj) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ByteArraySlice val = (ByteArraySlice)obj; dout.Write(val.Buffer, val.Offset, val.Count); return (short)val.Count; } internal object <RegisterVoiceCustomTypes>b__32_3(StreamBuffer din, short len) { byte[] array = new byte[len]; int num = din.Read(array, 0, (int)len); if (num != len) { return array[..num]; } return array; } } public const string PluginGuid = "local.repoversteam.transport"; public const string PluginName = "REPO Steam Transport"; public const string PluginVersion = "1.4.0"; private static bool _runInBackgroundSet; private MethodInfo _sendVoiceInfoMethod; private int _voiceResendTick = Environment.TickCount - 10000; private static int _lastResendFailWarnTick = -100000; private MethodInfo _remoteVoicesGetter; private FieldInfo _localVoicesField; private PropertyInfo _pvcInstance; private PropertyInfo _voiceClientProp; private PropertyInfo _primaryRecorderGetter; private int _voiceDiagTick = Environment.TickCount - 15000; private static int _lastDiagFailWarnTick = -100000; private static bool _voiceSliceBusyLogged; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static bool Verbose => ModConfig.Verbose?.Value ?? false; internal Harmony Harmony { get; private set; } private void Awake() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown Instance = this; Log = Logger.CreateLogSource("local.repoversteam.transport"); ModConfig.Initialize(((BaseUnityPlugin)this).Config); ConfigEntry<bool> enabled = ModConfig.Enabled; if (enabled == null || !enabled.Value) { Log.LogInfo((object)"[plugin] mod disabled by config (Enabled=false) - transport not initialized"); return; } RegisterVoiceCustomTypes(); object obj = <>c.<>9__17_0; if (obj == null) { LogCallback val = delegate(string msg, string _, LogType val2) { //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Invalid comparison between Unknown and I4 if (msg != null) { if (msg.Contains("Unity microphone input source creation failure")) { MicStormGuardPatch.NotifyMicFailure(); } if (Verbose && (msg.Contains("PhotonVoice") || msg.Contains("VoiceClient") || msg.Contains("Voice room") || msg.Contains("Voice client") || msg.Contains("OnVoiceStateChanged") || msg.Contains("FollowLeader") || msg.Contains("voice room") || msg.Contains("Recorder") || msg.Contains("Photon") || msg.Contains("Join") || msg.Contains("scene") || msg.Contains("Level") || msg.Contains("Property") || msg.Contains("index"))) { string text2 = ((msg.Length > 220) ? msg.Substring(0, 220) : msg); if ((int)val2 == 0 || (int)val2 == 4) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogWarning((object)("[pv] " + text2)); } } else { ManualLogSource log4 = Log; if (log4 != null) { log4.LogInfo((object)("[pv] " + text2)); } } } } }; <>c.<>9__17_0 = val; obj = (object)val; } Application.logMessageReceived += (LogCallback)obj; Application.runInBackground = true; Thread thread = new Thread(SdrFabric.PumpUdpLoop) { IsBackground = true, Name = "RepoSteamTransport udp" }; thread.Start(); Harmony = new Harmony("local.repoversteam.transport"); Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (type.GetCustomAttributes(typeof(HarmonyAttribute), inherit: false).Length == 0) { continue; } try { Harmony.CreateClassProcessor(type).Patch(); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("[patch] FAILED to apply " + type.Name + ": " + ex.Message)); } } } string text = ""; try { string location = Assembly.GetExecutingAssembly().Location; if (!string.IsNullOrEmpty(location)) { text = $" build {File.GetLastWriteTime(location):yyyy-MM-dd HH:mm:ss}"; } } catch (Exception ex2) { if (Verbose) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[plugin] build time read failed: " + ex2.Message)); } } } if (Verbose) { Log.LogInfo((object)("REPO Steam Transport v1.4.0" + text + " loaded. Transport is Steam SDR; virtual LB server = in-process on host.")); } } private void Update() { ConfigEntry<bool> enabled = ModConfig.Enabled; if (enabled == null || !enabled.Value) { return; } if (!_runInBackgroundSet) { _runInBackgroundSet = true; Application.runInBackground = true; } try { SdrFabric.Pump(); } catch (Exception ex) { if (Verbose) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("[pump] update pump failed: " + ex.Message)); } } } VoiceDiagnosticsTick(); VoiceInfoResendTick(); } private void VoiceInfoResendTick() { try { if (Environment.TickCount - _voiceResendTick < 10000) { return; } _voiceResendTick = Environment.TickCount; if (_pvcInstance == null || _voiceClientProp == null || _localVoicesField == null) { return; } object value = _pvcInstance.GetValue(null); if (value == null) { return; } object value2 = _voiceClientProp.GetValue(value); if (value2 == null || !(_localVoicesField.GetValue(value2) is IDictionary { Count: not 0 } dictionary)) { return; } if (_sendVoiceInfoMethod == null) { object obj = null; { IEnumerator enumerator = dictionary.Values.GetEnumerator(); try { if (enumerator.MoveNext()) { object current = enumerator.Current; obj = current; } } finally { IDisposable disposable = enumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } } Type type = obj?.GetType().BaseType; _sendVoiceInfoMethod = type?.GetMethod("sendVoiceInfoAndConfigFrame", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[2] { typeof(bool), typeof(int[]) }, null); if (_sendVoiceInfoMethod == null && type != null) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "sendVoiceInfoAndConfigFrame" && methodInfo.GetParameters().Length == 2) { _sendVoiceInfoMethod = methodInfo; break; } } } } if (_sendVoiceInfoMethod == null) { return; } foreach (object value3 in dictionary.Values) { _sendVoiceInfoMethod.Invoke(value3, new object[2] { false, null }); } if (Verbose) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[voice-resend] re-published voice info for {dictionary.Count} local voice(s)"); } } } catch (Exception ex) { int tickCount = Environment.TickCount; if (tickCount - _lastResendFailWarnTick >= 30000) { _lastResendFailWarnTick = tickCount; ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[voice-resend] tick failed: " + ex.Message)); } } } } private void VoiceDiagnosticsTick() { try { if (Environment.TickCount - _voiceDiagTick < 15000) { return; } _voiceDiagTick = Environment.TickCount; if (_pvcInstance == null) { Type typeFromHandle = typeof(PunVoiceClient); _pvcInstance = typeFromHandle.GetProperty("ExistingInstance", BindingFlags.Static | BindingFlags.Public); _voiceClientProp = typeFromHandle.GetProperty("VoiceClient", BindingFlags.Instance | BindingFlags.Public); Type typeFromHandle2 = typeof(VoiceClient); _remoteVoicesGetter = typeFromHandle2.GetProperty("RemoteVoiceInfos", BindingFlags.Instance | BindingFlags.Public)?.GetMethod; _localVoicesField = typeFromHandle2.GetField("localVoices", BindingFlags.Instance | BindingFlags.NonPublic); _primaryRecorderGetter = typeFromHandle.GetProperty("PrimaryRecorder", BindingFlags.Instance | BindingFlags.Public); } if (_pvcInstance == null || _voiceClientProp == null) { return; } object value = _pvcInstance.GetValue(null); if (value == null) { return; } object value2 = _voiceClientProp.GetValue(value); if (value2 == null) { return; } int localCount = -1; _localVoicesField?.GetValue(value2)?.Let(delegate(object o) { if (o is IDictionary dictionary2) { localCount = dictionary2.Count; } }); int num = -1; object obj = _remoteVoicesGetter?.Invoke(value2, null); if (obj is IDictionary dictionary) { num = dictionary.Count; } else if (obj is IEnumerable enumerable) { int num2 = 0; foreach (object item in enumerable) { num2++; } num = num2; } string arg = "?"; try { object obj2 = _primaryRecorderGetter?.GetValue(value); if (obj2 != null) { arg = (((bool)obj2.GetType().GetProperty("TransmitEnabled").GetValue(obj2)) ? "True" : "False"); } } catch { } if ((localCount >= 0 || num >= 0) && Verbose) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"[voice-diag] localVoices={localCount} remoteVoices={num} transmit={arg}"); } } } catch (Exception ex) { int tickCount = Environment.TickCount; if (tickCount - _lastDiagFailWarnTick >= 30000) { _lastDiagFailWarnTick = tickCount; ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[voice-diag] tick failed: " + ex.Message)); } } } } private void RegisterVoiceCustomTypes() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown try { Type typeFromHandle = typeof(ByteArraySlice); for (byte b = 68; b < 80; b++) { byte num = b; object obj = <>c.<>9__32_0; if (obj == null) { SerializeMethod val = delegate(object obj5) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ByteArraySlice val5 = (ByteArraySlice)obj5; byte[] array = new byte[val5.Count]; Buffer.BlockCopy(val5.Buffer, val5.Offset, array, 0, val5.Count); return array; }; <>c.<>9__32_0 = val; obj = (object)val; } object obj2 = <>c.<>9__32_1; if (obj2 == null) { DeserializeMethod val2 = (byte[] buffer) => buffer; <>c.<>9__32_1 = val2; obj2 = (object)val2; } if (Protocol.TryRegisterType(typeFromHandle, num, (SerializeMethod)obj, (DeserializeMethod)obj2)) { byte num2 = b; object obj3 = <>c.<>9__32_2; if (obj3 == null) { SerializeStreamMethod val3 = delegate(StreamBuffer dout, object obj5) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown ByteArraySlice val5 = (ByteArraySlice)obj5; dout.Write(val5.Buffer, val5.Offset, val5.Count); return (short)val5.Count; }; <>c.<>9__32_2 = val3; obj3 = (object)val3; } object obj4 = <>c.<>9__32_3; if (obj4 == null) { DeserializeStreamMethod val4 = delegate(StreamBuffer din, short len) { byte[] array = new byte[len]; int num3 = din.Read(array, 0, (int)len); return (num3 != len) ? ((object)array[..num3]) : ((object)array); }; <>c.<>9__32_3 = val4; obj4 = (object)val4; } if (Protocol.TryRegisterType(typeFromHandle, num2, (SerializeStreamMethod)obj3, (DeserializeStreamMethod)obj4)) { if (Verbose) { Log.LogInfo((object)$"[Voice] ByteArraySlice registered as custom serializer type 0x{b:X2}"); } return; } } } if (!_voiceSliceBusyLogged) { _voiceSliceBusyLogged = true; if (Verbose) { Log.LogInfo((object)"[Voice] ByteArraySlice serializer already registered (custom type codes busy) - ok, skipping"); } } } catch (Exception ex) { if (Verbose) { Log.LogWarning((object)("[Voice] ByteArraySlice registration failed: " + ex.Message)); } } } } } namespace RepoSteamTransport.Transport { public static class EnetCodec { public struct IncomingCommand { public byte Type; public byte Channel; public byte Flags; public int Size; public int ReliableSeq; public int UnreliableSeq; public byte[] Payload; public byte[] Raw; } public const byte CT_ACK = 1; public const byte CT_CONNECT = 2; public const byte CT_VERIFYCONNECT = 3; public const byte CT_DISCONNECT = 4; public const byte CT_PING = 5; public const byte CT_SENDRELIABLE = 6; public const byte CT_SENDUNRELIABLE = 7; public const byte CT_SENDFRAGMENT = 8; public const byte CT_SENDUNSEQUENCED = 11; public const byte CT_SYSTEM = 12; public const int HeaderSize = 12; public const int DatagramHeaderSize = 12; public const int FragmentHeaderSize = 20; public const int FragmentChunkBytes = 1100; public const int FragmentMaxCount = 128; public const int FragmentMaxTotalBytes = 262144; public static bool IsFlaggedReliable(byte flags) { return (flags & 1) != 0; } public static int ReadIntBE(byte[] b, int off) { return (b[off] << 24) | (b[off + 1] << 16) | (b[off + 2] << 8) | b[off + 3]; } public static void WriteIntBE(byte[] b, int off, int v) { b[off] = (byte)(v >> 24); b[off + 1] = (byte)(v >> 16); b[off + 2] = (byte)(v >> 8); b[off + 3] = (byte)v; } public static void WriteShortBE(byte[] b, int off, int v) { b[off] = (byte)(v >> 8); b[off + 1] = (byte)v; } public static bool TryParseDatagram(byte[] datagram, out int peerId, out int timeInt, out int challenge, out List<IncomingCommand> commands) { peerId = 0; timeInt = 0; challenge = 0; commands = null; if (datagram == null || datagram.Length < 12) { return false; } peerId = (datagram[0] << 8) | datagram[1]; byte b = datagram[3]; timeInt = ReadIntBE(datagram, 4); challenge = ReadIntBE(datagram, 8); commands = new List<IncomingCommand>(b); int num = 12; for (int i = 0; i < b; i++) { if (num + 12 > datagram.Length) { return false; } int num2 = ReadIntBE(datagram, num + 4); if (num2 < 12 || (long)num + (long)num2 > datagram.Length) { return false; } byte b2 = datagram[num]; int num3 = 0; if (b2 == 7 || b2 == 11) { if (num2 < 16) { return false; } num3 = 4; } IncomingCommand item = new IncomingCommand { Type = b2, Channel = datagram[num + 1], Flags = datagram[num + 2], Size = num2, ReliableSeq = ReadIntBE(datagram, num + 8), Raw = new byte[num2] }; if (num3 == 4) { item.UnreliableSeq = ReadIntBE(datagram, num + 12); } Array.Copy(datagram, num, item.Raw, 0, num2); int sourceIndex = num + 12 + num3; int num4 = num2 - 12 - num3; if (num4 > 0) { item.Payload = new byte[num4]; Array.Copy(datagram, sourceIndex, item.Payload, 0, num4); } commands.Add(item); num += num2; } return true; } public static byte[] BuildDatagram(int clientPeerId, int timeInt, int challenge, List<byte[]> commands) { int num = 12; foreach (byte[] command in commands) { num += command.Length; } byte[] array = new byte[num]; WriteShortBE(array, 0, clientPeerId); array[2] = 0; array[3] = (byte)commands.Count; WriteIntBE(array, 4, timeInt); WriteIntBE(array, 8, challenge); int num2 = 12; foreach (byte[] command2 in commands) { Buffer.BlockCopy(command2, 0, array, num2, command2.Length); num2 += command2.Length; } return array; } public static byte[] BuildAck(IncomingCommand cmd, int sentTime) { byte[] array = new byte[20] { 1, cmd.Channel, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; WriteIntBE(array, 4, 20); WriteIntBE(array, 8, 0); WriteIntBE(array, 12, cmd.ReliableSeq); WriteIntBE(array, 16, sentTime); return array; } public static byte[] BuildReliablePayload(byte channel, int seq, byte[] payload) { byte[] array = new byte[12 + ((payload != null) ? payload.Length : 0)]; array[0] = 6; array[1] = channel; array[2] = 1; array[3] = 4; WriteIntBE(array, 4, array.Length); WriteIntBE(array, 8, seq); if (payload != null && payload.Length != 0) { Buffer.BlockCopy(payload, 0, array, 12, payload.Length); } return array; } public static byte[] BuildUnreliablePayload(byte channel, int reliableSeqField, int unreliableSeq, byte[] payload) { byte[] array = new byte[16 + ((payload != null) ? payload.Length : 0)]; array[0] = 7; array[1] = channel; array[2] = 0; array[3] = 4; WriteIntBE(array, 4, array.Length); WriteIntBE(array, 8, reliableSeqField); WriteIntBE(array, 12, unreliableSeq); if (payload != null && payload.Length != 0) { Buffer.BlockCopy(payload, 0, array, 16, payload.Length); } return array; } public static List<byte[]> BuildFragmentCommands(int seqStart, byte[] payload, byte channel) { if (payload == null || payload.Length == 0) { return null; } int num = payload.Length; int num2 = (num + 1100 - 1) / 1100; if (num2 > 128 || num > 262144) { return null; } List<byte[]> list = new List<byte[]>(num2); int num3 = 0; for (int i = 0; i < num2; i++) { int num4 = Math.Min(1100, num - num3); byte[] array = new byte[32 + num4]; array[0] = 8; array[1] = channel; array[2] = 1; array[3] = 4; WriteIntBE(array, 4, array.Length); WriteIntBE(array, 8, seqStart + i); WriteIntBE(array, 12, seqStart); WriteIntBE(array, 16, num2); WriteIntBE(array, 20, i); WriteIntBE(array, 24, num); WriteIntBE(array, 28, num3); Buffer.BlockCopy(payload, num3, array, 32, num4); list.Add(array); num3 += num4; } return list; } public static byte[] BuildVerifyConnect(byte[] connectCmd) { byte[] array = new byte[connectCmd.Length]; Buffer.BlockCopy(connectCmd, 0, array, 0, connectCmd.Length); array[0] = 3; array[2] = 0; WriteIntBE(array, 8, 0); return array; } public static byte[] BuildDisconnect() { byte[] array = new byte[12] { 4, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0 }; WriteIntBE(array, 4, 12); WriteIntBE(array, 8, 0); return array; } public static byte[] BuildPing() { byte[] array = new byte[12] { 5, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0 }; WriteIntBE(array, 4, 12); WriteIntBE(array, 8, 0); return array; } } public static class LanDiscovery { private sealed class DiscoveryCacheEntry { public string Ip; public int Tick; } private const int Port = 47977; private const string QueryPrefix = "REPOQ|"; private const string AnswerPrefix = "REPOA|"; private const int CacheTtlMs = 600000; private static readonly Dictionary<ulong, DiscoveryCacheEntry> Cache = new Dictionary<ulong, DiscoveryCacheEntry>(); private static readonly object CacheSync = new object(); private static readonly object ResponderSync = new object(); private static volatile bool _hostRunning; private static UdpClient? _responderSocket; private static Thread? _responderThread; private static ulong _responderHostId; private static string? _responderToken; public static void InvalidateCache() { lock (CacheSync) { Cache.Clear(); } } public static string FindHostCached(ulong hostSteamId, int timeoutMs, string token, Func<bool>? cancelled = null) { int tickCount = Environment.TickCount; lock (CacheSync) { if (Cache.TryGetValue(hostSteamId, out var value) && tickCount - value.Tick < 600000) { return value.Ip; } } string text = FindHost(hostSteamId, timeoutMs, token, cancelled); if (cancelled != null && cancelled()) { return text; } lock (CacheSync) { Cache[hostSteamId] = new DiscoveryCacheEntry { Ip = text, Tick = Environment.TickCount }; return text; } } public static void StartHostResponder(ulong hostSteamId, string token) { lock (ResponderSync) { if (_responderThread != null && _responderThread.IsAlive) { for (int i = 0; i < 10; i++) { if (!_responderThread.IsAlive) { break; } Thread.Sleep(10); } if (_responderThread.IsAlive) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[LanDiscovery] previous responder still alive - responder not restarted"); } } return; } } _hostRunning = true; _responderHostId = hostSteamId; _responderToken = token; _responderThread = new Thread(ResponderLoop) { IsBackground = true, Name = "RepoSteamTransport lan-responder" }; _responderThread.Start(); } if (Plugin.Verbose) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)string.Format("[LanDiscovery] host responder listening on udp/{0} (steamId {1}, token {2})", 47977, hostSteamId, string.IsNullOrEmpty(token) ? "none" : "set")); } } } public static void StopHostResponder() { lock (ResponderSync) { _hostRunning = false; _responderToken = null; try { _responderSocket?.Close(); } catch (Exception ex) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[LanDiscovery] responder close failed: " + ex.Message)); } } } _responderSocket = null; } } private static void ResponderLoop() { UdpClient udpClient = null; try { lock (ResponderSync) { if (!_hostRunning) { return; } udpClient = (_responderSocket = new UdpClient(47977)); } while (_hostRunning) { IPEndPoint iPEndPoint = null; byte[] data; try { iPEndPoint = new IPEndPoint(IPAddress.Any, 0); data = udpClient.Receive(ref iPEndPoint); } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.Interrupted) { break; } continue; } catch (ObjectDisposedException) { break; } catch (Exception ex3) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[LanDiscovery] receive failed: " + ex3.Message)); } } continue; } if (iPEndPoint != null) { TryAnswer(udpClient, iPEndPoint, data); } } } catch (Exception ex4) { if (Plugin.Verbose) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[LanDiscovery] host responder stopped: " + ex4.Message)); } } } finally { lock (ResponderSync) { if (_responderSocket == udpClient) { _responderSocket = null; } } try { udpClient?.Close(); } catch (Exception ex5) { if (Plugin.Verbose) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)("[LanDiscovery] responder socket dispose failed: " + ex5.Message)); } } } } } private static void TryAnswer(UdpClient udp, IPEndPoint requester, byte[] data) { ulong responderHostId; string responderToken; lock (ResponderSync) { if (!_hostRunning) { return; } responderHostId = _responderHostId; responderToken = _responderToken; } string text = Encoding.ASCII.GetString(data); if (!text.StartsWith("REPOQ|", StringComparison.Ordinal)) { return; } string[] array = text.Substring("REPOQ|".Length).Trim().Split('|'); string text2 = array[0]; if (text2.Length <= 0 || !ulong.TryParse(text2, out var result) || result == 0L || result == responderHostId) { string text3 = ((array.Length > 1) ? array[1] : ""); if ((!SdrFabric.TryGetHostLanToken(out var token) || !(text3 != token)) && SdrFabric.UdpPhaseListenersBound()) { string s = "REPOA|" + responderHostId + (string.IsNullOrEmpty(responderToken) ? "" : ("|" + responderToken)); byte[] bytes = Encoding.ASCII.GetBytes(s); udp.Send(bytes, bytes.Length, requester); } } } private static List<IPEndPoint> BroadcastEndpoints() { List<IPEndPoint> list = new List<IPEndPoint>(); HashSet<string> seen = new HashSet<string>(StringComparer.Ordinal); try { Add(new IPEndPoint(IPAddress.Broadcast, 47977)); NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up || networkInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback) { continue; } foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) { if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) { byte[] addressBytes = unicastAddress.Address.GetAddressBytes(); if (addressBytes.Length == 4 && addressBytes[3] != byte.MaxValue) { addressBytes[3] = byte.MaxValue; Add(new IPEndPoint(new IPAddress(addressBytes), 47977)); } } } } } catch (Exception ex) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[LanDiscovery] interface scan failed: " + ex.Message)); } } } return list; void Add(IPEndPoint ep) { string item = ep.Address.ToString(); if (seen.Add(item)) { list.Add(ep); } } } public static string FindHost(ulong hostSteamId, int timeoutMs, string token, Func<bool>? cancelled = null) { try { using UdpClient udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 400; List<IPEndPoint> list = BroadcastEndpoints(); byte[] bytes = Encoding.ASCII.GetBytes("REPOQ|" + hostSteamId + (string.IsNullOrEmpty(token) ? "" : ("|" + token))); int num = Environment.TickCount + timeoutMs; while (num - Environment.TickCount > 0) { if (cancelled != null && cancelled()) { return null; } foreach (IPEndPoint item in list) { udpClient.Send(bytes, bytes.Length, item); } while (true) { IPEndPoint remoteEP = null; byte[] bytes2; try { bytes2 = udpClient.Receive(ref remoteEP); } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.TimedOut) { break; } throw; } string text = Encoding.ASCII.GetString(bytes2); if (text.StartsWith("REPOA|", StringComparison.Ordinal)) { string[] array = text.Substring("REPOA|".Length).Trim().Split('|'); if (array.Length >= 1 && ulong.TryParse(array[0], out var result) && result == hostSteamId && remoteEP != null && (array.Length <= 1 || string.IsNullOrEmpty(array[1]) || string.IsNullOrEmpty(token) || !(array[1] != token))) { return remoteEP.Address.ToString(); } } } } } catch (Exception ex2) { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[LanDiscovery] probe failed: " + ex2.Message)); } } } return null; } } public static class PhasePorts { public const int Master = 7777; public const int Game = 7778; public const int Voice = 7779; } public class HostListener : SocketManager { public int VirtualPort; public ConcurrentQueue<RemoteDatagram> Inbox = new ConcurrentQueue<RemoteDatagram>(); public ConcurrentDictionary<uint, Connection> Connections = new ConcurrentDictionary<uint, Connection>(); public readonly ConcurrentDictionary<uint, ulong> RemoteSteamIds = new ConcurrentDictionary<uint, ulong>(); private int _loggedMessages; private string Key(ulong steamId) { return $"sdr:{steamId}:{VirtualPort}"; } public override void OnConnecting(Connection connection, ConnectionInfo info) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) NetIdentity identity; if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; object arg = VirtualPort; identity = ((ConnectionInfo)(ref info)).Identity; log.LogInfo((object)$"[SDR host:{arg}] incoming from {((NetIdentity)(ref identity)).SteamId} -> accept"); } Connections[((Connection)(ref connection)).Id] = connection; ConcurrentDictionary<uint, ulong> remoteSteamIds = RemoteSteamIds; uint id = ((Connection)(ref connection)).Id; identity = ((ConnectionInfo)(ref info)).Identity; remoteSteamIds[id] = SteamId.op_Implicit(((NetIdentity)(ref identity)).SteamId); Result val = ((Connection)(ref connection)).Accept(); if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR host:{VirtualPort}] accept result: {val}"); } } public override void OnConnected(Connection connection, ConnectionInfo info) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) ((SocketManager)this).OnConnected(connection, info); if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; object arg = VirtualPort; NetIdentity identity = ((ConnectionInfo)(ref info)).Identity; log.LogInfo((object)$"[SDR host:{arg}] connection established with {((NetIdentity)(ref identity)).SteamId}"); } } public override void OnDisconnected(Connection connection, ConnectionInfo info) { //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; object[] obj = new object[4] { VirtualPort, null, null, null }; NetIdentity identity = ((ConnectionInfo)(ref info)).Identity; obj[1] = ((NetIdentity)(ref identity)).SteamId; obj[2] = ((ConnectionInfo)(ref info)).State; obj[3] = ((ConnectionInfo)(ref info)).EndReason; log.LogWarning((object)string.Format("[SDR host:{0}] connection {1} closed ({2}, end={3})", obj)); } Connections.TryRemove(((Connection)(ref connection)).Id, out var _); RemoteSteamIds.TryRemove(((Connection)(ref connection)).Id, out var _); VirtualServer.EvictSession($"sdr:c{((Connection)(ref connection)).Id}:{VirtualPort}", $"state={((ConnectionInfo)(ref info)).State}, end={((ConnectionInfo)(ref info)).EndReason}"); ((SocketManager)this).OnDisconnected(connection, info); } public override void OnMessage(Connection connection, NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) byte[] array = new byte[size]; Marshal.Copy(data, array, 0, size); Inbox.Enqueue(new RemoteDatagram(array, SteamId.op_Implicit(((NetIdentity)(ref identity)).SteamId), VirtualPort, connection)); if (_loggedMessages < 8) { _loggedMessages++; if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR host:{VirtualPort}] got datagram #{_loggedMessages}: {size}B from {((NetIdentity)(ref identity)).SteamId}"); } } } } public readonly struct RemoteDatagram { public readonly byte[] Data; public readonly ulong SteamId; public readonly int VirtualPort; public readonly Connection Connection; public string SourceKey => $"sdr:c{((Connection)(ref Connection)).Id}:{VirtualPort}"; public RemoteDatagram(byte[] data, ulong steamId, int virtualPort, Connection connection) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) Data = data; SteamId = steamId; VirtualPort = virtualPort; Connection = connection; } } public class HostConnection : ConnectionManager { public ConcurrentQueue<byte[]> Inbox = new ConcurrentQueue<byte[]>(); public bool Established; public Action OnEstablished; public Action OnClosed; public Action<byte[]> DataSink; public SteamSdrSocket Owner; public override void OnConnected(ConnectionInfo info) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; NetIdentity identity = ((ConnectionInfo)(ref info)).Identity; log.LogInfo((object)$"[SDR client] connected to host {((NetIdentity)(ref identity)).SteamId}"); } ((ConnectionManager)this).OnConnected(info); Established = true; OnEstablished?.Invoke(); } public override void OnDisconnected(ConnectionInfo info) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SDR client] disconnected from host ({((ConnectionInfo)(ref info)).State}, end={((ConnectionInfo)(ref info)).EndReason}, established={Established}, owner={Owner != null})"); } bool established = Established; Established = false; SdrFabric.ClientDied(this); if (Owner != null) { if (established) { OnClosed?.Invoke(); } else if (Owner == null || !Owner.RetryConnect()) { OnClosed?.Invoke(); } } } public override void OnMessage(IntPtr data, int size, long messageNum, long recvTime, int channel) { byte[] array = new byte[size]; Marshal.Copy(data, array, 0, size); Inbox.Enqueue(array); } } public static class SdrFabric { private sealed class QueuedAction { public Func<object> Work; public TaskCompletionSource<object> Done; public volatile bool Abandoned; } private sealed class LoopbackPipe { public int SocketId; public int Port; public string Key; public ConcurrentQueue<byte[]> ClientToServer = new ConcurrentQueue<byte[]>(); public Action<byte[]> Sink; } internal static readonly object Sync = new object(); public static ulong HostSteamId; private static readonly ConcurrentQueue<QueuedAction> UnityActions = new ConcurrentQueue<QueuedAction>(); private const int UnityActionWaitSliceMs = 100; private const int UnityActionWaitTimeoutMs = 5000; private static int _nextLoopbackSocketId; private static readonly List<LoopbackPipe> Loopbacks = new List<LoopbackPipe>(); private static readonly List<HostListener> Hosts = new List<HostListener>(); private static readonly List<HostConnection> Clients = new List<HostConnection>(); private static readonly List<UdpClient> UdpListeners = new List<UdpClient>(); private static readonly ConcurrentQueue<(string Key, byte[] Data, Action<byte[]> Send, string Advertise)> UdpInbox = new ConcurrentQueue<(string, byte[], Action<byte[]>, string)>(); private const int UdpInboxMaxQueued = 256; private const int UdpInboxDrainPerFrame = 128; private static int _udpInboxCount; private const int PhaseListenerCount = 3; private static volatile string _hostToken; private static volatile bool _hostTokenPublished; private static volatile string _hostLobbyToken; private const int HostTokenBytes = 8; private const int ZtPublishIntervalMs = 3000; private static int _ztPublishTick = Environment.TickCount - 3000; public const string UdpHelloPrefix = "RREPO1|"; public const string UdpHelloAckPrefix = "RREPO2|"; private static readonly byte[] UdpHelloPrefixBytes = Encoding.ASCII.GetBytes("RREPO1|"); private const int UdpAllowlistTtlMs = 300000; private static readonly Dictionary<string, int> UdpAllowlist = new Dictionary<string, int>(); private static readonly HashSet<string> UdpSessionKeys = new HashSet<string>(); private const int UdpDropLogIntervalMs = 2000; private static int _udpDropLogTick; private static List<IPAddress> _localV4Cache; private static int _localV4CacheTick = Environment.TickCount - 10000; private static int _receivedBatches; private static int _pumpErrorCount; private static int _connDumpTick; private static int _lastPumpTick = Environment.TickCount; private const int LongFrameSuspensionMs = 2000; private static readonly object InternalSingleton = ResolveSingleton(); public static ulong LocalSteamId => SteamId.op_Implicit(SteamClient.IsValid ? SteamClient.SteamId : SteamId.op_Implicit(0uL)); internal static T RunOnUnityThread<T>(Func<T> work, CancellationToken token) { QueuedAction queuedAction = new QueuedAction { Work = () => token.IsCancellationRequested ? ((object)default(T)) : ((object)work()), Done = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously) }; UnityActions.Enqueue(queuedAction); int num2; for (int num = 0; num < 5000; num += num2) { if (queuedAction.Done.Task.IsCompleted) { break; } if (token.IsCancellationRequested) { return default(T); } num2 = Math.Min(100, 5000 - num); queuedAction.Done.Task.Wait(num2); } if (!queuedAction.Done.Task.IsCompleted) { queuedAction.Abandoned = true; return default(T); } object result = queuedAction.Done.Task.Result; if (result is T) { return (T)result; } return default(T); } internal static void PostToUnityThread(Action work) { UnityActions.Enqueue(new QueuedAction { Work = delegate { work(); return (object)null; }, Done = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously) }); } public static ulong GetRemotePeerSteamId(string sourceKey) { try { if (string.IsNullOrEmpty(sourceKey)) { return 0uL; } string[] array = sourceKey.Split(':'); if (array.Length < 2 || array[0] != "sdr") { return 0uL; } if (ulong.TryParse(array[1], out var result)) { return result; } string text = array[1]; if (text.Length < 2 || text[0] != 'c' || !uint.TryParse(text.Substring(1), out var result2)) { return 0uL; } lock (Sync) { foreach (HostListener host in Hosts) { if (host.RemoteSteamIds.TryGetValue(result2, out var value)) { return value; } } } } catch { } return 0uL; } public static void StartLoopback(int socketId, int port, Action<byte[]> sink) { lock (Sync) { foreach (LoopbackPipe loopback in Loopbacks) { if (loopback.SocketId == socketId) { loopback.Port = port; loopback.Key = $"loopback:{socketId}:{port}"; loopback.Sink = sink; return; } } Loopbacks.Add(new LoopbackPipe { SocketId = socketId, Port = port, Key = $"loopback:{socketId}:{port}", Sink = sink }); } StartHost(); } public static void LoopbackSend(int socketId, int port, byte[] datagram) { lock (Sync) { foreach (LoopbackPipe loopback in Loopbacks) { if (loopback.SocketId == socketId) { loopback.ClientToServer.Enqueue(datagram); break; } } } } public static void UnregisterLoopbackSink(int socketId) { lock (Sync) { for (int num = Loopbacks.Count - 1; num >= 0; num--) { if (Loopbacks[num].SocketId == socketId) { Loopbacks.RemoveAt(num); } } } } public static bool StartHost() { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) lock (Sync) { if (Hosts.Count > 0) { return true; } } if (!ModConfig.IsHostRole()) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SDR] not the host role - remote-facing stack not raised"); } return false; } if (!SteamClient.IsValid) { Plugin.Log.LogError((object)"[SDR] SteamClient is not initialized; cannot host."); return false; } try { _hostToken = GenerateHostToken(); _hostTokenPublished = false; int[] array = new int[3] { 7777, 7778, 7779 }; foreach (int num in array) { HostListener hostListener = SteamNetworkingSockets.CreateRelaySocket<HostListener>(num); hostListener.VirtualPort = num; lock (Sync) { Hosts.Add(hostListener); } } HostSteamId = SteamId.op_Implicit(SteamClient.SteamId); if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR] host listening on relay ports {7777}/{7778}/{7779} (SteamId {HostSteamId})"); } LanDiscovery.StartHostResponder(HostSteamId, _hostToken); int[] array2 = new int[3] { 7777, 7778, 7779 }; foreach (int port in array2) { StartUdpListener(port); } return true; } catch (Exception arg) { Plugin.Log.LogError((object)$"[SDR] failed to create relay socket: {arg}"); return false; } } private static void StartUdpListener(int port) { try { UdpClient item = new UdpClient(port); lock (Sync) { UdpListeners.Add(item); } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR] direct UDP listener on :{port} (LAN / local test mode)"); } } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SDR] UDP listener on :{port} unavailable: {ex.Message}"); } } } internal static bool UdpPhaseListenersBound() { lock (Sync) { return UdpListeners.Count >= 3; } } public static void StopHost() { HostListener[] array; UdpClient[] array2; string[] array3; lock (Sync) { array = Hosts.ToArray(); Hosts.Clear(); array2 = UdpListeners.ToArray(); UdpListeners.Clear(); UdpAllowlist.Clear(); array3 = new string[UdpSessionKeys.Count]; UdpSessionKeys.CopyTo(array3); UdpSessionKeys.Clear(); _hostToken = null; _hostTokenPublished = false; _hostLobbyToken = null; _ztPublishTick = Environment.TickCount - 3000; (string, byte[], Action<byte[]>, string) result; while (UdpInbox.TryDequeue(out result)) { Interlocked.Decrement(ref _udpInboxCount); } } HostListener[] array4 = array; foreach (HostListener hostListener in array4) { foreach (KeyValuePair<uint, Connection> connection in hostListener.Connections) { VirtualServer.EvictSession($"sdr:c{connection.Key}:{hostListener.VirtualPort}", "host stack stopped"); } hostListener.Connections.Clear(); hostListener.RemoteSteamIds.Clear(); RemoteDatagram result2; while (hostListener.Inbox.TryDequeue(out result2)) { } try { ((SocketManager)hostListener).Close(); } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SDR] relay socket :{hostListener.VirtualPort} close failed: {ex.Message}"); } } } string[] array5 = array3; foreach (string sourceKey in array5) { VirtualServer.EvictSession(sourceKey, "host stack stopped"); } UdpClient[] array6 = array2; foreach (UdpClient udpClient in array6) { try { udpClient.Close(); } catch (Exception ex2) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[SDR] udp listener close failed: " + ex2.Message)); } } } LanDiscovery.StopHostResponder(); LanDiscovery.InvalidateCache(); if (array.Length != 0 && Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SDR] host stack stopped: relay sockets closed, UDP listeners unbound, LAN responder stopped, allowlist cleared"); } } private static string GenerateHostToken() { byte[] array = new byte[8]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } StringBuilder stringBuilder = new StringBuilder(16); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2")); } return stringBuilder.ToString(); } internal static bool TryGetHostLanToken(out string token) { token = _hostToken; if (!string.IsNullOrEmpty(token)) { return _hostTokenPublished; } return false; } private static void RefreshHostLobbyToken() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) try { if (ModConfig.TryGetLobby(out var lobby)) { SteamId id = ((Lobby)(ref lobby)).Id; if (((SteamId)(ref id)).IsValid) { string data = ((Lobby)(ref lobby)).GetData("rtok"); _hostLobbyToken = (string.IsNullOrEmpty(data) ? null : data); return; } } _hostLobbyToken = null; } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[SDR] rtok refresh failed: " + ex.Message)); } } } public static string GetHostLobbyToken() { return _hostLobbyToken; } private static void PumpZtPublish() { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) int tickCount = Environment.TickCount; if (tickCount - _ztPublishTick < 3000) { return; } RefreshHostLobbyToken(); lock (Sync) { if (Hosts.Count == 0) { return; } } _ztPublishTick = tickCount; try { if (ModConfig.TryGetLobby(out var lobby)) { SteamId id = ((Lobby)(ref lobby)).Id; if (((SteamId)(ref id)).IsValid) { string zeroTierIp = ModConfig.GetZeroTierIp(); if (!string.IsNullOrEmpty(zeroTierIp)) { string data = ((Lobby)(ref lobby)).GetData("ztip"); if (data != zeroTierIp && ((Lobby)(ref lobby)).SetData("ztip", zeroTierIp) && Plugin.Verbose) { Plugin.Log.LogInfo((object)("[ZT] published ZeroTier address to lobby: " + zeroTierIp)); } } string hostToken = _hostToken; if (!string.IsNullOrEmpty(hostToken)) { string data2 = ((Lobby)(ref lobby)).GetData("rtok"); if (data2 == hostToken) { _hostTokenPublished = true; } else if (((Lobby)(ref lobby)).SetData("rtok", hostToken)) { _hostTokenPublished = true; if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[ZT] published host session token to lobby (rtok)"); } } } } } } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[ZT] lobby publish failed: " + ex.Message)); } } SweepUdpAllowlist(tickCount); } private static void LogUdpDropVerbose(string message) { int tickCount = Environment.TickCount; if (tickCount - _udpDropLogTick >= 2000) { _udpDropLogTick = tickCount; if (Plugin.Verbose) { Plugin.Log.LogWarning((object)message); } } } private static bool IsUdpHello(byte[] datagram) { if (datagram == null || datagram.Length < UdpHelloPrefixBytes.Length) { return false; } for (int i = 0; i < UdpHelloPrefixBytes.Length; i++) { if (datagram[i] != UdpHelloPrefixBytes[i]) { return false; } } return true; } private static bool UdpPeerAllowed(string peerKey, int now) { lock (Sync) { int value; return UdpAllowlist.TryGetValue(peerKey, out value) && now - value <= 300000; } } private static void AuthorizeUdpPeer(string peerKey, int now) { lock (Sync) { UdpAllowlist[peerKey] = now; } } private static void SweepUdpAllowlist(int now) { lock (Sync) { if (UdpAllowlist.Count == 0) { return; } List<string> list = null; foreach (KeyValuePair<string, int> item in UdpAllowlist) { if (now - item.Value > 300000) { (list ?? (list = new List<string>())).Add(item.Key); } } if (list == null) { return; } foreach (string item2 in list) { UdpAllowlist.Remove(item2); } } } private static void HandleUdpHello(UdpClient listener, IPEndPoint peer, byte[] datagram, int now, int listenPort) { string text = Encoding.ASCII.GetString(datagram, UdpHelloPrefixBytes.Length, datagram.Length - UdpHelloPrefixBytes.Length); if (TryGetHostLanToken(out var token) && text != token) { LogUdpDropVerbose($"[SDR] udp hello rejected (token mismatch): {peer} on :{listenPort}"); return; } string text2 = peer.ToString(); AuthorizeUdpPeer(text2, now); try { byte[] bytes = Encoding.ASCII.GetBytes("RREPO2|" + HostSteamId); listener.Send(bytes, bytes.Length, peer); } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[SDR] udp hello ack failed: " + ex.Message)); } } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR] udp hello accepted: {text2} on :{listenPort} (allowlisted, window {300}s)"); } } public static void PumpUdpLoop() { while (true) { try { PumpUdp(); } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[udp] background pump failed: " + ex.Message)); } } Thread.Sleep(2); } } private static void PumpUdp() { UdpClient[] array; lock (Sync) { array = UdpListeners.ToArray(); } UdpClient[] array2 = array; foreach (UdpClient listener in array2) { try { PumpUdpListener(listener); } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[SDR] udp pump listener skipped: " + ex.Message)); } } } } private static void PumpUdpListener(UdpClient listener) { int port = ((IPEndPoint)listener.Client.LocalEndPoint).Port; while (listener.Available > 0) { try { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] array = listener.Receive(ref remoteEP); int tickCount = Environment.TickCount; if (IsUdpHello(array)) { HandleUdpHello(listener, remoteEP, array, tickCount, port); continue; } string text = remoteEP.ToString(); if (!UdpPeerAllowed(text, tickCount)) { LogUdpDropVerbose($"[SDR] udp drop (endpoint not allowlisted): {text} on :{port}"); continue; } AuthorizeUdpPeer(text, tickCount); string item = ResolveAdvertiseIp(remoteEP.Address); string text2 = $"udp:{port}:{remoteEP}"; IPEndPoint peer = remoteEP; lock (Sync) { UdpSessionKeys.Add(text2); } if (Interlocked.Increment(ref _udpInboxCount) > 256) { Interlocked.Decrement(ref _udpInboxCount); LogUdpDropVerbose($"[SDR] udp inbox full (>{256}) - datagram dropped: {text} on :{port}"); } else { UdpInbox.Enqueue((text2, array, delegate(byte[] b) { listener.Send(b, b.Length, peer); }, item)); } } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogError((object)("[SDR] udp pump: " + ex.Message)); } break; } } } private static string ResolveAdvertiseIp(IPAddress remote) { int tickCount = Environment.TickCount; if (_localV4Cache == null || tickCount - _localV4CacheTick > 10000) { List<IPAddress> list = new List<IPAddress>(); try { NetworkInterface[] allNetworkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); foreach (NetworkInterface networkInterface in allNetworkInterfaces) { if (networkInterface.OperationalStatus != OperationalStatus.Up) { continue; } foreach (UnicastIPAddressInformation unicastAddress in networkInterface.GetIPProperties().UnicastAddresses) { if (unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork && !IPAddress.Loopback.Equals(unicastAddress.Address)) { list.Add(unicastAddress.Address); } } } } catch { } _localV4Cache = list; _localV4CacheTick = tickCount; } if (remote != null && remote.AddressFamily == AddressFamily.InterNetwork) { byte[] addressBytes = remote.GetAddressBytes(); if (addressBytes.Length == 4) { foreach (IPAddress item in _localV4Cache) { byte[] addressBytes2 = item.GetAddressBytes(); if (addressBytes2.Length == 4 && addressBytes2[0] == addressBytes[0] && addressBytes2[1] == addressBytes[1] && addressBytes2[2] == addressBytes[2]) { return item.ToString(); } } } } if (_localV4Cache.Count <= 0) { return null; } return _localV4Cache[0].ToString(); } public static void Pump() { //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Unknown result type (might be due to invalid IL or missing references) //IL_0451: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Unknown result type (might be due to invalid IL or missing references) int tickCount = Environment.TickCount; int num = tickCount - _lastPumpTick; _lastPumpTick = tickCount; if (num > 2000) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR] long frame {num}ms - eviction/give-up timers suspended for one cycle"); } VirtualServer.SuspendTimersOneCycle(); } int num2 = 0; (string, byte[], Action<byte[]>, string) result; while (num2 < 128 && UdpInbox.TryDequeue(out result)) { Interlocked.Decrement(ref _udpInboxCount); num2++; try { VirtualServer.HandleClientDatagram(result.Item1, result.Item2, result.Item3, result.Item4); } catch (Exception arg) { Plugin.Log.LogError((object)$"[SDR] udp datagram processing failed ({result.Item1}): {arg}"); } } QueuedAction result2; while (UnityActions.TryDequeue(out result2)) { if (result2.Abandoned) { result2.Done.TrySetResult(null); continue; } object result3 = null; try { result3 = result2.Work(); } catch (Exception arg2) { Plugin.Log.LogError((object)$"[SDR] queued steam action failed: {arg2}"); } result2.Done.TrySetResult(result3); } HostListener[] array; HostConnection[] array2; lock (Sync) { array = Hosts.ToArray(); array2 = Clients.ToArray(); } LoopbackPipe[] array3; lock (Sync) { array3 = Loopbacks.ToArray(); } LoopbackPipe[] array4 = array3; foreach (LoopbackPipe loopbackPipe in array4) { if (loopbackPipe.ClientToServer.IsEmpty) { continue; } string key = loopbackPipe.Key; Action<byte[]> sink = loopbackPipe.Sink; byte[] result4; while (loopbackPipe.ClientToServer.TryDequeue(out result4)) { if (sink != null) { VirtualServer.HandleClientDatagram(key, result4, delegate(byte[] b) { sink(b); }); } } } try { int num3 = 0; HostListener[] array5 = array; foreach (HostListener hostListener in array5) { num3 += ((SocketManager)hostListener).Receive(32, true); } HostConnection[] array6 = array2; foreach (HostConnection hostConnection in array6) { num3 += ((ConnectionManager)hostConnection).Receive(32, true); } if (Environment.TickCount - _connDumpTick > 2000 && ConnectionsAny()) { _connDumpTick = Environment.TickCount; HostListener[] array7 = array; foreach (HostListener hostListener2 in array7) { foreach (KeyValuePair<uint, Connection> connection in hostListener2.Connections) { try { if (Plugin.Verbose) { ManualLogSource log = Plugin.Log; object arg3 = hostListener2.VirtualPort; object arg4 = connection.Key; Connection value = connection.Value; log.LogInfo((object)$"[SDR diag] :{arg3} handle={arg4}\n{((Connection)(ref value)).DetailedStatus()}"); } } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR diag] :{hostListener2.VirtualPort} handle={connection.Key} status query failed: {ex.Message}"); } } } } } if (num3 > 0) { _receivedBatches++; if (_receivedBatches <= 8 && Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR] receive pump: {num3} message(s) pushed to callbacks"); } } } catch (Exception ex2) { if (_pumpErrorCount < 3 && Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SDR] receive pump failed: {ex2}\n{ex2.StackTrace}"); } _pumpErrorCount++; } HostListener[] array8; HostConnection[] array9; lock (Sync) { array8 = Hosts.ToArray(); array9 = Clients.ToArray(); } HostListener[] array10 = array8; foreach (HostListener hostListener3 in array10) { RemoteDatagram result5; while (hostListener3.Inbox.TryDequeue(out result5)) { Connection conn = result5.Connection; VirtualServer.HandleClientDatagram(result5.SourceKey, result5.Data, delegate(byte[] b) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) ((Connection)(ref conn)).SendMessage(b, (SendType)1, (ushort)0); }); } } HostConnection[] array11 = array9; foreach (HostConnection hostConnection2 in array11) { Action<byte[]> dataSink = hostConnection2.DataSink; if (dataSink != null) { byte[] result6; while (hostConnection2.Inbox.TryDequeue(out result6)) { dataSink(result6); } } } VirtualServer.PumpSessions(); PumpZtPublish(); } public static HostConnection ConnectClient(ulong hostSteamId, int virtualPort) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!SteamClient.IsValid) { Plugin.Log.LogError((object)"[SDR] SteamClient is not initialized; cannot connect."); return null; } if (hostSteamId == 0L) { Plugin.Log.LogError((object)"[SDR] no host SteamId configured and no Steam lobby owner to resolve."); return null; } try { SteamNetworkingUtils.ConnectionTimeout = 30000; SteamNetworkingUtils.Timeout = 30000; HostConnection hostConnection = SteamNetworkingSockets.ConnectRelay<HostConnection>(SteamId.op_Implicit(hostSteamId), virtualPort); lock (Sync) { Clients.Add(hostConnection); } HostSteamId = hostSteamId; if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR] connecting to host {hostSteamId} on virtual port {virtualPort} (timeout 30s)..."); } return hostConnection; } catch (Exception arg) { Plugin.Log.LogError((object)$"[SDR] failed to start relay connect: {arg}"); return null; } } public static void ClientDied(HostConnection mgr) { lock (Sync) { Clients.Remove(mgr); } } public static void DisconnectClient(HostConnection mgr) { if (mgr == null) { return; } lock (Sync) { Clients.Remove(mgr); } PostToUnityThread(delegate { try { ((ConnectionManager)mgr).Close(false, 0, "Closing Connection"); } catch (Exception arg) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SDR] client close failed: {arg}"); } } }); } public static void CloseHostConnection(string sourceKey, string reason) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(sourceKey)) { return; } string[] array = sourceKey.Split(':'); if (array.Length < 3 || array[0] != "sdr") { return; } string text = array[1]; if (text.Length < 2 || text[0] != 'c' || !uint.TryParse(text.Substring(1), out var result) || !int.TryParse(array[2], out var result2)) { return; } lock (Sync) { foreach (HostListener host in Hosts) { if (host.VirtualPort == result2 && host.Connections.TryGetValue(result, out var value)) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SDR host:{result2}] closing rejected connection {result}: {reason}"); } ((Connection)(ref value)).Flush(); ((Connection)(ref value)).Close(false, 0, reason); } } } } private static object ResolveSingleton() { try { return typeof(SteamNetworkingSockets).GetProperty("Internal", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); } catch { return null; } } private static bool ConnectionsAny() { lock (Sync) { foreach (HostListener host in Hosts) { if (host.Connections.Count > 0) { return true; } } } return false; } } public class SteamSdrSocket : IPhotonSocket { private enum Mode { None, Loopback, Client, Udp } private volatile Mode _mode; private volatile HostConnection _clientMgr; private volatile UdpClient _udp; private int _phasePort = 7777; private ulong _hostId; private volatile string _hostToken; private volatile bool _udpHelloSent; private int _retries; private readonly int _socketId = ++_nextSocketId; private static int _nextSocketId; private Action<byte[]>? _loopbackSink; private readonly ConcurrentQueue<byte[]> _inbound = new ConcurrentQueue<byte[]>(); private readonly object _stateLock = new object(); private CancellationTokenSource _connectCts; private Thread _connectWorker; private Thread _pumpThread; private Thread _udpThread; private int _sendsLogged; private int _skipsLogged; private const int UdpProbeTimeoutMs = 500; public SteamSdrSocket(PeerBase peer) : base(peer) { if (((IPhotonSocket)this).ReportDebugOfLevel((DebugLevel)3)) { ((IPhotonSocket)this).EnqueueDebugReturn((DebugLevel)3, "SteamSdrSocket created"); } } public override bool Connect() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) lock (_stateLock) { if ((int)((IPhotonSocket)this).State != 0) { ((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, "SteamSdrSocket.Connect(): already in state " + ((object)((IPhotonSocket)this).State/*cast due to .constrained prefix*/).ToString()); return false; } } string text = base.peerBase.ServerAddress ?? ""; if (Plugin.Verbose) { Plugin.Log.LogInfo((object)("[SteamSdrSocket] connecting to '" + text + "'")); } if (text.StartsWith("sdr://", StringComparison.OrdinalIgnoreCase)) { string text2 = text.Substring("sdr://".Length).Trim(); int port = 7777; int num = text2.LastIndexOf(':'); if (num > 0 && int.TryParse(text2.Substring(num + 1), out var result)) { port = result; text2 = text2.Substring(0, num); } _phasePort = port; if (IPAddress.TryParse(text2, out IPAddress address)) { _mode = Mode.Udp; _hostToken = SdrFabric.GetHostLobbyToken(); try { _udp = new UdpClient(0); _udp.Connect(address, port); } catch (Exception arg) { ((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, $"SteamSdrSocket: UDP connect failed: {arg}"); return false; } _udpThread = new Thread(UdpReceiveLoop) { IsBackground = true, Name = "SteamSdrSocket udp-recv" }; _udpThread.Start(); OnEstablished(); return true; } if (text2.Equals("loopback", StringComparison.OrdinalIgnoreCase)) { _mode = Mode.Loopback; _loopbackSink = DeliverFromServer; SdrFabric.StartLoopback(_socketId, port, _loopbackSink); OnEstablished(); return true; } if (ulong.TryParse(text2, out var hostId) && hostId != 0L) { if (SteamClient.IsValid && hostId == SteamId.op_Implicit(SteamClient.SteamId)) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] target is our own SteamId -> loopback (host peer)"); } _mode = Mode.Loopback; _loopbackSink = DeliverFromServer; SdrFabric.StartLoopback(_socketId, port, _loopbackSink); OnEstablished(); return true; } lock (_stateLock) { _connectCts?.Cancel(); _connectCts = new CancellationTokenSource(); CancellationToken token = _connectCts.Token; _hostId = hostId; _connectWorker = new Thread((ThreadStart)delegate { ConnectLadderWorker(hostId, port, token); }) { IsBackground = true, Name = "SteamSdrSocket connect-ladder" }; ((IPhotonSocket)this).State = (PhotonSocketState)1; _connectWorker.Start(); } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] connect ladder handed to background worker (steamId {hostId}, port {port})"); } return true; } } ((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, "SteamSdrSocket: unsupported address '" + text + "' (expected sdr://loopback or sdr://<steamid64>)"); return false; } private bool LadderCancelled(CancellationToken token) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 if (token.IsCancellationRequested) { return true; } lock (_stateLock) { return (int)((IPhotonSocket)this).State != 1; } } private void ConnectLadderWorker(ulong hostId, int port, CancellationToken token) { //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Invalid comparison between Unknown and I4 try { _hostToken = SdrFabric.RunOnUnityThread(() => SdrFabric.GetHostLobbyToken(), token); if (LadderCancelled(token)) { return; } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] ladder 1/3: LAN discovery for steamId {hostId} started on background worker (up to 2500 ms)"); } string text = LanDiscovery.FindHostCached(hostId, 2500, _hostToken, () => token.IsCancellationRequested); if (LadderCancelled(token)) { return; } if (!string.IsNullOrEmpty(text) && IPAddress.TryParse(text, out IPAddress address)) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] ladder 1/3: LAN host discovered at {text} -> probing (steamId {hostId})"); } UdpClient udpClient = ProbeUdpEndpoint(address, port, _hostToken); if (udpClient != null) { _udpHelloSent = true; EstablishUdpFromWorker(udpClient, "SteamSdrSocket udp-recv", token); return; } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] ladder 1/3: LAN probe failed -> next ladder step"); } } string text2 = SdrFabric.RunOnUnityThread(() => ModConfig.GetZeroTierFallbackIp(), token); if (!string.IsNullOrEmpty(text2) && IPAddress.TryParse(text2, out IPAddress address2)) { UdpClient udpClient2 = ProbeUdpEndpoint(address2, port, _hostToken); if (udpClient2 != null) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] ladder 2/3: ZeroTier tunnel {text2}:{port} reachable (probe ok) -> direct UDP"); } _udpHelloSent = true; EstablishUdpFromWorker(udpClient2, "SteamSdrSocket udp-recv-zerotier", token); return; } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] ladder 2/3: ZeroTier probe failed -> Steam relay"); } } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] ladder 3/3: no LAN host, ZeroTier unreachable -> Steam relay"); } HostConnection hostConnection = SdrFabric.RunOnUnityThread(() => SdrFabric.ConnectClient(hostId, port), token); if (token.IsCancellationRequested) { if (hostConnection != null) { SdrFabric.DisconnectClient(hostConnection); } return; } if (hostConnection == null) { WorkerFailed("SteamSdrSocket: failed to start SDR client connect"); return; } hostConnection.Owner = this; hostConnection.OnEstablished = OnEstablished; hostConnection.OnClosed = OnRemoteClosed; hostConnection.DataSink = DeliverFromServer; bool flag = false; lock (_stateLock) { if ((int)((IPhotonSocket)this).State == 1 && !token.IsCancellationRequested) { _clientMgr = hostConnection; _mode = Mode.Client; flag = true; } } if (!flag) { hostConnection.Owner = null; SdrFabric.DisconnectClient(hostConnection); } } catch (Exception arg) { WorkerFailed($"SteamSdrSocket: connect ladder crashed: {arg}"); } } private bool EstablishUdpFromWorker(UdpClient udp, string recvThreadName, CancellationToken token) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 lock (_stateLock) { if ((int)((IPhotonSocket)this).State != 1 || token.IsCancellationRequested) { try { udp.Close(); } catch { } return false; } _udp = udp; _mode = Mode.Udp; _udpThread = new Thread(UdpReceiveLoop) { IsBackground = true, Name = recvThreadName }; _udpThread.Start(); ((IPhotonSocket)this).State = (PhotonSocketState)2; if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] transport established -> Photon peer Connected"); } base.peerBase.OnConnect(); _pumpThread = new Thread(PumpLoop) { IsBackground = true, Name = "SteamSdrSocket pump" }; _pumpThread.Start(); return true; } } private void WorkerFailed(string reason) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 lock (_stateLock) { if ((int)((IPhotonSocket)this).State != 1) { return; } _mode = Mode.None; ((IPhotonSocket)this).State = (PhotonSocketState)0; } ((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, reason); ((IPhotonSocket)this).HandleException((StatusCode)1023); } private static UdpClient ProbeUdpEndpoint(IPAddress addr, int port, string token) { UdpClient udpClient = null; try { udpClient = new UdpClient(0); udpClient.Connect(addr, port); udpClient.Client.ReceiveTimeout = 500; byte[] bytes = Encoding.ASCII.GetBytes("RREPO1|" + token); udpClient.Send(bytes, bytes.Length); IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] array = udpClient.Receive(ref remoteEP); if (array != null && AsciiStartsWith(array, "RREPO2|")) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] probe {addr}:{port} answered (hello ack) -> endpoint reachable and allowlisted"); } return udpClient; } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] probe {addr}:{port} answered without a hello ack -> treated as a miss"); } try { udpClient.Close(); } catch (Exception ex) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)("[SteamSdrSocket] probe socket close failed: " + ex.Message)); } } return null; } catch (Exception ex2) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] probe {addr}:{port} failed: {ex2.Message}"); } try { udpClient?.Close(); } catch (Exception ex3) { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)("[SteamSdrSocket] probe socket close failed: " + ex3.Message)); } } return null; } } private static bool AsciiStartsWith(byte[] data, string prefix) { if (data == null || data.Length < prefix.Length) { return false; } for (int i = 0; i < prefix.Length; i++) { if (data[i] != (byte)prefix[i]) { return false; } } return true; } public bool RetryConnect() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 if (_mode != Mode.Client) { return false; } lock (_stateLock) { if ((int)((IPhotonSocket)this).State != 1) { return false; } } if (++_retries > 2) { string text = ((ModConfig.GetZeroTierIp() != null) ? ModConfig.GetZeroTierFallbackIp() : null); if (!string.IsNullOrEmpty(text) && IPAddress.TryParse(text, out IPAddress _)) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Steam P2P failed after {_retries - 1} attempt(s) -> ZeroTier fallback {text}:{_phasePort}"); } if (_clientMgr != null) { _clientMgr.Owner = null; } _mode = Mode.Udp; _hostToken = SdrFabric.GetHostLobbyToken(); try { _udp = new UdpClient(0); _udp.Connect(text, _phasePort); } catch (Exception arg) { ((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)1, $"SteamSdrSocket: ZeroTier UDP connect failed: {arg}"); _mode = Mode.None; return false; } _clientMgr = null; _udpThread = new Thread(UdpReceiveLoop) { IsBackground = true, Name = "SteamSdrSocket udp-recv-zerotier" }; _udpThread.Start(); OnEstablished(); return true; } Plugin.Log.LogError((object)$"[SteamSdrSocket] Steam P2P connect failed after {_retries - 1} attempt(s), no ZeroTier fallback available"); return false; } lock (_stateLock) { _connectCts?.Cancel(); _connectCts = new CancellationTokenSource(); CancellationToken token = _connectCts.Token; ulong hostId = _hostId; int port = _phasePort; _connectWorker = new Thread((ThreadStart)delegate { RetryWorker(hostId, port, token); }) { IsBackground = true, Name = "SteamSdrSocket relay-retry" }; _connectWorker.Start(); } if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Steam P2P retry #{_retries} scheduled on background worker -> sdr://{_hostId}:{_phasePort}"); } return true; } private void RetryWorker(ulong hostId, int port, CancellationToken token) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Invalid comparison between Unknown and I4 int num = 1200; while (num > 0 && !token.IsCancellationRequested) { Thread.Sleep((num >= 100) ? 100 : num); num -= 100; } if (token.IsCancellationRequested) { return; } HostConnection hostConnection = SdrFabric.RunOnUnityThread(() => SdrFabric.ConnectClient(hostId, port), token); if (token.IsCancellationRequested) { if (hostConnection != null) { SdrFabric.DisconnectClient(hostConnection); } return; } if (hostConnection == null) { OnRemoteClosed(); return; } hostConnection.Owner = this; hostConnection.OnEstablished = OnEstablished; hostConnection.OnClosed = OnRemoteClosed; hostConnection.DataSink = DeliverFromServer; bool flag = false; lock (_stateLock) { if ((int)((IPhotonSocket)this).State == 1 && !token.IsCancellationRequested) { _clientMgr = hostConnection; _mode = Mode.Client; flag = true; } } if (!flag) { hostConnection.Owner = null; SdrFabric.DisconnectClient(hostConnection); } else if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Steam P2P retry #{_retries} -> sdr://{hostId}:{port}"); } } private void OnEstablished() { lock (_stateLock) { ((IPhotonSocket)this).State = (PhotonSocketState)2; } if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] transport established -> Photon peer Connected"); } base.peerBase.OnConnect(); _pumpThread = new Thread(PumpLoop) { IsBackground = true, Name = "SteamSdrSocket pump" }; _pumpThread.Start(); } private void PumpLoop() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Invalid comparison between Unknown and I4 while ((int)((IPhotonSocket)this).State == 2) { if (_inbound.TryDequeue(out var result)) { try { ((IPhotonSocket)this).HandleReceivedDatagram(result, result.Length, false); } catch (Exception arg) { Plugin.Log.LogError((object)$"[SteamSdrSocket] pump delivery failed: {arg}"); } } else { Thread.Sleep(2); } } } private void UdpReceiveLoop() { while (_mode == Mode.Udp) { UdpClient udp = _udp; if (udp == null) { break; } try { IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); byte[] datagram = udp.Receive(ref remoteEP); DeliverFromServer(datagram); } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.Interrupted) { break; } if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[SteamSdrSocket] udp recv: " + ex.Message)); } } catch (ObjectDisposedException) { break; } catch (Exception arg) { Plugin.Log.LogError((object)$"[SteamSdrSocket] udp recv loop: {arg}"); } } } private void OnRemoteClosed() { ((IPhotonSocket)this).Listener.DebugReturn((DebugLevel)2, "SteamSdrSocket: SDR connection closed by remote"); ((IPhotonSocket)this).HandleException((StatusCode)1043); } public override bool Disconnect() { if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] Disconnect()"); } Mode mode; HostConnection clientMgr; UdpClient udp; lock (_stateLock) { ((IPhotonSocket)this).State = (PhotonSocketState)3; _connectCts?.Cancel(); _connectCts = null; mode = _mode; clientMgr = _clientMgr; udp = _udp; _clientMgr = null; _udp = null; } try { if (mode == Mode.Client) { if (clientMgr != null) { clientMgr.Owner = null; } SdrFabric.DisconnectClient(clientMgr); } if (mode == Mode.Loopback) { SdrFabric.UnregisterLoopbackSink(_socketId); } if (mode == Mode.Udp) { try { udp?.Close(); } catch { } } } catch (Exception arg) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SteamSdrSocket] exception during disconnect: {arg}"); } } lock (_stateLock) { _mode = Mode.None; ((IPhotonSocket)this).State = (PhotonSocketState)0; } return true; } public override PhotonSocketError Send(byte[] data, int length) { //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Invalid comparison between Unknown and I4 //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) try { switch (_mode) { case Mode.Loopback: SdrFabric.LoopbackSend(_socketId, _phasePort, CopyOf(data, length)); return (PhotonSocketError)0; case Mode.Client: { HostConnection clientMgr = _clientMgr; if (clientMgr == null || !((ConnectionManager)clientMgr).Connected) { if (_skipsLogged < 8) { _skipsLogged++; if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SteamSdrSocket] Send skipped: no live SDR connection (mgr={clientMgr != null}, connected={((ConnectionManager)(clientMgr?)).Connected})"); } } return (PhotonSocketError)1; } byte[] array = CopyOf(data, length); Result val = ((Connection)(ref ((ConnectionManager)clientMgr).Connection)).SendMessage(array, (SendType)1, (ushort)0); if (_sendsLogged < 8) { _sendsLogged++; if (Plugin.Verbose) { Plugin.Log.LogInfo((object)$"[SteamSdrSocket] Send #{_sendsLogged}: {length}B -> host, result={val}, connState={((ConnectionManager)clientMgr).Connected}"); } } if ((int)val != 1) { if (Plugin.Verbose) { Plugin.Log.LogWarning((object)$"[SteamSdrSocket] SendMessage result: {val}"); } return (PhotonSocketError)1; } return (PhotonSocketError)0; } case Mode.Udp: { UdpClient udp = _udp; if (udp == null) { return (PhotonSocketError)1; } try { if (!_udpHelloSent) { _udpHelloSent = true; byte[] bytes = Encoding.ASCII.GetBytes("RREPO1|" + _hostToken); udp.Send(bytes, bytes.Length); if (Plugin.Verbose) { Plugin.Log.LogInfo((object)"[SteamSdrSocket] udp hello sent (transport authorization)"); } } udp.Send(data, length); } catch (Exception ex) { if (_skipsLogged < 8) { _skipsLogged++; if (Plugin.Verbose) { Plugin.Log.LogWarning((object)("[