#define TRACE using System; using System.Buffers; using System.Buffers.Binary; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; 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 BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using NetworkingLibrary.Features; using NetworkingLibrary.Modules; using NetworkingLibrary.Services; using Photon.Pun; using Photon.Realtime; using Steamworks; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("NetworkingLibrary.Tests")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("AeralisFoundation")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Networking library for mods using Steam instead of PUN.")] [assembly: AssemblyFileVersion("1.0.9.0")] [assembly: AssemblyInformationalVersion("1.0.9+0f1c2d7274cf8f619512eace85de587430684362")] [assembly: AssemblyProduct("NetworkingLibrary")] [assembly: AssemblyTitle("AeralisFoundation.NetworkingLibrary")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.9.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace NetworkingLibrary { [BepInPlugin("AeralisFoundation.NetworkingLibrary", "NetworkingLibrary", "1.0.9")] public class Net : BaseUnityPlugin { internal static Harmony? Harmony; public ConfigFile config; internal static Func<(INetworkingService service, DefaultServiceSelectionReason reason)> CreateDefaultNetworkingServiceWithReason = delegate { DefaultServiceSelectionReason reason; INetworkingService item = NetworkingServiceFactory.CreateDefaultServiceWithReason(out reason); return (item, reason); }; internal static Func CreateOfflineNetworkingService = () => new OfflineNetworkingService(); internal static Func IsApplicationPlaying = () => Application.isPlaying; internal static Action DestroyObject = Object.Destroy; internal static Action DestroyObjectImmediate = Object.DestroyImmediate; internal static Func RealtimeSinceStartupProvider = () => Time.realtimeSinceStartup; internal static Func WaitForSecondsRealtimeFactory = (float seconds) => (object?)new WaitForSecondsRealtime(seconds); private const string StartupRetryLogSource = "Net.StartupRetry"; private const string StartupRetryTransitionLogKey = "Net.StartupRetry.Transition"; private const float StartupRetryTransitionLogCooldownSeconds = 1.5f; private const float StartupRetryWindowSeconds = 10f; private const float StartupRetryInitialDelaySeconds = 0.5f; private const float StartupRetryMaxDelaySeconds = 3f; private const float StartupRetryBackoffMultiplier = 1.8f; public static Net Instance { get; private set; } = null; internal static ManualLogSource Logger { get; private set; } = null; public static INetworkingService? Service { get; private set; } private void OnDestroy() { try { Service?.Shutdown(); } catch (Exception ex) { ManualLogSource logger = Logger; if (logger != null) { logger.LogWarning((object)("Failed to shutdown networking service during plugin teardown: " + ex.Message)); } } finally { Service = null; ResetNetworkingStartupHooks(); Message.ResetSerializersForTests(); } try { Harmony harmony = Harmony; if (harmony != null) { harmony.UnpatchSelf(); ManualLogSource logger2 = Logger; if (logger2 != null) { logger2.LogDebug((object)"Removed Harmony patches during plugin teardown."); } } } catch (Exception ex2) { ManualLogSource logger3 = Logger; if (logger3 != null) { logger3.LogWarning((object)("Failed to unpatch Harmony during plugin teardown: " + ex2.Message)); } } finally { Harmony = null; Instance = null; } } private void Awake() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Expected O, but got Unknown //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) Logger = ((BaseUnityPlugin)this).Logger; Instance = this; FileManager.InitializeConfig(); try { if (Harmony == null) { Harmony = new Harmony("AeralisFoundation.NetworkingLibrary"); } Harmony.PatchAll(); } catch (Exception arg) { Logger.LogError((object)$"Failed to apply Harmony patches: {arg}"); } Service = null; TryInitializeNetworkingService(Logger, out INetworkingService service, out DefaultServiceSelectionReason defaultSelectionReason); Service = service; if (defaultSelectionReason == DefaultServiceSelectionReason.SteamApiNotReady && Service is OfflineNetworkingService) { ((MonoBehaviour)this).StartCoroutine(RetrySteamInitializationForStartupWindow(10f)); } string text = "NetworkingLibrary.Poller"; List list = (from poller in Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0) orderby ((Behaviour)poller).isActiveAndEnabled descending, ((Component)poller).gameObject.activeInHierarchy descending, ((Object)poller).GetInstanceID() select poller).ToList(); NetworkingPoller networkingPoller = list.FirstOrDefault(); if ((Object)(object)networkingPoller == (Object)null) { GameObject val = new GameObject(text); networkingPoller = val.AddComponent(); ((Object)networkingPoller).hideFlags = (HideFlags)61; } CleanupDuplicatePollers(networkingPoller, list.Skip(1), DestroyPollerDuringStartup); GameObject gameObject = ((Component)networkingPoller).gameObject; ((Object)gameObject).name = text; if (list.Count > 0) { Scene scene = gameObject.scene; if (((Scene)(ref scene)).IsValid()) { scene = gameObject.scene; if (((Scene)(ref scene)).name != "DontDestroyOnLoad") { ManualLogSource logger = Logger; string[] obj = new string[5] { "Promoting existing poller object '", ((Object)gameObject).name, "' from scene '", null, null }; scene = gameObject.scene; obj[3] = ((Scene)(ref scene)).name; obj[4] = "' to DontDestroyOnLoad lifecycle."; logger.LogDebug((object)string.Concat(obj)); } } } Object.DontDestroyOnLoad((Object)(object)gameObject); Logger.LogInfo((object)"NetworkingLibrary v1.0.9 has fully loaded!"); } private static void CleanupDuplicatePollers(NetworkingPoller canonicalPoller, IEnumerable duplicatePollers, Action destroyAction) { foreach (NetworkingPoller duplicatePoller in duplicatePollers) { if (!((Object)(object)duplicatePoller == (Object)null)) { GameObject gameObject = ((Component)duplicatePoller).gameObject; if (!((Object)(object)gameObject == (Object)null)) { bool flag = HasOnlyTransformAndPollerComponents(gameObject); bool flag2 = flag && (Object)(object)gameObject.transform != (Object)null && ((Component)canonicalPoller).transform.IsChildOf(gameObject.transform); destroyAction((Object)(object)((flag && !flag2) ? ((NetworkingPoller)(object)gameObject) : duplicatePoller)); } } } } private static bool HasOnlyTransformAndPollerComponents(GameObject pollerObject) { Component[] components = pollerObject.GetComponents(); bool flag = false; bool flag2 = false; int num = 0; foreach (Component val in components) { if ((Object)(object)val == (Object)null) { continue; } num++; if (val is Transform) { flag = true; continue; } if (val is NetworkingPoller) { flag2 = true; continue; } return false; } return num == 2 && flag && flag2; } private static void DestroyPollerDuringStartup(Object target) { if (target is NetworkingPoller networkingPoller) { ((Behaviour)networkingPoller).enabled = false; } else { GameObject val = (GameObject)(object)((target is GameObject) ? target : null); if (val != null) { val.SetActive(false); } } if (IsApplicationPlaying()) { DestroyObject(target); } else { DestroyObjectImmediate(target); } } internal static bool TryInitializeNetworkingService(ManualLogSource? logger, out INetworkingService? service) { DefaultServiceSelectionReason defaultSelectionReason; return TryInitializeNetworkingService(logger, out service, out defaultSelectionReason); } internal static bool TryInitializeNetworkingService(ManualLogSource? logger, out INetworkingService? service, out DefaultServiceSelectionReason defaultSelectionReason) { service = null; defaultSelectionReason = DefaultServiceSelectionReason.ProbeFailed; try { (INetworkingService, DefaultServiceSelectionReason) tuple = CreateDefaultNetworkingServiceWithReason(); defaultSelectionReason = tuple.Item2; (service, _) = tuple; service.Initialize(); if (!service.IsInitialized) { if (logger != null) { logger.LogError((object)("Default networking service '" + service.GetType().Name + "' initialization returned without becoming active. Attempting OfflineNetworkingService fallback.")); } try { service.Shutdown(); } catch (Exception arg) { if (logger != null) { logger.LogWarning((object)$"Failed to shutdown uninitialized default networking service '{service.GetType().Name}' during fallback: {arg}"); } } service = null; throw new InvalidOperationException("Default networking service initialization completed without becoming active."); } return true; } catch (Exception arg2) { SafeShutdown(service, "failed default networking service initialization"); service = null; if (logger != null) { logger.LogError((object)$"Failed to initialize default networking service. Attempting OfflineNetworkingService fallback. Exception: {arg2}"); } } try { service = CreateOfflineNetworkingService(); service.Initialize(); if (!service.IsInitialized) { if (logger != null) { logger.LogError((object)("Fallback networking service '" + service.GetType().Name + "' initialization returned without becoming active. Networking service disabled.")); } try { service.Shutdown(); } catch (Exception arg3) { if (logger != null) { logger.LogWarning((object)$"Failed to shutdown uninitialized fallback networking service '{service.GetType().Name}': {arg3}"); } } service = null; return false; } return true; } catch (Exception arg4) { SafeShutdown(service, "failed fallback OfflineNetworkingService initialization"); if (logger != null) { logger.LogError((object)$"FATAL: Failed to initialize fallback OfflineNetworkingService. Networking service disabled. Exception: {arg4}"); } service = null; return false; } } private IEnumerator RetrySteamInitializationForStartupWindow(float retryWindowSeconds) { float delaySeconds = 0.5f; float deadline = RealtimeSinceStartupProvider() + Mathf.Max(0.1f, retryWindowSeconds); while (RealtimeSinceStartupProvider() < deadline) { yield return WaitForSecondsRealtimeFactory(delaySeconds); INetworkingService service = Service; if (service == null) { yield break; } INetworkingService networkingService; DefaultServiceSelectionReason defaultServiceSelectionReason; try { (networkingService, defaultServiceSelectionReason) = CreateDefaultNetworkingServiceWithReason(); } catch (Exception ex) { LogStartupRetryTransitionThrottled("Aborting startup retries: default service creation threw " + ex.GetType().Name + ": " + ex.Message); yield break; } if (defaultServiceSelectionReason == DefaultServiceSelectionReason.SteamReady) { try { networkingService.Initialize(); if (!networkingService.IsInitialized) { LogStartupRetryTransitionThrottled("Steam readiness probe passed but Steam service did not initialize yet; continuing startup retries."); SafeShutdown(networkingService, "startup retry steam candidate"); delaySeconds = Mathf.Min(3f, delaySeconds * 1.8f); continue; } if (Service == service) { if (service.InLobby) { LogStartupRetryTransitionThrottled("Skipping Steam promotion during startup retry because the active service is currently in a lobby."); SafeShutdown(networkingService, "startup retry steam candidate while active lobby"); delaySeconds = Mathf.Min(3f, delaySeconds * 1.8f); continue; } ReplaceService(service, networkingService, "Steam became ready during startup retry window."); yield break; } SafeShutdown(networkingService, "startup retry stale steam candidate"); yield break; } catch (Exception ex2) { SafeShutdown(networkingService, "startup retry steam candidate after initialize exception"); LogStartupRetryTransitionThrottled("Aborting startup retries: Steam service initialization threw " + ex2.GetType().Name + ": " + ex2.Message); yield break; } } SafeShutdown(networkingService, "startup retry discarded candidate"); if (defaultServiceSelectionReason == DefaultServiceSelectionReason.SteamApiNotReady) { delaySeconds = Mathf.Min(3f, delaySeconds * 1.8f); continue; } LogStartupRetryTransitionThrottled($"Aborting startup retries: default selection reason is {defaultServiceSelectionReason}."); yield break; } LogStartupRetryTransitionThrottled("Startup retry window elapsed before Steam became ready."); } private static void ReplaceService(INetworkingService previousService, INetworkingService nextService, string reason) { if (previousService == nextService) { return; } try { if (previousService is INetworkingServiceStateTransfer networkingServiceStateTransfer) { networkingServiceStateTransfer.CopyRuntimeStateTo(nextService); } } catch (Exception ex) { ManualLogSource logger = Logger; if (logger != null) { logger.LogWarning((object)("Failed to migrate networking runtime state before service replacement: " + ex.Message)); } SafeShutdown(nextService, "startup retry failed replacement candidate"); return; } SafeShutdown(previousService, "startup retry previous service"); Service = nextService; LogStartupRetryTransitionThrottled("Service transition completed: " + previousService.GetType().Name + " -> " + nextService.GetType().Name + ". Reason: " + reason); } private static void SafeShutdown(INetworkingService? service, string context) { if (service == null) { return; } try { service.Shutdown(); } catch (Exception ex) { ManualLogSource logger = Logger; if (logger != null) { logger.LogWarning((object)("Failed to shutdown networking service during " + context + ": " + ex.Message)); } } } private static void LogStartupRetryTransitionThrottled(string message) { NetLog.DebugThrottled("Net.StartupRetry", "Net.StartupRetry.Transition", 1.5, message, () => RealtimeSinceStartupProvider(), includeOriginalMessageInFallback: true); } internal static void ResetNetworkingStartupHooks() { CreateDefaultNetworkingServiceWithReason = delegate { DefaultServiceSelectionReason reason; INetworkingService item = NetworkingServiceFactory.CreateDefaultServiceWithReason(out reason); return (item, reason); }; CreateOfflineNetworkingService = () => new OfflineNetworkingService(); IsApplicationPlaying = () => Application.isPlaying; DestroyObject = Object.Destroy; DestroyObjectImmediate = Object.DestroyImmediate; RealtimeSinceStartupProvider = () => Time.realtimeSinceStartup; WaitForSecondsRealtimeFactory = (float seconds) => (object?)new WaitForSecondsRealtime(seconds); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "AeralisFoundation.NetworkingLibrary"; public const string PLUGIN_NAME = "NetworkingLibrary"; public const string PLUGIN_VERSION = "1.0.9"; } } namespace NetworkingLibrary.Services { public enum ReliableType { Unreliable, Reliable, UnreliableNoDelay } public interface INetworkingService { bool IsInitialized { get; } bool InLobby { get; } ulong HostSteamId64 { get; } string HostIdString { get; } bool IsHost { get; } Func? IncomingValidator { get; set; } event Action? LobbyCreated; event Action? LobbyEntered; event Action? LobbyLeft; event Action? PlayerEntered; event Action? PlayerLeft; event Action? LobbyDataChanged; event Action? PlayerDataChanged; ulong GetLocalSteam64(); ulong[] GetLobbyMemberSteamIds(); void Initialize(); void Shutdown(); void CreateLobby(int maxPlayers = 8); void JoinLobby(ulong lobbySteamId64); void LeaveLobby(); void InviteToLobby(ulong steamId64); IDisposable RegisterNetworkObject(object instance, uint modId, int mask = 0); IDisposable RegisterNetworkType(Type type, uint modId, int mask = 0); void DeregisterNetworkObject(object instance, uint modId, int mask = 0); void DeregisterNetworkType(Type type, uint modId, int mask = 0); void RPC(uint modId, string methodName, ReliableType reliable, params object[] parameters); void RPC(uint modId, string methodName, ReliableType reliable, Type[] parameterTypes, params object?[] parameters); void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, params object[] parameters); void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, Type[] parameterTypes, params object?[] parameters); void RPCToHost(uint modId, string methodName, ReliableType reliable, params object[] parameters); void RegisterLobbyDataKey(string key); void SetLobbyData(string key, object value); T GetLobbyData(string key); void RegisterPlayerDataKey(string key); void SetPlayerData(string key, object value); T GetPlayerData(ulong steamId64, string key); void PollReceive(); void RegisterModSigner(uint modId, Func signerDelegate); void RegisterModPublicKey(uint modId, RSAParameters pub); } internal interface INetworkingServiceStateTransfer { void CopyRuntimeStateTo(INetworkingService target); } public class NetworkingPoller : MonoBehaviour { private const string LogSource = "NetworkingPoller"; private const float ErrorLogCooldownSeconds = 2f; internal static Func TimeProvider = () => Time.unscaledTime; internal static Func FallbackTimeProvider = () => DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; private double mainThreadDispatcherLastErrorLogTime = double.NegativeInfinity; private bool mainThreadDispatcherHadFault; private bool mainThreadDispatcherSuppressedFault; private int mainThreadDispatcherSuppressedExceptionCount; private double pollReceiveLastErrorLogTime = double.NegativeInfinity; private bool pollReceiveHadFault; private bool pollReceiveSuppressedFault; private int pollReceiveSuppressedExceptionCount; private void Update() { PollGuarded(UnityMainThreadDispatcher.ProcessPendingMainThreadWork, "Main-thread dispatcher", ref mainThreadDispatcherLastErrorLogTime, ref mainThreadDispatcherHadFault, ref mainThreadDispatcherSuppressedFault, ref mainThreadDispatcherSuppressedExceptionCount); PollGuarded(delegate { Net.Service?.PollReceive(); }, "PollReceive", ref pollReceiveLastErrorLogTime, ref pollReceiveHadFault, ref pollReceiveSuppressedFault, ref pollReceiveSuppressedExceptionCount); } private static void PollGuarded(Action action, string name, ref double lastErrorLogTime, ref bool hadFault, ref bool suppressedFault, ref int suppressedExceptionCount) { bool flag = true; try { action(); } catch (Exception arg) { flag = false; hadFault = true; double num = ResolveTime(); if (num < lastErrorLogTime || num - lastErrorLogTime >= 2.0) { lastErrorLogTime = num; NetLog.Error("NetworkingPoller", $"{name} error: {arg}"); suppressedFault = false; suppressedExceptionCount = 0; } else { suppressedFault = true; suppressedExceptionCount++; } } if (flag && hadFault) { NetLog.Info("NetworkingPoller", suppressedFault ? $"{name} recovered after repeated failures. Suppressed {suppressedExceptionCount} errors since last emitted error." : $"{name} recovered. Suppressed {suppressedExceptionCount} errors since last emitted error."); hadFault = false; suppressedFault = false; suppressedExceptionCount = 0; } } private static double ResolveTime() { try { float num = TimeProvider(); if (!float.IsNaN(num) && !float.IsInfinity(num)) { return num; } } catch { } try { double num2 = FallbackTimeProvider(); if (!double.IsNaN(num2) && !double.IsInfinity(num2)) { return num2; } } catch { } return DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; } private void Awake() { Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } } public enum DefaultServiceSelectionReason { SteamReady, SteamClientNotRunning, SteamApiNotReady, ProbeFailed, UnityEditorOffline } public static class NetworkingServiceFactory { private const string LogSource = "NetworkingServiceFactory"; private const float ProbeDebugLogCooldownSeconds = 2f; private static readonly string[] steamManagerTypeCandidates = new string[3] { "pworld.Scripts.SteamManager, Assembly-CSharp", "SteamManager, Assembly-CSharp", "SteamManager" }; internal static Func IsSteamClientRunning = () => SteamAPI.IsSteamRunning(); internal static Func IsSteamApiInitialized = ProbeSteamApiInitialized; internal static Func CreateSteamService = () => new SteamNetworkingService(); internal static Func CreateOfflineService = () => new OfflineNetworkingService(); internal static Func ResolveType = Type.GetType; internal static Func UnscaledTimeProvider = () => Time.unscaledTime; public static INetworkingService CreateDefaultService() { DefaultServiceSelectionReason reason; return CreateDefaultServiceWithReason(out reason); } internal static INetworkingService CreateDefaultServiceWithReason(out DefaultServiceSelectionReason reason) { DefaultServiceSelectionReason defaultServiceSelectionReason = DefaultServiceSelectionReason.SteamApiNotReady; try { bool flag = IsSteamClientRunning(); NetLog.Info("NetworkingServiceFactory", $"Steam client running: {flag}."); if (!flag) { defaultServiceSelectionReason = DefaultServiceSelectionReason.SteamClientNotRunning; NetLog.Info("NetworkingServiceFactory", "Falling back to OfflineNetworkingService. Reason: Steam client is not running."); } else { bool flag2 = IsSteamApiInitialized(); NetLog.Info("NetworkingServiceFactory", $"Steam API initialized: {flag2}."); if (flag2) { reason = DefaultServiceSelectionReason.SteamReady; NetLog.Info("NetworkingServiceFactory", "Steam ready. Creating SteamNetworkingService."); return CreateSteamService(); } defaultServiceSelectionReason = DefaultServiceSelectionReason.SteamApiNotReady; NetLog.Info("NetworkingServiceFactory", "Falling back to OfflineNetworkingService. Reason: Steam API is not initialized."); } } catch (Exception arg) { reason = DefaultServiceSelectionReason.ProbeFailed; NetLog.Error("NetworkingServiceFactory", $"Steam readiness probe failed. Falling back to OfflineNetworkingService. Exception: {arg}"); return CreateOfflineService(); } reason = defaultServiceSelectionReason; return CreateOfflineService(); } private static bool ProbeSteamApiInitialized() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (TryReadSteamManagerInitialized(out var isInitialized)) { return isInitialized; } try { return SteamUser.GetSteamID() != CSteamID.Nil; } catch (Exception ex) { NetLog.DebugThrottled("NetworkingServiceFactory", "NetworkingServiceFactory.ProbeSteamApiInitialized", 2.0, "ProbeSteamApiInitialized fallback SteamUser.GetSteamID failed: " + ex.GetType().Name + ": " + ex.Message, () => UnscaledTimeProvider(), includeOriginalMessageInFallback: true); return false; } } private static bool TryReadSteamManagerInitialized(out bool isInitialized) { isInitialized = false; try { string[] array = steamManagerTypeCandidates; foreach (string text in array) { Type type = ResolveType(text); if (!(type == null) && ShouldUseResolvedSteamManagerCandidate(text, type) && TryReadInitializedSafely(type, out isInitialized)) { return true; } } Type type2 = ResolveLoadedSteamManagerType(); if (type2 != null && TryReadInitializedSafely(type2, out isInitialized)) { return true; } return false; } catch (Exception ex) { NetLog.DebugThrottled("NetworkingServiceFactory", "NetworkingServiceFactory.TryReadSteamManagerInitialized", 2.0, "TryReadSteamManagerInitialized reflection probe failed: " + ex.GetType().Name + ": " + ex.Message, () => UnscaledTimeProvider(), includeOriginalMessageInFallback: true); return false; } } private static bool TryReadInitializedSafely(Type steamManagerType, out bool isInitialized) { isInitialized = false; try { return TryReadInitializedFromType(steamManagerType, out isInitialized); } catch (Exception ex) { NetLog.DebugThrottled("NetworkingServiceFactory", "NetworkingServiceFactory.TryReadInitializedSafely", 2.0, "TryReadInitializedFromType failed for '" + (steamManagerType.FullName ?? steamManagerType.Name) + "': " + ex.GetType().Name + ": " + ex.Message, () => UnscaledTimeProvider(), includeOriginalMessageInFallback: true); return false; } } private static bool ShouldUseResolvedSteamManagerCandidate(string candidateTypeName, Type steamManagerType) { if (string.Equals(candidateTypeName, "SteamManager", StringComparison.Ordinal)) { return IsPreferredSteamManagerType(steamManagerType); } return true; } private static Type? ResolveLoadedSteamManagerType() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (assembly == null || assembly.IsDynamic) { continue; } Type[] array; try { array = assembly.GetTypes(); } catch (ReflectionTypeLoadException exception) { array = GetLoadableTypes(exception); } catch { continue; } foreach (Type type in array) { if (!(type == null) && string.Equals(type.Name, "SteamManager", StringComparison.Ordinal) && (!(type.GetProperty("Initialized", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.PropertyType != typeof(bool)) || !(type.GetField("Initialized", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.FieldType != typeof(bool))) && IsPreferredSteamManagerType(type)) { return type; } } } return null; } private static Type[] GetLoadableTypes(ReflectionTypeLoadException exception) { Type[] types = exception.Types; int num = 0; for (int i = 0; i < types.Length; i++) { if (types[i] != null) { num++; } } if (num == 0) { return Array.Empty(); } Type[] array = new Type[num]; int num2 = 0; foreach (Type type in types) { if (!(type == null)) { array[num2++] = type; } } return array; } private static bool IsPreferredSteamManagerType(Type steamManagerType) { string fullName = steamManagerType.FullName; if (string.Equals(fullName, "pworld.Scripts.SteamManager", StringComparison.Ordinal)) { return true; } if (string.Equals(fullName, "SteamManager", StringComparison.Ordinal)) { return true; } string name = steamManagerType.Assembly.GetName().Name; return string.Equals(name, "Assembly-CSharp", StringComparison.Ordinal); } private static bool TryReadInitializedFromType(Type steamManagerType, out bool isInitialized) { isInitialized = false; PropertyInfo property = steamManagerType.GetProperty("Initialized", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property?.PropertyType == typeof(bool)) { isInitialized = (bool)(property.GetValue(null) ?? ((object)false)); return true; } FieldInfo field = steamManagerType.GetField("Initialized", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field?.FieldType == typeof(bool)) { isInitialized = (bool)(field.GetValue(null) ?? ((object)false)); return true; } return false; } internal static void ResetTestHooks() { IsSteamClientRunning = () => SteamAPI.IsSteamRunning(); IsSteamApiInitialized = ProbeSteamApiInitialized; CreateSteamService = () => new SteamNetworkingService(); CreateOfflineService = () => new OfflineNetworkingService(); ResolveType = Type.GetType; UnscaledTimeProvider = () => Time.unscaledTime; } } public class OfflineNetworkingService : INetworkingService, INetworkingServiceStateTransfer { private sealed class RegistrationTransferToken : IDisposable { private readonly object sync = new object(); private Action? onDispose; private bool isDisposed; public void SetDisposeAction(Action? action) { Action action2 = null; bool flag = false; lock (sync) { if (isDisposed) { flag = true; } else { action2 = onDispose; onDispose = action; } } if (flag) { action?.Invoke(); } else { action2?.Invoke(); } } public void Dispose() { Action action; lock (sync) { if (isDisposed) { return; } isDisposed = true; action = onDispose; onDispose = null; } action?.Invoke(); } } private sealed class HandlerRegistration { public string MethodName = string.Empty; public MessageHandler Handler; } private sealed class RuntimeRegistration { public object? Instance; public Type? Type; public uint ModId; public int Mask; public RegistrationTransferToken Token; } private class SlidingWindowRateLimiter { private readonly int limit; private readonly TimeSpan window; private readonly Queue q = new Queue(); private readonly object qLock = new object(); public SlidingWindowRateLimiter(int limit, TimeSpan window) { this.limit = limit; this.window = window; } public bool IncomingAllowed() { lock (qLock) { DateTime utcNow = DateTime.UtcNow; while (q.Count > 0 && utcNow - q.Peek() > window) { q.Dequeue(); } if (q.Count >= limit) { return false; } q.Enqueue(utcNow); return true; } } } private class MessageHandler { public object Target; public MethodInfo Method; public ParameterInfo[] Parameters; public bool TakesInfo; public int Mask; public int ParameterCountWithoutRpcInfo; public string OverloadKey = string.Empty; } private readonly object rpcLock = new object(); private readonly object cryptoStateLock = new object(); private readonly Dictionary>> rpcs = new Dictionary>>(); private readonly Dictionary lobbyData = new Dictionary(); private readonly Dictionary> perPlayerData = new Dictionary>(); private readonly HashSet lobbyKeys = new HashSet(); private readonly HashSet playerKeys = new HashSet(); private readonly SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter(100, TimeSpan.FromSeconds(1.0)); private readonly MessageSizePolicy messageSizePolicy; private readonly Dictionary perPeerSymmetricKey = new Dictionary(); private byte[]? globalSharedSecret; private HMACSHA256? globalHmac; private readonly Dictionary> modSigners = new Dictionary>(); private readonly Dictionary modPublicKeys = new Dictionary(); private static readonly TimeSpan LogFallbackCooldown = TimeSpan.FromSeconds(5.0); private static readonly object logFallbackLock = new object(); private static DateTime lastLogFallbackUtc = DateTime.MinValue; private static int suppressedLogFallbackCount; private static readonly TimeSpan DeserializeFailureCooldown = TimeSpan.FromSeconds(2.0); private static readonly object deserializeFailureLock = new object(); private static DateTime lastDeserializeFailureUtc = DateTime.MinValue; private static int suppressedDeserializeFailureCount; private static readonly TimeSpan ExceptionLogCooldown = TimeSpan.FromSeconds(3.0); private static readonly object exceptionLogThrottleLock = new object(); private static readonly Dictionary lastExceptionLogByKey = new Dictionary(); private static readonly Dictionary suppressedExceptionLogByKey = new Dictionary(); internal static Func UtcNow = () => DateTime.UtcNow; private long _nextMessageId; private bool offlineIsHost; private readonly List runtimeRegistrations = new List(); public bool IsInitialized { get; private set; } public bool InLobby { get; private set; } public ulong HostSteamId64 { get; private set; } = 1000uL; public string HostIdString => HostSteamId64.ToString(); public Func? IncomingValidator { get; set; } public ulong LocalSteamId { get; private set; } = 1000uL; public bool IsHost => offlineIsHost; public event Action? LobbyCreated; public event Action? LobbyEntered; public event Action? LobbyLeft; public event Action? PlayerEntered; public event Action? PlayerLeft; public event Action? LobbyDataChanged; public event Action? PlayerDataChanged; private static void LogError(string message) { try { ManualLogSource logger = Net.Logger; if (logger == null) { throw new InvalidOperationException("Net logger is unavailable."); } logger.LogError((object)message); } catch (Exception ex) { LogFallbackWarningThrottled("error", message, ex); } } private static void LogWarning(string message) { try { ManualLogSource logger = Net.Logger; if (logger == null) { throw new InvalidOperationException("Net logger is unavailable."); } logger.LogWarning((object)message); } catch (Exception ex) { LogFallbackWarningThrottled("warning", message, ex); } } private static void LogFallbackWarningThrottled(string level, string originalMessage, Exception ex) { DateTime now = UtcNow(); lock (logFallbackLock) { if (IsInCooldown(now, lastLogFallbackUtc, LogFallbackCooldown)) { suppressedLogFallbackCount++; return; } int num = suppressedLogFallbackCount; suppressedLogFallbackCount = 0; lastLogFallbackUtc = now; string text = ((num > 0) ? $" Suppressed {num} similar logger failures." : string.Empty); string text2 = "[OfflineNetworkingService] Failed to write " + level + " log. Exception: " + ex.GetType().Name + ": " + ex.Message + ". Original message: " + originalMessage + "." + text; try { Debug.LogWarning((object)text2); } catch { Trace.TraceWarning(text2); } } } private static string FormatException(Exception ex) { return ex.GetType().Name + ": " + ex.Message; } private static void LogDeserializeFailureThrottled(Exception ex, string context) { DateTime now = UtcNow(); lock (deserializeFailureLock) { if (IsInCooldown(now, lastDeserializeFailureUtc, DeserializeFailureCooldown)) { suppressedDeserializeFailureCount++; return; } int num = suppressedDeserializeFailureCount; suppressedDeserializeFailureCount = 0; lastDeserializeFailureUtc = now; string text = ((num > 0) ? $" Suppressed {num} similar deserialization failures." : string.Empty); LogWarning("Offline RPC deserialization skipped handler (" + context + "). Exception: " + FormatException(ex) + "." + text); } } private static void LogExceptionThrottled(string key, bool error, string messagePrefix, Exception ex) { DateTime dateTime = UtcNow(); lock (exceptionLogThrottleLock) { if (lastExceptionLogByKey.TryGetValue(key, out var value) && IsInCooldown(dateTime, value, ExceptionLogCooldown)) { suppressedExceptionLogByKey[key] = ((!suppressedExceptionLogByKey.TryGetValue(key, out var value2)) ? 1 : (value2 + 1)); return; } lastExceptionLogByKey[key] = dateTime; int value3; int num = (suppressedExceptionLogByKey.TryGetValue(key, out value3) ? value3 : 0); suppressedExceptionLogByKey.Remove(key); string text = ((num > 0) ? $" Suppressed {num} similar exceptions." : string.Empty); string message = messagePrefix + " Exception: " + FormatException(ex) + "." + text; if (error) { LogError(message); } else { LogWarning(message); } } } private static bool IsInCooldown(DateTime now, DateTime previous, TimeSpan cooldown) { if (previous != DateTime.MinValue && now >= previous) { return now - previous < cooldown; } return false; } public ulong GetLocalSteam64() { return LocalSteamId; } public ulong[] GetLobbyMemberSteamIds() { lock (rpcLock) { if (!InLobby || perPlayerData.Count == 0) { return Array.Empty(); } ulong[] array = new ulong[perPlayerData.Count]; int num = 0; foreach (ulong key in perPlayerData.Keys) { array[num++] = key; } Array.Sort(array); return array; } } public void RegisterModSigner(uint modId, Func signerDelegate) { if (signerDelegate == null) { throw new ArgumentNullException("signerDelegate"); } lock (cryptoStateLock) { modSigners[modId] = signerDelegate; } } public void RegisterModPublicKey(uint modId, RSAParameters pub) { if (pub.Modulus == null || pub.Modulus.Length == 0) { throw new ArgumentException("RSA public key modulus must not be empty.", "pub"); } if (pub.Exponent == null || pub.Exponent.Length == 0) { throw new ArgumentException("RSA public key exponent must not be empty.", "pub"); } lock (cryptoStateLock) { modPublicKeys[modId] = CloneRsaParameters(pub); } } public void CopyRuntimeStateTo(INetworkingService target) { if (target == null) { throw new ArgumentNullException("target"); } (object, Type, uint, int, RegistrationTransferToken)[] array; string[] array2; string[] array3; lock (rpcLock) { array = runtimeRegistrations.Select((RuntimeRegistration registration) => (registration.Instance, registration.Type, registration.ModId, registration.Mask, registration.Token)).ToArray(); array2 = lobbyKeys.ToArray(); array3 = playerKeys.ToArray(); } KeyValuePair>[] array4; KeyValuePair[] array5; lock (cryptoStateLock) { array4 = modSigners.ToArray(); array5 = modPublicKeys.Select((KeyValuePair entry) => new KeyValuePair(entry.Key, CloneRsaParameters(entry.Value))).ToArray(); } List list = new List(); Func incomingValidator = target.IncomingValidator; try { target.IncomingValidator = IncomingValidator; (object, Type, uint, int, RegistrationTransferToken)[] array6 = array; for (int i = 0; i < array6.Length; i++) { (object, Type, uint, int, RegistrationTransferToken) tuple = array6[i]; list.Add((tuple.Item2 != null) ? target.RegisterNetworkType(tuple.Item2, tuple.Item3, tuple.Item4) : target.RegisterNetworkObject(tuple.Item1, tuple.Item3, tuple.Item4)); } string[] array7 = array2; foreach (string key in array7) { target.RegisterLobbyDataKey(key); } string[] array8 = array3; foreach (string key2 in array8) { target.RegisterPlayerDataKey(key2); } KeyValuePair>[] array9 = array4; for (int l = 0; l < array9.Length; l++) { KeyValuePair> keyValuePair = array9[l]; target.RegisterModSigner(keyValuePair.Key, keyValuePair.Value); } KeyValuePair[] array10 = array5; for (int m = 0; m < array10.Length; m++) { KeyValuePair keyValuePair2 = array10[m]; target.RegisterModPublicKey(keyValuePair2.Key, keyValuePair2.Value); } } catch { try { target.IncomingValidator = incomingValidator; } catch { } for (int num = list.Count - 1; num >= 0; num--) { list[num].Dispose(); } throw; } for (int n = 0; n < array.Length; n++) { array[n].Item5.SetDisposeAction(list[n].Dispose); } } private static RSAParameters CloneRsaParameters(RSAParameters source) { RSAParameters result = default(RSAParameters); result.Modulus = ((source.Modulus == null) ? null : ((byte[])source.Modulus.Clone())); result.Exponent = ((source.Exponent == null) ? null : ((byte[])source.Exponent.Clone())); return result; } private ulong NextMessageId() { return (ulong)Interlocked.Increment(ref _nextMessageId); } public OfflineNetworkingService(MessageSizePolicy? messageSizePolicy = null) { this.messageSizePolicy = messageSizePolicy ?? Message.DefaultSizePolicy; } public void Initialize() { if (!IsInitialized) { EnsureLocalPeerKey(); IsInitialized = true; } } public void Shutdown() { if (InLobby) { LeaveLobby(); } IncomingValidator = null; IsInitialized = false; offlineIsHost = false; HostSteamId64 = LocalSteamId; lock (rpcLock) { rpcs.Clear(); runtimeRegistrations.Clear(); lobbyKeys.Clear(); playerKeys.Clear(); lobbyData.Clear(); perPlayerData.Clear(); } lock (cryptoStateLock) { modSigners.Clear(); modPublicKeys.Clear(); ClearPerPeerSymmetricKeysUnderLock(); ClearGlobalSharedSecretUnderLock(); globalHmac?.Dispose(); globalHmac = null; } lock (exceptionLogThrottleLock) { lastExceptionLogByKey.Clear(); suppressedExceptionLogByKey.Clear(); } } public void CreateLobby(int maxPlayers = 8) { if (!IsInitialized) { LogError("CreateLobby called before OfflineNetworkingService.Initialize."); return; } if (InLobby) { LogWarning("CreateLobby called while already in a lobby. Leaving current lobby before creating a new one."); LeaveLobby(); } if (maxPlayers != 8) { LogWarning($"Offline mode is single-peer only; requested lobby capacity {maxPlayers} is ignored."); } EnsureLocalPeerKey(); lock (rpcLock) { InLobby = true; HostSteamId64 = LocalSteamId; lobbyData.Clear(); perPlayerData.Clear(); perPlayerData[LocalSteamId] = new Dictionary(); offlineIsHost = true; } this.LobbyCreated?.Invoke(); this.LobbyEntered?.Invoke(); this.PlayerEntered?.Invoke(LocalSteamId); } public void JoinLobby(ulong lobbySteamId64) { if (!IsInitialized) { LogError("JoinLobby called before OfflineNetworkingService.Initialize."); return; } if (lobbySteamId64 == 0L) { LogWarning("JoinLobby called with invalid lobby id 0."); return; } if (InLobby) { LogWarning("JoinLobby called while already in a lobby. Leaving current lobby before joining a new lobby."); LeaveLobby(); } EnsureLocalPeerKey(); if (lobbySteamId64 != LocalSteamId) { LogWarning($"Offline mode is single-peer only; treating JoinLobby argument {lobbySteamId64} as lobby id and preserving local host identity {LocalSteamId}."); } lock (rpcLock) { InLobby = true; HostSteamId64 = LocalSteamId; lobbyData.Clear(); perPlayerData.Clear(); perPlayerData[LocalSteamId] = new Dictionary(); offlineIsHost = true; } this.LobbyEntered?.Invoke(); this.PlayerEntered?.Invoke(LocalSteamId); } public void LeaveLobby() { bool flag; lock (rpcLock) { if (!InLobby) { return; } flag = perPlayerData.ContainsKey(LocalSteamId); InLobby = false; HostSteamId64 = LocalSteamId; lobbyData.Clear(); perPlayerData.Clear(); offlineIsHost = false; } lock (cryptoStateLock) { ClearPerPeerSymmetricKeysUnderLock(); ClearGlobalSharedSecretUnderLock(); globalHmac?.Dispose(); globalHmac = null; } this.LobbyLeft?.Invoke(); if (flag) { this.PlayerLeft?.Invoke(LocalSteamId); } } private void ClearPerPeerSymmetricKeysUnderLock() { foreach (byte[] value in perPeerSymmetricKey.Values) { if (value != null) { CryptographicOperations.ZeroMemory(value); } } perPeerSymmetricKey.Clear(); } private void ClearGlobalSharedSecretUnderLock() { if (globalSharedSecret != null) { CryptographicOperations.ZeroMemory(globalSharedSecret); globalSharedSecret = null; } } public void InviteToLobby(ulong steamId64) { if (!IsInitialized) { LogError("InviteToLobby called before OfflineNetworkingService.Initialize."); } else if (InLobby) { if (steamId64 == 0L) { LogWarning("InviteToLobby called with invalid target Steam64 id 0."); } else if (steamId64 != LocalSteamId) { LogWarning($"Offline mode supports strict local loopback only; invite target {steamId64} is ignored."); } } } private void EnsureLocalPeerKey() { lock (cryptoStateLock) { if (perPeerSymmetricKey.ContainsKey(LocalSteamId)) { return; } using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); byte[] array = new byte[32]; randomNumberGenerator.GetBytes(array); perPeerSymmetricKey[LocalSteamId] = array; } } public IDisposable RegisterNetworkObject(object instance, uint modId, int mask = 0) { object instance2 = instance; if (instance2 == null) { throw new ArgumentNullException("instance"); } Type type = instance2.GetType(); List registeredHandlers = new List(); RegistrationTransferToken token = new RegistrationTransferToken(); lock (rpcLock) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo method in methods) { if (method.IsDefined(typeof(CustomRPCAttribute), inherit: false)) { if (!rpcs.TryGetValue(modId, out Dictionary> value)) { value = new Dictionary>(); rpcs[modId] = value; } if (!value.TryGetValue(method.Name, out var value2)) { value2 = new List(); value[method.Name] = value2; } ParameterInfo[] parameters = method.GetParameters(); if (!value2.Any((MessageHandler existing) => existing.Mask == mask && existing.Method == method && existing.Target == instance2)) { MessageHandler messageHandler = new MessageHandler { Target = instance2, Method = method, Parameters = parameters, TakesInfo = (parameters.Length != 0 && RpcInfoParameterTypeHelper.IsRpcInfoParameterType(parameters.Last().ParameterType)), Mask = mask }; messageHandler.ParameterCountWithoutRpcInfo = (messageHandler.TakesInfo ? (parameters.Length - 1) : parameters.Length); messageHandler.OverloadKey = BuildOverloadKey(messageHandler); value2.Add(messageHandler); registeredHandlers.Add(new HandlerRegistration { MethodName = method.Name, Handler = messageHandler }); } } } if (registeredHandlers.Count > 0) { runtimeRegistrations.Add(new RuntimeRegistration { Instance = instance2, ModId = modId, Mask = mask, Token = token }); } } token.SetDisposeAction(delegate { DeregisterHandlers(modId, registeredHandlers); RemoveRuntimeRegistration(token); }); return token; } public IDisposable RegisterNetworkType(Type type, uint modId, int mask = 0) { if (type == null) { throw new ArgumentNullException("type"); } List registeredHandlers = new List(); RegistrationTransferToken token = new RegistrationTransferToken(); lock (rpcLock) { MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.IsDefined(typeof(CustomRPCAttribute), inherit: false)); if (methodInfo != null) { throw new InvalidOperationException("Cannot register instance RPC method " + type.FullName + "." + methodInfo.Name + " without an instance."); } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo method2 in methods) { if (method2.IsDefined(typeof(CustomRPCAttribute), inherit: false)) { if (!rpcs.TryGetValue(modId, out Dictionary> value)) { value = new Dictionary>(); rpcs[modId] = value; } if (!value.TryGetValue(method2.Name, out var value2)) { value2 = new List(); value[method2.Name] = value2; } ParameterInfo[] parameters = method2.GetParameters(); if (!value2.Any((MessageHandler existing) => existing.Mask == mask && existing.Method == method2)) { MessageHandler messageHandler = new MessageHandler { Target = null, Method = method2, Parameters = parameters, TakesInfo = (parameters.Length != 0 && RpcInfoParameterTypeHelper.IsRpcInfoParameterType(parameters.Last().ParameterType)), Mask = mask }; messageHandler.ParameterCountWithoutRpcInfo = (messageHandler.TakesInfo ? (parameters.Length - 1) : parameters.Length); messageHandler.OverloadKey = BuildOverloadKey(messageHandler); value2.Add(messageHandler); registeredHandlers.Add(new HandlerRegistration { MethodName = method2.Name, Handler = messageHandler }); } } } if (registeredHandlers.Count > 0) { runtimeRegistrations.Add(new RuntimeRegistration { Type = type, ModId = modId, Mask = mask, Token = token }); } } token.SetDisposeAction(delegate { DeregisterHandlers(modId, registeredHandlers); RemoveRuntimeRegistration(token); }); return token; } private void DeregisterHandlers(uint modId, List handlersToRemove) { lock (rpcLock) { if (!rpcs.TryGetValue(modId, out Dictionary> value) || handlersToRemove.Count == 0) { return; } foreach (HandlerRegistration item in handlersToRemove) { if (value.TryGetValue(item.MethodName, out var value2)) { value2.Remove(item.Handler); if (value2.Count == 0) { value.Remove(item.MethodName); } } } if (value.Count == 0) { rpcs.Remove(modId); } } } public void DeregisterNetworkObject(object instance, uint modId, int mask = 0) { object instance2 = instance; if (instance2 == null) { throw new ArgumentNullException("instance"); } lock (rpcLock) { if (!rpcs.TryGetValue(modId, out Dictionary> value)) { return; } MethodInfo[] methods = instance2.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { if (!methodInfo.IsDefined(typeof(CustomRPCAttribute), inherit: false) || !value.TryGetValue(methodInfo.Name, out var value2)) { continue; } for (int num = value2.Count - 1; num >= 0; num--) { if (value2[num].Target == instance2 && value2[num].Mask == mask) { value2.RemoveAt(num); } } if (value2.Count == 0) { value.Remove(methodInfo.Name); } } if (value.Count == 0) { rpcs.Remove(modId); } runtimeRegistrations.RemoveAll((RuntimeRegistration registration) => registration.Type == null && registration.ModId == modId && registration.Mask == mask && registration.Instance == instance2); } } public void DeregisterNetworkType(Type type, uint modId, int mask = 0) { Type type2 = type; if (type2 == null) { throw new ArgumentNullException("type"); } lock (rpcLock) { if (!rpcs.TryGetValue(modId, out Dictionary> value)) { return; } string[] array = value.Keys.ToArray(); foreach (string key in array) { if (!value.TryGetValue(key, out var value2)) { continue; } for (int num = value2.Count - 1; num >= 0; num--) { if (value2[num].Target == null && value2[num].Method.DeclaringType == type2 && value2[num].Mask == mask) { value2.RemoveAt(num); } } if (value2.Count == 0) { value.Remove(key); } } if (value.Count == 0) { rpcs.Remove(modId); } runtimeRegistrations.RemoveAll((RuntimeRegistration registration) => registration.Type == type2 && registration.ModId == modId && registration.Mask == mask); } } private void RemoveRuntimeRegistration(RegistrationTransferToken token) { RegistrationTransferToken token2 = token; lock (rpcLock) { runtimeRegistrations.RemoveAll((RuntimeRegistration registration) => registration.Token == token2); } } public void RPC(uint modId, string methodName, ReliableType reliable, params object[] parameters) { if (!InLobby) { LogError("RPC called while not in lobby"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, null); if (message != null) { DispatchIncoming(message, LocalSteamId); } } public void RPC(uint modId, string methodName, ReliableType reliable, Type[] parameterTypes, params object?[] parameters) { if (!InLobby) { LogError("RPC called while not in lobby"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, parameterTypes); if (message != null) { DispatchIncoming(message, LocalSteamId); } } public void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, params object[] parameters) { if (!InLobby) { LogError("Cannot RPC target when not in lobby"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, null); if (message != null && targetSteamId64 == LocalSteamId) { DispatchIncoming(message, LocalSteamId); } } public void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, Type[] parameterTypes, params object?[] parameters) { if (!InLobby) { LogError("Cannot RPC target when not in lobby"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, parameterTypes); if (message != null && targetSteamId64 == LocalSteamId) { DispatchIncoming(message, LocalSteamId); } } public void RPCToHost(uint modId, string methodName, ReliableType reliable, params object[] parameters) { if (!InLobby) { LogError("Not in lobby"); } else { RPCTarget(modId, methodName, HostSteamId64, reliable, parameters); } } public void RegisterLobbyDataKey(string key) { ValidateDataKey(key, "key"); lock (rpcLock) { lobbyKeys.Add(key); } } public void SetLobbyData(string key, object value) { ValidateDataKey(key, "key"); string text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; bool flag = false; bool flag2 = false; bool flag3 = false; lock (rpcLock) { if (InLobby) { flag = !lobbyKeys.Contains(key); flag3 = !lobbyData.TryGetValue(key, out string value2) || value2 != text; if (flag3) { lobbyData[key] = text; } } else { flag2 = true; } } if (flag2) { LogError("Cannot set lobby data when not in lobby."); return; } if (flag) { LogWarning("Accessing unregistered lobby key " + key); } if (flag3) { this.LobbyDataChanged?.Invoke(new string[1] { key }); } } public T GetLobbyData(string key) { ValidateDataKey(key, "key"); string value = null; bool flag = false; bool flag2 = false; bool flag3 = false; lock (rpcLock) { if (InLobby) { flag2 = !lobbyKeys.Contains(key); flag = lobbyData.TryGetValue(key, out value); } else { flag3 = true; } } if (flag3) { LogError("Cannot get lobby data when not in lobby."); return default(T); } if (flag2) { LogWarning("Accessing unregistered lobby key " + key); } if (!flag) { return default(T); } try { return DataValueConverter.ConvertTo(value); } catch (Exception ex) { LogExceptionThrottled("lobby_parse:" + key, error: true, "Could not parse lobby data [" + key + "," + value + "].", ex); return default(T); } } public void RegisterPlayerDataKey(string key) { ValidateDataKey(key, "key"); lock (rpcLock) { playerKeys.Add(key); } } public void SetPlayerData(string key, object value) { ValidateDataKey(key, "key"); string text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; bool flag = false; bool flag2 = false; bool flag3 = false; bool flag4 = false; lock (rpcLock) { if (InLobby) { flag = !playerKeys.Contains(key); if (!perPlayerData.TryGetValue(LocalSteamId, out Dictionary value2)) { value2 = new Dictionary(); perPlayerData[LocalSteamId] = value2; flag3 = true; } flag4 = !value2.TryGetValue(key, out var value3) || value3 != text; if (flag4) { value2[key] = text; } } else { flag2 = true; } } if (flag2) { LogError("Cannot set player data when not in lobby."); return; } if (flag) { LogWarning("Accessing unregistered player key " + key); } if (flag3) { LogWarning($"Local player {LocalSteamId} was missing in per-player state while setting key '{key}'. Recreating local state bucket."); } if (flag4) { this.PlayerDataChanged?.Invoke(LocalSteamId, new string[1] { key }); } } public T GetPlayerData(ulong steamId64, string key) { ValidateDataKey(key, "key"); string value = null; bool flag = false; bool flag2 = false; bool flag3 = false; lock (rpcLock) { if (InLobby) { flag2 = !playerKeys.Contains(key); flag = perPlayerData.TryGetValue(steamId64, out Dictionary value2) && value2.TryGetValue(key, out value); } else { flag3 = true; } } if (flag3) { LogError("Cannot get player data when not in lobby."); return default(T); } if (flag2) { LogWarning("Accessing unregistered player key " + key); } if (!flag) { return default(T); } try { return DataValueConverter.ConvertTo(value); } catch (Exception ex) { LogExceptionThrottled("player_parse:" + key, error: true, "Could not parse player data [" + key + "," + value + "].", ex); return default(T); } } public void PollReceive() { } private static void ValidateDataKey(string key, string paramName) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentException("Data key must be a non-empty string.", paramName); } } private Message? BuildMessage(uint modId, string methodName, int mask, object?[] parameters, Type[]? parameterTypes) { object?[] parameters2 = parameters; Type[] parameterTypes2 = parameterTypes; string methodName2 = methodName; MessageHandler[] handlersSnapshot; try { handlersSnapshot = Array.Empty(); lock (rpcLock) { if (rpcs.TryGetValue(modId, out Dictionary> value) && value.TryGetValue(methodName2, out var value2) && value2.Count > 0) { handlersSnapshot = value2.ToArray(); } } if (handlersSnapshot.Length != 0) { if (parameterTypes2 != null && parameterTypes2.Length != parameters2.Length) { throw new ArgumentException($"Parameter type count mismatch: expected {parameterTypes2.Length}, got {parameters2.Length}", "parameters"); } MessageHandler messageHandler = null; if (parameterTypes2 != null) { messageHandler = FindTypedHandler(exactMatch: true) ?? FindTypedHandler(exactMatch: false); } if (messageHandler == null) { messageHandler = FindUntypedHandler(allowNullableConversions: false) ?? FindUntypedHandler(allowNullableConversions: true); } if (messageHandler == null && parameterTypes2 == null) { messageHandler = handlersSnapshot.FirstOrDefault((MessageHandler h) => h.ParameterCountWithoutRpcInfo == parameters2.Length && h.Mask == mask); } if (messageHandler == null) { LogError($"No RPC overload matched method '{methodName2}' for mask {mask} and parameter list."); return null; } Message message = new Message(modId, methodName2, mask, messageHandler.OverloadKey, messageSizePolicy); ParameterInfo[] parameters3 = messageHandler.Parameters; int parameterCountWithoutRpcInfo = messageHandler.ParameterCountWithoutRpcInfo; if (parameterCountWithoutRpcInfo != parameters2.Length) { throw new ArgumentException($"Parameter count mismatch for {methodName2}: expected {parameterCountWithoutRpcInfo}, got {parameters2.Length}", "parameters"); } for (int i = 0; i < parameterCountWithoutRpcInfo; i++) { Type parameterType = parameters3[i].ParameterType; object obj = parameters2[i]; if (obj == null) { if (parameterType.IsValueType && Nullable.GetUnderlyingType(parameterType) == null) { throw new ArgumentNullException("parameters", $"Parameter {i} for {methodName2} cannot be null; expected non-nullable {parameterType}."); } message.WriteObject(parameterType, null); } else { if (!IsParameterValueCompatible(parameterType, obj)) { throw new ArgumentException($"Parameter {i} type mismatch: expected {parameterType}, got {obj.GetType()}", "parameters"); } message.WriteObject(parameterType, obj); } } if (message.Length() > messageSizePolicy.MaxLogicalSize) { LogError("Message exceeds maximum allowed overall size."); return null; } return message; } Message message2 = new Message(modId, methodName2, mask, messageSizePolicy); if (parameterTypes2 != null) { if (parameterTypes2.Length != parameters2.Length) { throw new ArgumentException($"Parameter type count mismatch: expected {parameterTypes2.Length}, got {parameters2.Length}", "parameters"); } for (int j = 0; j < parameters2.Length; j++) { Type type = parameterTypes2[j]; object obj2 = parameters2[j]; if (obj2 == null) { if (type.IsValueType && Nullable.GetUnderlyingType(type) == null) { throw new ArgumentNullException("parameters", $"Parameter {j} for {methodName2} cannot be null; expected non-nullable {type}."); } message2.WriteObject(type, null); } else { if (!IsParameterValueCompatible(type, obj2)) { throw new ArgumentException($"Parameter {j} type mismatch: expected {type}, got {obj2.GetType()}", "parameters"); } message2.WriteObject(type, obj2); } } } else { for (int k = 0; k < parameters2.Length; k++) { object obj3 = parameters2[k] ?? throw new ArgumentNullException("parameters", $"Parameter {k} is null for unregistered RPC {methodName2}; use typed RPC overload."); message2.WriteObject(obj3.GetType(), obj3); } } if (message2.Length() > messageSizePolicy.MaxLogicalSize) { LogError("Message exceeds maximum allowed overall size."); return null; } return message2; } catch (Exception arg) { LogError($"BuildMessage failed: {arg}"); return null; } MessageHandler? FindTypedHandler(bool exactMatch) { MessageHandler[] array2 = handlersSnapshot; foreach (MessageHandler messageHandler3 in array2) { if (messageHandler3.Mask == mask) { ParameterInfo[] parameters5 = messageHandler3.Parameters; int parameterCountWithoutRpcInfo3 = messageHandler3.ParameterCountWithoutRpcInfo; if (parameterCountWithoutRpcInfo3 == parameterTypes2.Length) { bool flag2 = true; for (int num = 0; num < parameterCountWithoutRpcInfo3; num++) { Type parameterType3 = parameters5[num].ParameterType; Type suppliedType = parameterTypes2[num] ?? throw new ArgumentNullException("parameterTypes", $"Parameter type {num} for {methodName2} cannot be null."); if (!IsParameterTypeCompatible(parameterType3, suppliedType, exactMatch)) { flag2 = false; break; } object obj5 = parameters2[num]; if (obj5 == null) { if (parameterType3.IsValueType && Nullable.GetUnderlyingType(parameterType3) == null) { flag2 = false; break; } } else if (!IsParameterValueCompatible(parameterType3, obj5)) { flag2 = false; break; } } if (flag2) { return messageHandler3; } } } } return null; } MessageHandler? FindUntypedHandler(bool allowNullableConversions) { MessageHandler[] array = handlersSnapshot; foreach (MessageHandler messageHandler2 in array) { if (messageHandler2.Mask == mask) { ParameterInfo[] parameters4 = messageHandler2.Parameters; int parameterCountWithoutRpcInfo2 = messageHandler2.ParameterCountWithoutRpcInfo; if (parameterCountWithoutRpcInfo2 == parameters2.Length) { bool flag = true; for (int m = 0; m < parameterCountWithoutRpcInfo2; m++) { Type parameterType2 = parameters4[m].ParameterType; object obj4 = parameters2[m]; if (obj4 == null) { if (parameterType2.IsValueType && Nullable.GetUnderlyingType(parameterType2) == null) { flag = false; break; } } else if (allowNullableConversions ? (!IsParameterValueCompatible(parameterType2, obj4)) : (!IsParameterValueCompatibleWithoutNullableFallback(parameterType2, obj4))) { flag = false; break; } } if (flag) { return messageHandler2; } } } } return null; } } private void DispatchIncoming(Message message, ulong from) { Message message2 = message; if ((IncomingValidator != null && !IncomingValidator(message2, from)) || !rateLimiter.IncomingAllowed()) { return; } MessageHandler[] handlersSnapshot; lock (rpcLock) { if (!rpcs.TryGetValue(message2.ModID, out Dictionary> value)) { LogWarning($"No mod {message2.ModID}"); return; } if (!value.TryGetValue(message2.MethodName, out var value2)) { LogWarning("No method " + message2.MethodName); return; } handlersSnapshot = value2.ToArray(); } MessageHandler chosenHandler = null; object[] chosenParams = null; MessageHandler fallbackHandler = null; object[] fallbackParams = null; bool flag = !string.IsNullOrEmpty(message2.OverloadKey); bool flag2 = false; if (flag) { foreach (MessageHandler messageHandler in handlersSnapshot) { if (messageHandler.Mask == message2.Mask && !(messageHandler.OverloadKey != message2.OverloadKey)) { flag2 = true; break; } } } if (flag2) { TryDispatch(true); if (chosenHandler == null) { TryDispatch(false); } } else { TryDispatch(null); } if (chosenHandler == null) { chosenHandler = fallbackHandler; chosenParams = fallbackParams; } if (chosenHandler == null || chosenParams == null) { LogWarning($"No matching overload for {message2.MethodName} (mask {message2.Mask})"); return; } try { chosenHandler.Method.Invoke(chosenHandler.Target, chosenParams); } catch (Exception arg) { LogError($"Invoke RPC error: {arg}"); } void TryDispatch(bool? preferOverloadKeyMatch) { foreach (MessageHandler messageHandler2 in handlersSnapshot) { if (messageHandler2.Mask == message2.Mask) { if (preferOverloadKeyMatch.HasValue) { bool flag3 = messageHandler2.OverloadKey == message2.OverloadKey; if (preferOverloadKeyMatch.Value != flag3) { continue; } } if (TryDeserializeForHandler(message2, messageHandler2, from, isLocalLoopback: true, out object[] callParams, out int unread)) { if (unread == 0) { chosenHandler = messageHandler2; chosenParams = callParams; break; } if (fallbackHandler == null) { fallbackHandler = messageHandler2; } if (fallbackParams == null) { fallbackParams = callParams; } } } } } } private bool TryDeserializeForHandler(Message source, MessageHandler handler, ulong from, bool isLocalLoopback, out object[] callParams, out int unread) { callParams = null; unread = int.MaxValue; Message.ReadCursor readCursor = source.SaveReadCursor(); Message.ReadCursor cursor = readCursor; try { if (readCursor.Position == 0) { cursor = AdvanceCursorPastHeader(source); } source.RestoreReadCursor(cursor); ParameterInfo[] parameters = handler.Parameters; int parameterCountWithoutRpcInfo = handler.ParameterCountWithoutRpcInfo; callParams = new object[parameters.Length]; for (int i = 0; i < parameterCountWithoutRpcInfo; i++) { callParams[i] = source.ReadObject(parameters[i].ParameterType); } if (handler.TakesInfo) { Type parameterType = parameters[^1].ParameterType; callParams[parameters.Length - 1] = CreateRpcInfoInstance(parameterType, from, isLocalLoopback); } unread = source.UnreadLength(); return true; } catch (Exception ex) { LogDeserializeFailureThrottled(ex, (handler.Method.DeclaringType?.Name ?? "UnknownType") + "." + handler.Method.Name); return false; } finally { source.RestoreReadCursor(readCursor); } } private static Message.ReadCursor AdvanceCursorPastHeader(Message message) { message.ReadByte(); message.ReadUInt(); message.ReadString(); message.ReadInt(); if (message.ProtocolVersion >= 3 && message.ReadBool()) { message.ReadString(); } return message.SaveReadCursor(); } private object CreateRpcInfoInstance(Type infoType, ulong from, bool isLocalLoopback) { try { ConstructorInfo constructor = infoType.GetConstructor(new Type[3] { typeof(ulong), typeof(string), typeof(bool) }); if (constructor != null) { return constructor.Invoke(new object[3] { from, from.ToString(), isLocalLoopback }); } ConstructorInfo constructor2 = infoType.GetConstructor(new Type[1] { typeof(ulong) }); if (constructor2 != null) { return constructor2.Invoke(new object[1] { from }); } object obj = Activator.CreateInstance(infoType); if (obj == null) { return null; } AssignRpcIdentityMembers(obj, infoType, from, isLocalLoopback); return obj; } catch (Exception ex) { LogExceptionThrottled("rpc_info:" + (infoType.FullName ?? infoType.Name), error: false, "CreateRpcInfoInstance failed for " + (infoType.FullName ?? infoType.Name) + ".", ex); return null; } } private static void AssignRpcIdentityMembers(object instance, Type infoType, ulong steamId64, bool isLocalLoopback) { string steamIdString = steamId64.ToString(); AssignRpcIdentityMember(instance, infoType, "SenderSteamID", steamId64, steamIdString); AssignRpcIdentityMember(instance, infoType, "Sender", steamId64, steamIdString); AssignRpcIdentityMember(instance, infoType, "SteamId64", steamId64, steamIdString); AssignRpcIdentityMember(instance, infoType, "SteamIdString", steamId64, steamIdString); AssignRpcLoopbackMember(instance, infoType, isLocalLoopback); } private static void AssignRpcIdentityMember(object instance, Type infoType, string memberName, ulong steamId64, string steamIdString) { object instance2 = instance; FieldInfo field = infoType.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { TryAssignMemberValue(field.FieldType, delegate(object value) { field.SetValue(instance2, value); }, steamId64, steamIdString); return; } PropertyInfo property = infoType.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(property == null) && property.CanWrite) { TryAssignMemberValue(property.PropertyType, delegate(object value) { property.SetValue(instance2, value); }, steamId64, steamIdString); } } private static void AssignRpcLoopbackMember(object instance, Type infoType, bool isLocalLoopback) { object instance2 = instance; FieldInfo field = infoType.GetField("IsLocalLoopback", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { TryAssignLoopbackMemberValue(field.FieldType, delegate(object value) { field.SetValue(instance2, value); }, isLocalLoopback); return; } PropertyInfo property = infoType.GetProperty("IsLocalLoopback", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(property == null) && property.CanWrite) { TryAssignLoopbackMemberValue(property.PropertyType, delegate(object value) { property.SetValue(instance2, value); }, isLocalLoopback); } } private static void TryAssignLoopbackMemberValue(Type memberType, Action assign, bool isLocalLoopback) { if (memberType == typeof(bool)) { assign(isLocalLoopback); } else if (memberType == typeof(bool?)) { assign(isLocalLoopback); } } private static void TryAssignMemberValue(Type memberType, Action assign, ulong steamId64, string steamIdString) { Type type = Nullable.GetUnderlyingType(memberType) ?? memberType; if (type == typeof(string)) { assign(steamIdString); } else if (IsCompatibleIntegralType(type)) { assign(ConvertIntegral(steamId64, type)); } } private static bool IsCompatibleIntegralType(Type t) { if (!(t == typeof(ulong)) && !(t == typeof(long)) && !(t == typeof(uint)) && !(t == typeof(int)) && !(t == typeof(ushort)) && !(t == typeof(short)) && !(t == typeof(byte))) { return t == typeof(sbyte); } return true; } private static object ConvertIntegral(ulong value, Type targetType) { if (targetType == typeof(ulong)) { return value; } checked { if (targetType == typeof(long)) { return (long)value; } if (targetType == typeof(uint)) { return (uint)value; } if (targetType == typeof(int)) { return (int)value; } if (targetType == typeof(ushort)) { return (ushort)value; } if (targetType == typeof(short)) { return (short)value; } if (targetType == typeof(byte)) { return (byte)value; } if (targetType == typeof(sbyte)) { return (sbyte)value; } throw new InvalidCastException($"Unsupported integral conversion to {targetType}."); } } private static string BuildOverloadKey(MessageHandler handler) { ParameterInfo[] parameters = handler.Parameters; int num = (handler.TakesInfo ? (parameters.Length - 1) : parameters.Length); if (num <= 0) { return string.Empty; } return string.Join("|", from p in parameters.Take(num) select p.ParameterType.AssemblyQualifiedName ?? p.ParameterType.FullName ?? p.ParameterType.Name); } private static bool IsParameterValueCompatible(Type parameterType, object value) { Type underlyingType = Nullable.GetUnderlyingType(parameterType); if (!(underlyingType != null)) { return parameterType.IsAssignableFrom(value.GetType()); } return underlyingType.IsAssignableFrom(value.GetType()); } private static bool IsParameterValueCompatibleWithoutNullableFallback(Type parameterType, object value) { if (Nullable.GetUnderlyingType(parameterType) != null) { return false; } return parameterType.IsAssignableFrom(value.GetType()); } private static bool IsParameterTypeCompatible(Type parameterType, Type suppliedType, bool exactMatch) { if (exactMatch) { return parameterType == suppliedType; } Type underlyingType = Nullable.GetUnderlyingType(parameterType); if (!parameterType.IsAssignableFrom(suppliedType)) { return underlyingType?.IsAssignableFrom(suppliedType) ?? false; } return true; } } public class SteamNetworkingService : INetworkingService, INetworkingServiceStateTransfer { private class FragmentBuffer { public int Total; public DateTime FirstSeen = DateTime.UtcNow; public Dictionary Fragments = new Dictionary(); } private sealed class HandlerRegistration { public string MethodName = string.Empty; public MessageHandler Handler; } private sealed class RuntimeRegistration { public object? Instance; public Type Type; public uint ModId; public int Mask; public RegistrationToken Token; } private sealed class RegistrationToken : IDisposable { private readonly object sync = new object(); private Action? disposeAction; private bool disposed; public void SetDisposeAction(Action? action) { Action action2 = null; bool flag = false; lock (sync) { if (disposed) { flag = true; } else { action2 = disposeAction; disposeAction = action; } } if (flag) { action?.Invoke(); } else { action2?.Invoke(); } } public void Dispose() { Action action; lock (sync) { if (disposed) { return; } disposed = true; action = disposeAction; disposeAction = null; } action?.Invoke(); } } private class HandshakeState { public string? PeerPub; public string LocalNonce = string.Empty; public byte[]? Sym; public bool Completed; } private class SlidingWindowRateLimiter { private readonly int limit; private readonly TimeSpan window; private readonly Queue q = new Queue(); private readonly object qLock = new object(); public SlidingWindowRateLimiter(int limit, TimeSpan window) { this.limit = limit; this.window = window; } public bool Allowed() { lock (qLock) { DateTime utcNow = DateTime.UtcNow; while (q.Count > 0 && utcNow - q.Peek() > window) { q.Dequeue(); } if (q.Count >= limit) { return false; } q.Enqueue(utcNow); return true; } } public bool IncomingAllowed() { return Allowed(); } } private enum Priority { High, Normal, Low } private class QueuedSend { public byte[] Framed; public CSteamID Target; public ReliableType Reliable; public DateTime Enqueued; } private class UnackedMessage { public byte[] Framed; public CSteamID Target; public ReliableType Reliable; public DateTime LastSent; public int Attempts; public ulong msgId => BinaryPrimitives.ReadUInt64LittleEndian(Framed.AsSpan(1, 8)); } private class MessageHandler { public object Target; public MethodInfo Method; public ParameterInfo[] Parameters; public bool TakesInfo; public int Mask; public int ParameterCountWithoutRpcInfo; public string OverloadKey = string.Empty; } private const string LogSource = "SteamNetworkingService"; private const int CHANNEL = 120; private const int MAX_IN_MESSAGES = 500; private const int MAX_NORMAL_QUEUE_DEPTH = 256; private const int MAX_LOW_QUEUE_DEPTH = 128; private const double DebugLogCooldownSeconds = 2.0; private const double QueueOverflowWarningCooldownSeconds = 2.0; private const string GetLocalSteam64DebugCooldownKey = "SteamNetworkingService.GetLocalSteam64"; private const string IsHostDebugCooldownKey = "SteamNetworkingService.IsHost"; private const string LocalSteamIdDebugCooldownKey = "SteamNetworkingService.LocalSteamId"; private const string LobbyOwnerDebugCooldownKey = "SteamNetworkingService.LobbyOwner"; private const string ProcessIncomingFrameExceptionCooldownKey = "SteamNetworkingService.ReceiveMessages.ProcessIncomingFrameException"; private const string ReceiveMessagesOuterExceptionCooldownKey = "SteamNetworkingService.ReceiveMessages.OuterException"; private readonly IntPtr[] inMessages = new IntPtr[500]; private readonly object rpcLock = new object(); private readonly object cryptoStateLock = new object(); private CSteamID[] players = Array.Empty(); private readonly List lobbyDataKeys = new List(); private readonly List playerDataKeys = new List(); private readonly Dictionary> lastPlayerData = new Dictionary>(); private readonly Dictionary lastLobbyData = new Dictionary(); private readonly HashSet localEmptyLobbyDataKeys = new HashSet(); private readonly HashSet localEmptyPlayerDataKeys = new HashSet(); private Func getNumLobbyMembers = SteamMatchmaking.GetNumLobbyMembers; private Func getLobbyOwner = SteamMatchmaking.GetLobbyOwner; private Func getLocalSteamId = SteamUser.GetSteamID; private Func getLobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex; private Action setLobbyData = delegate(CSteamID lobby, string key, string value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) SteamMatchmaking.SetLobbyData(lobby, key, value); }; private Func getLobbyData = SteamMatchmaking.GetLobbyData; private Action setLobbyMemberData = SteamMatchmaking.SetLobbyMemberData; private Func getLobbyMemberData = SteamMatchmaking.GetLobbyMemberData; private Func receiveMessagesOnChannel = SteamNetworkingMessages.ReceiveMessagesOnChannel; private Callback? cbLobbyEnter; private Callback? cbLobbyCreated; private Callback? cbLobbyChatUpdate; private Callback? cbLobbyDataUpdate; private readonly Dictionary>> rpcs = new Dictionary>>(); private readonly List runtimeRegistrations = new List(); private readonly Queue normalQueue = new Queue(); private readonly Queue lowQueue = new Queue(); private readonly object queueLock = new object(); private readonly Dictionary<(ulong target, ulong msgId), UnackedMessage> unacked = new Dictionary<(ulong, ulong), UnackedMessage>(); private readonly object unackedLock = new object(); private readonly List<(ulong sender, ulong msgId)> staleFragmentKeys = new List<(ulong, ulong)>(); private TimeSpan ackTimeout = TimeSpan.FromSeconds(1.2); private int maxRetransmitAttempts = 5; private long _nextMessageId; private readonly Dictionary outgoingSequencePerMod = new Dictionary(); private readonly Dictionary> lastSeenSequence = new Dictionary>(); private readonly Dictionary rateLimiters = new Dictionary(); private readonly Dictionary perPeerSymmetricKey = new Dictionary(); private byte[]? globalSharedSecret; private HMACSHA256? globalHmac; private readonly Dictionary> modSigners = new Dictionary>(); private readonly Dictionary modPublicKeys = new Dictionary(); private RSAParameters[] modPublicKeysSnapshot = Array.Empty(); private readonly Dictionary handshakeStates = new Dictionary(); private const byte FRAG_FLAG = 1; private const byte COMPRESSED_FLAG = 2; private const byte HMAC_FLAG = 4; private const byte SIGN_FLAG = 8; private const byte ACK_FLAG = 16; private const int FRAME_HEADER_SIZE = 25; private RSA? LocalRsa; private Func localRsaFactory = () => RSA.Create(2048); private readonly MessageSizePolicy messageSizePolicy; private readonly Dictionary<(ulong sender, ulong msgId), FragmentBuffer> fragmentBuffers = new Dictionary<(ulong, ulong), FragmentBuffer>(); private readonly object fragmentLock = new object(); private readonly TimeSpan FragmentTimeout = TimeSpan.FromSeconds(30.0); private readonly TimeSpan FragmentCleanupInterval = TimeSpan.FromSeconds(5.0); private DateTime nextFragmentCleanupAt = DateTime.MinValue; public bool IsInitialized { get; private set; } public bool InLobby { get; private set; } public ulong HostSteamId64 { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_0042: Unknown result type (might be due to invalid IL or missing references) if (Lobby == CSteamID.Nil) { return 0uL; } if (!TryGetLobbyOwner(Lobby, out var owner, "SteamNetworkingService.LobbyOwner", "HostSteamId64")) { return 0uL; } if (owner == CSteamID.Nil) { return 0uL; } return owner.m_SteamID; } } public string HostIdString { get { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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_0039: Unknown result type (might be due to invalid IL or missing references) if (Lobby == CSteamID.Nil) { return string.Empty; } if (!TryGetLobbyOwner(Lobby, out var owner, "SteamNetworkingService.LobbyOwner", "HostIdString")) { return string.Empty; } if (!(owner == CSteamID.Nil)) { return ((object)(CSteamID)(ref owner)).ToString(); } return string.Empty; } } public CSteamID Lobby { get; private set; } = CSteamID.Nil; public bool IsHost { get { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) try { if (!InLobby) { return false; } if (!TryGetLobbyOwner(Lobby, out var owner, "SteamNetworkingService.IsHost", "IsHost")) { return false; } if (owner == CSteamID.Nil) { return false; } if (!TryGetLocalSteamId(out var steamId, "SteamNetworkingService.IsHost", "IsHost")) { return false; } return owner == steamId; } catch (Exception ex) { NetLog.DebugThrottled("SteamNetworkingService", "SteamNetworkingService.IsHost", 2.0, "IsHost check failed: " + ex.GetType().Name + ": " + ex.Message); return false; } } } public Func? IncomingValidator { get; set; } public event Action? LobbyCreated; public event Action? LobbyEntered; public event Action? LobbyLeft; public event Action? PlayerEntered; public event Action? PlayerLeft; public event Action? LobbyDataChanged; public event Action? PlayerDataChanged; public ulong GetLocalSteam64() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!TryGetLocalSteamId(out var steamId, "SteamNetworkingService.GetLocalSteam64", "GetLocalSteam64")) { return 0uL; } return steamId.m_SteamID; } public ulong[] GetLobbyMemberSteamIds() { //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) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0058: 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) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0068: 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) if (!InLobby || Lobby == CSteamID.Nil) { return Array.Empty(); } try { int num = getNumLobbyMembers(Lobby); if (num <= 0) { return Array.Empty(); } ulong[] array = new ulong[num]; int num2 = 0; for (int i = 0; i < num; i++) { CSteamID val = getLobbyMemberByIndex(Lobby, i); if (!(val == CSteamID.Nil)) { array[num2++] = val.m_SteamID; } } if (num2 == 0) { return Array.Empty(); } if (num2 == array.Length) { return array; } ulong[] array2 = new ulong[num2]; Array.Copy(array, array2, num2); return array2; } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"GetLobbyMemberSteamIds error: {arg}"); return Array.Empty(); } } private ulong NextMessageId() { return (ulong)Interlocked.Increment(ref _nextMessageId); } public SteamNetworkingService(MessageSizePolicy? messageSizePolicy = null) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) this.messageSizePolicy = messageSizePolicy ?? CreateDefaultMessageSizePolicy(); } private static void LogInfo(string message) { try { NetLog.Info("SteamNetworkingService", message); } catch (Exception ex) { Trace.TraceWarning("[SteamNetworkingService] Failed to write info log. Exception: " + ex.GetType().Name + ": " + ex.Message + ". Original message: " + message); } } private static MessageSizePolicy CreateDefaultMessageSizePolicy() { try { return new MessageSizePolicy(524288); } catch { return Message.DefaultSizePolicy; } } public void Initialize() { if (IsInitialized) { return; } GameObject createdPumpGameObject = null; SteamCallbackPump createdPumpComponent = null; bool callbackPumpingEnabled = SteamCallbackPump.CallbackPumpingEnabled; bool enabledPumpingInThisInitialize = false; bool flag = !Application.isPlaying; if (Application.isPlaying) { try { PrepareCanonicalSteamCallbackPump(out createdPumpGameObject, out createdPumpComponent); SteamCallbackPump.EnablePumping(); enabledPumpingInThisInitialize = true; flag = true; } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"Failed to create SteamCallbackPump: {arg}"); } } if (!flag) { CleanupFailedPumpSetup(enabledPumpingInThisInitialize, callbackPumpingEnabled, createdPumpGameObject, createdPumpComponent, "Initialize.DisablePumpingAfterPumpSetupFailure"); IsInitialized = false; } else if (!TryInitializeSteamCallbacksAndCrypto()) { CleanupFailedPumpSetup(enabledPumpingInThisInitialize, callbackPumpingEnabled, createdPumpGameObject, createdPumpComponent, "Initialize.DisablePumpingAfterCallbackFailure"); IsInitialized = false; } else { IsInitialized = true; LogInfo("SteamNetworkingService initialized"); } } private static SteamCallbackPump PrepareCanonicalSteamCallbackPump(out GameObject? createdPumpGameObject, out SteamCallbackPump? createdPumpComponent) { //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown createdPumpGameObject = null; createdPumpComponent = null; List source = (from p in Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0) where (Object)(object)p != (Object)null && (Object)(object)((Component)p).gameObject != (Object)null orderby ((Behaviour)p).isActiveAndEnabled descending, ((Component)p).gameObject.activeInHierarchy descending, ((Object)p).GetInstanceID() select p).ToList(); SteamCallbackPump steamCallbackPump = source.FirstOrDefault(); if ((Object)(object)steamCallbackPump == (Object)null) { GameObject val = GameObject.Find("SteamCallbackPump"); if ((Object)(object)val == (Object)null) { val = (createdPumpGameObject = new GameObject("SteamCallbackPump")); LogInfo("Created SteamCallbackPump GameObject."); } steamCallbackPump = val.GetComponent(); if ((Object)(object)steamCallbackPump == (Object)null) { steamCallbackPump = val.AddComponent(); if ((Object)(object)createdPumpGameObject == (Object)null) { createdPumpComponent = steamCallbackPump; } } } else { CleanupDuplicatePumps(steamCallbackPump, source.Skip(1)); } GameObject gameObject = ((Component)steamCallbackPump).gameObject; if (!gameObject.activeSelf) { gameObject.SetActive(true); } if (!gameObject.activeInHierarchy) { gameObject.transform.SetParent((Transform)null, true); if (!gameObject.activeSelf) { gameObject.SetActive(true); } } if (!((Behaviour)steamCallbackPump).enabled) { ((Behaviour)steamCallbackPump).enabled = true; } Object.DontDestroyOnLoad((Object)(object)gameObject); return steamCallbackPump; } private static void CleanupDuplicatePumps(SteamCallbackPump canonicalPump, IEnumerable duplicatePumps) { foreach (SteamCallbackPump duplicatePump in duplicatePumps) { if (!((Object)(object)duplicatePump == (Object)null)) { GameObject gameObject = ((Component)duplicatePump).gameObject; if (!((Object)(object)gameObject == (Object)null)) { bool flag = HasOnlyTransformAndSteamCallbackPumpComponents(gameObject); bool flag2 = flag && (Object)(object)gameObject.transform != (Object)null && ((Component)canonicalPump).transform.IsChildOf(gameObject.transform); DestroyPumpDuringStartup((Object)(object)((flag && !flag2) ? ((SteamCallbackPump)(object)gameObject) : duplicatePump)); } } } } private static bool HasOnlyTransformAndSteamCallbackPumpComponents(GameObject pumpObject) { Component[] components = pumpObject.GetComponents(); bool flag = false; bool flag2 = false; int num = 0; foreach (Component val in components) { if ((Object)(object)val == (Object)null) { continue; } num++; if (val is Transform) { flag = true; continue; } if (val is SteamCallbackPump) { flag2 = true; continue; } return false; } return num == 2 && flag && flag2; } private static void DestroyPumpDuringStartup(Object target) { if (target is SteamCallbackPump steamCallbackPump) { ((Behaviour)steamCallbackPump).enabled = false; } else { GameObject val = (GameObject)(object)((target is GameObject) ? target : null); if (val != null) { val.SetActive(false); } } if (Application.isPlaying) { Object.Destroy(target); } else { Object.DestroyImmediate(target); } } private void CleanupFailedPumpSetup(bool enabledPumpingInThisInitialize, bool pumpingWasEnabledBeforeInitialize, GameObject? createdPumpGameObject, SteamCallbackPump? createdPumpComponent, string disablePumpingContextKey) { if (enabledPumpingInThisInitialize && !pumpingWasEnabledBeforeInitialize) { try { SteamCallbackPump.DisablePumping(); } catch (Exception ex) { string text = ((disablePumpingContextKey == "Initialize.DisablePumpingAfterPumpSetupFailure") ? "Initialize failed pump setup cleanup at SteamCallbackPump.DisablePumping" : ((disablePumpingContextKey == "Initialize.DisablePumpingAfterCallbackFailure") ? "Initialize failed callback/crypto cleanup at SteamCallbackPump.DisablePumping" : "Initialize failed cleanup at SteamCallbackPump.DisablePumping")); NetLog.DebugThrottled("SteamNetworkingService", disablePumpingContextKey, 2.0, text + ": " + ex.GetType().Name + ": " + ex.Message); } } try { if ((Object)(object)createdPumpGameObject != (Object)null) { if (Application.isPlaying) { Object.Destroy((Object)(object)createdPumpGameObject); } else { Object.DestroyImmediate((Object)(object)createdPumpGameObject); } } else if ((Object)(object)createdPumpComponent != (Object)null) { if (Application.isPlaying) { Object.Destroy((Object)(object)createdPumpComponent); } else { Object.DestroyImmediate((Object)(object)createdPumpComponent); } } } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"Failed to cleanup created SteamCallbackPump after initialization failure: {arg}"); } } private bool TryInitializeSteamCallbacksAndCrypto() { try { cbLobbyEnter = Callback.Create((DispatchDelegate)OnLobbyEnter); cbLobbyCreated = Callback.Create((DispatchDelegate)OnLobbyCreated); cbLobbyChatUpdate = Callback.Create((DispatchDelegate)OnLobbyChatUpdate); cbLobbyDataUpdate = Callback.Create((DispatchDelegate)OnLobbyDataUpdate); LocalRsa = localRsaFactory(); return true; } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"Failed to initialize Steam callbacks and crypto: {arg}"); DisposeSteamCallbacks(); LocalRsa?.Dispose(); LocalRsa = null; return false; } } public void Shutdown() { //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) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) if (InLobby || Lobby != CSteamID.Nil) { LeaveLobby(); } IncomingValidator = null; DisposeSteamCallbacks(); lock (rpcLock) { rpcs.Clear(); runtimeRegistrations.Clear(); lobbyDataKeys.Clear(); playerDataKeys.Clear(); localEmptyLobbyDataKeys.Clear(); localEmptyPlayerDataKeys.Clear(); } ClearOutboundState(); lastLobbyData.Clear(); lastPlayerData.Clear(); players = Array.Empty(); Lobby = CSteamID.Nil; InLobby = false; lock (cryptoStateLock) { modSigners.Clear(); modPublicKeys.Clear(); modPublicKeysSnapshot = Array.Empty(); handshakeStates.Clear(); ClearPerPeerSymmetricKeysUnderLock(); ClearGlobalSharedSecretUnderLock(); globalHmac?.Dispose(); globalHmac = null; } LocalRsa?.Dispose(); LocalRsa = null; IsInitialized = false; try { SteamCallbackPump.DisableAndDestroyExisting(); } catch (Exception ex) { NetLog.DebugThrottled("SteamNetworkingService", "Shutdown.DisableAndDestroyExisting", 2.0, "Shutdown cleanup at SteamCallbackPump.DisableAndDestroyExisting failed: " + ex.GetType().Name + ": " + ex.Message); } lock (lastSeenSequence) { lastSeenSequence.Clear(); } lock (rateLimiters) { rateLimiters.Clear(); } lock (outgoingSequencePerMod) { outgoingSequencePerMod.Clear(); } lock (fragmentLock) { fragmentBuffers.Clear(); staleFragmentKeys.Clear(); nextFragmentCleanupAt = DateTime.MinValue; } LogInfo("SteamNetworkingService shutdown"); } private void DisposeSteamCallbacks() { DisposeSteamCallback(cbLobbyEnter, "cbLobbyEnter"); DisposeSteamCallback(cbLobbyCreated, "cbLobbyCreated"); DisposeSteamCallback(cbLobbyChatUpdate, "cbLobbyChatUpdate"); DisposeSteamCallback(cbLobbyDataUpdate, "cbLobbyDataUpdate"); cbLobbyEnter = null; cbLobbyCreated = null; cbLobbyChatUpdate = null; cbLobbyDataUpdate = null; } private static void DisposeSteamCallback(Callback? callback, string fieldName) { if (callback == null) { return; } try { callback.Dispose(); } catch (Exception ex) { NetLog.DebugThrottled("SteamNetworkingService", "SteamNetworkingService.DisposeSteamCallback." + fieldName, 2.0, "Failed to dispose " + fieldName + ": " + ex.GetType().Name + ": " + ex.Message); } } public void CreateLobby(int maxPlayers = 8) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized) { NetLog.Error("SteamNetworkingService", "CreateLobby called before SteamNetworkingService.Initialize."); return; } if (maxPlayers <= 0) { NetLog.Warning("SteamNetworkingService", $"CreateLobby maxPlayers {maxPlayers} is invalid. Clamping to 1."); maxPlayers = 1; } try { SteamMatchmaking.CreateLobby((ELobbyType)0, maxPlayers); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"CreateLobby failed: {arg}"); } } public void JoinLobby(ulong lobbySteamId64) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized) { NetLog.Error("SteamNetworkingService", "JoinLobby called before SteamNetworkingService.Initialize."); return; } if (lobbySteamId64 == 0L) { NetLog.Warning("SteamNetworkingService", "JoinLobby called with invalid lobby id 0."); return; } try { SteamMatchmaking.JoinLobby(new CSteamID(lobbySteamId64)); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"JoinLobby failed for lobby {lobbySteamId64}: {arg}"); } } public void LeaveLobby() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //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) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (InLobby && !(Lobby == CSteamID.Nil)) { try { SteamMatchmaking.LeaveLobby(Lobby); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"LeaveLobby failed for lobby {Lobby}: {arg}"); } OnLobbyLeftInternal(); } } public void InviteToLobby(ulong steamId64) { //IL_0021: 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_0057: 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) if (!IsInitialized) { NetLog.Error("SteamNetworkingService", "InviteToLobby called before SteamNetworkingService.Initialize."); return; } if (!InLobby || Lobby == CSteamID.Nil) { NetLog.Warning("SteamNetworkingService", "InviteToLobby called while not in a lobby."); return; } if (steamId64 == 0L) { NetLog.Warning("SteamNetworkingService", "InviteToLobby called with invalid target Steam64 id 0."); return; } try { SteamMatchmaking.InviteUserToLobby(Lobby, new CSteamID(steamId64)); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"InviteToLobby failed for target {steamId64}: {arg}"); } } private void OnLobbyEnter(LobbyEnter_t param) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) NetLog.Debug("SteamNetworkingService", $"LobbyEnter {param.m_ulSteamIDLobby}"); Lobby = new CSteamID(param.m_ulSteamIDLobby); InLobby = true; RefreshPlayerList(); this.LobbyEntered?.Invoke(); } private void OnLobbyCreated(LobbyCreated_t param) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Invalid comparison between Unknown and I4 //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) NetLog.Debug("SteamNetworkingService", $"LobbyCreated: {param.m_eResult}"); if ((int)param.m_eResult == 1) { Lobby = new CSteamID(param.m_ulSteamIDLobby); InLobby = true; RefreshPlayerList(); this.LobbyCreated?.Invoke(); } else { NetLog.Error("SteamNetworkingService", $"Lobby creation failed: {param.m_eResult}"); } } private void OnLobbyChatUpdate(LobbyChatUpdate_t param) { //IL_0008: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_003f: 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_0056: Unknown result type (might be due to invalid IL or missing references) try { RefreshPlayerList(); CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(param.m_ulSteamIDUserChanged); EChatMemberStateChange val2 = (EChatMemberStateChange)param.m_rgfChatMemberStateChange; if ((val2 & 1) != 0) { this.PlayerEntered?.Invoke(val.m_SteamID); } EChatMemberStateChange val3 = (EChatMemberStateChange)30; if ((val2 & val3) != 0) { CleanupPeerState(val.m_SteamID); this.PlayerLeft?.Invoke(val.m_SteamID); } } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"OnLobbyChatUpdate error: {arg}"); } } internal void CleanupPeerState(ulong steamId64) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (steamId64 == 0L) { return; } lastPlayerData.Remove(new CSteamID(steamId64)); lock (lastSeenSequence) { lastSeenSequence.Remove(steamId64); } lock (rateLimiters) { rateLimiters.Remove(steamId64); } lock (cryptoStateLock) { handshakeStates.Remove(steamId64); if (perPeerSymmetricKey.TryGetValue(steamId64, out byte[] value) && value != null) { CryptographicOperations.ZeroMemory(value); perPeerSymmetricKey.Remove(steamId64); } } } internal void OnLobbyLeftInternal() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) players = Array.Empty(); lastLobbyData.Clear(); lastPlayerData.Clear(); lock (rpcLock) { localEmptyLobbyDataKeys.Clear(); localEmptyPlayerDataKeys.Clear(); } Lobby = CSteamID.Nil; InLobby = false; ClearOutboundState(); lock (lastSeenSequence) { lastSeenSequence.Clear(); } lock (rateLimiters) { rateLimiters.Clear(); } lock (outgoingSequencePerMod) { outgoingSequencePerMod.Clear(); } lock (fragmentLock) { fragmentBuffers.Clear(); staleFragmentKeys.Clear(); nextFragmentCleanupAt = DateTime.MinValue; } lock (cryptoStateLock) { handshakeStates.Clear(); ClearPerPeerSymmetricKeysUnderLock(); ClearGlobalSharedSecretUnderLock(); globalHmac?.Dispose(); globalHmac = null; } this.LobbyLeft?.Invoke(); } private void ClearPerPeerSymmetricKeys() { lock (cryptoStateLock) { ClearPerPeerSymmetricKeysUnderLock(); } } private void ClearPerPeerSymmetricKeysUnderLock() { foreach (byte[] value in perPeerSymmetricKey.Values) { if (value != null) { CryptographicOperations.ZeroMemory(value); } } perPeerSymmetricKey.Clear(); } private void ClearGlobalSharedSecret() { lock (cryptoStateLock) { ClearGlobalSharedSecretUnderLock(); } } private void ClearGlobalSharedSecretUnderLock() { if (globalSharedSecret != null) { CryptographicOperations.ZeroMemory(globalSharedSecret); globalSharedSecret = null; } } private void SetPeerSymmetricKeyUnderLock(ulong steamId64, HandshakeState state, byte[] sym) { ZeroIfReplaced(state.Sym, sym); if (perPeerSymmetricKey.TryGetValue(steamId64, out byte[] value)) { ZeroIfReplaced(value, sym); } state.Sym = sym; perPeerSymmetricKey[steamId64] = sym; } private static void ZeroIfReplaced(byte[]? previous, byte[] next) { if (previous != null && previous != next) { CryptographicOperations.ZeroMemory(previous); } } private void ClearOutboundState() { lock (queueLock) { normalQueue.Clear(); lowQueue.Clear(); lock (unackedLock) { unacked.Clear(); } } } private void RefreshPlayerList() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_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) try { if (Lobby == CSteamID.Nil) { players = Array.Empty(); return; } int num = getNumLobbyMembers(Lobby); players = (CSteamID[])(object)new CSteamID[num]; for (int i = 0; i < players.Length; i++) { players[i] = getLobbyMemberByIndex(Lobby, i); } NetLog.Debug("SteamNetworkingService", $"RefreshPlayerList: total members = {players.Length}"); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"RefreshPlayerList error: {arg}"); players = Array.Empty(); } } private void OnLobbyDataUpdate(LobbyDataUpdate_t param) { //IL_0009: 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_001d: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) if (!InLobby || param.m_ulSteamIDLobby != Lobby.m_SteamID) { return; } if (param.m_ulSteamIDLobby == param.m_ulSteamIDMember) { string[] array; lock (rpcLock) { array = lobbyDataKeys.ToArray(); } List list = null; string[] array2 = array; foreach (string text in array2) { string text2 = getLobbyData(Lobby, text); if (!lastLobbyData.TryGetValue(text, out string value) || value != text2) { if (list == null) { list = new List(); } list.Add(text); lastLobbyData[text] = text2; } } if (list != null) { this.LobbyDataChanged?.Invoke(list.ToArray()); } return; } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(param.m_ulSteamIDMember); string[] array3; lock (rpcLock) { array3 = playerDataKeys.ToArray(); } if (!lastPlayerData.TryGetValue(val, out Dictionary value2)) { value2 = new Dictionary(); lastPlayerData[val] = value2; } List list2 = null; string[] array4 = array3; foreach (string text3 in array4) { string text4 = getLobbyMemberData(Lobby, val, text3); if (!value2.TryGetValue(text3, out var value3) || value3 != text4) { if (list2 == null) { list2 = new List(); } list2.Add(text3); value2[text3] = text4; } } if (list2 != null) { this.PlayerDataChanged?.Invoke(val.m_SteamID, list2.ToArray()); } } public void RegisterLobbyDataKey(string key) { ValidateDataKey(key, "key"); bool flag; lock (rpcLock) { flag = lobbyDataKeys.Contains(key); if (!flag) { lobbyDataKeys.Add(key); } } if (flag) { NetLog.Warning("SteamNetworkingService", "Lobby key " + key + " already registered"); } } public void SetLobbyData(string key, object value) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) ValidateDataKey(key, "key"); if (!InLobby) { NetLog.Error("SteamNetworkingService", "Cannot set lobby data when not in lobby."); return; } bool flag; lock (rpcLock) { flag = lobbyDataKeys.Contains(key); } if (!flag) { NetLog.Warning("SteamNetworkingService", "Accessing unregistered lobby key '" + key + "'."); } string text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; try { setLobbyData(Lobby, key, text); lock (rpcLock) { if (text.Length == 0) { localEmptyLobbyDataKeys.Add(key); } else { localEmptyLobbyDataKeys.Remove(key); } } } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"SetLobbyData failed for key '{key}': {arg}"); } } public T GetLobbyData(string key) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) ValidateDataKey(key, "key"); if (!InLobby) { NetLog.Error("SteamNetworkingService", "Cannot get lobby data when not in lobby."); return default(T); } bool flag; lock (rpcLock) { flag = lobbyDataKeys.Contains(key); } if (!flag) { NetLog.Warning("SteamNetworkingService", "Accessing unregistered lobby key '" + key + "'."); } string text; try { text = getLobbyData(Lobby, key); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"GetLobbyData failed for key '{key}': {arg}"); return default(T); } if (string.IsNullOrEmpty(text)) { bool flag2; lock (rpcLock) { flag2 = localEmptyLobbyDataKeys.Contains(key); } if (flag2 && typeof(T) == typeof(string)) { return (T)(object)string.Empty; } return default(T); } try { return DataValueConverter.ConvertTo(text); } catch (Exception ex) { NetLog.Error("SteamNetworkingService", "Could not parse lobby data [" + key + "," + text + "] as " + typeof(T).Name + ": " + ex.GetType().Name + ": " + ex.Message); return default(T); } } public void RegisterPlayerDataKey(string key) { ValidateDataKey(key, "key"); bool flag; lock (rpcLock) { flag = playerDataKeys.Contains(key); if (!flag) { playerDataKeys.Add(key); } } if (flag) { NetLog.Warning("SteamNetworkingService", "Player key " + key + " already registered"); } } public void SetPlayerData(string key, object value) { //IL_0086: Unknown result type (might be due to invalid IL or missing references) ValidateDataKey(key, "key"); if (!InLobby) { NetLog.Error("SteamNetworkingService", "Cannot set player data when not in lobby."); return; } bool flag; lock (rpcLock) { flag = playerDataKeys.Contains(key); } if (!flag) { NetLog.Warning("SteamNetworkingService", "Accessing unregistered player key '" + key + "'."); } string text = Convert.ToString(value, CultureInfo.InvariantCulture) ?? string.Empty; try { setLobbyMemberData(Lobby, key, text); lock (rpcLock) { if (text.Length == 0) { localEmptyPlayerDataKeys.Add(key); } else { localEmptyPlayerDataKeys.Remove(key); } } } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"SetPlayerData failed for key '{key}': {arg}"); } } public T GetPlayerData(ulong steamId64, string key) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) ValidateDataKey(key, "key"); if (!InLobby) { NetLog.Error("SteamNetworkingService", "Cannot get player data when not in lobby."); return default(T); } bool flag; lock (rpcLock) { flag = playerDataKeys.Contains(key); } if (!flag) { NetLog.Warning("SteamNetworkingService", "Accessing unregistered player key '" + key + "'."); } CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(steamId64); string text; try { text = getLobbyMemberData(Lobby, val, key); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"GetPlayerData failed for key '{key}' and player '{steamId64}': {arg}"); return default(T); } if (string.IsNullOrEmpty(text)) { bool flag2; lock (rpcLock) { flag2 = localEmptyPlayerDataKeys.Contains(key); } if (flag2 && typeof(T) == typeof(string) && TryGetLocalSteamId(out var steamId65, "SteamNetworkingService.LocalSteamId", "GetPlayerData") && steamId65 == val) { return (T)(object)string.Empty; } return default(T); } try { return DataValueConverter.ConvertTo(text); } catch (Exception ex) { NetLog.Error("SteamNetworkingService", "Could not parse player data [" + key + "," + text + "] as " + typeof(T).Name + ": " + ex.GetType().Name + ": " + ex.Message); return default(T); } } private static void ValidateDataKey(string key, string paramName) { if (string.IsNullOrWhiteSpace(key)) { throw new ArgumentException("Data key must be a non-empty string.", paramName); } } public IDisposable RegisterNetworkObject(object instance, uint modId, int mask = 0) { if (instance == null) { throw new ArgumentNullException("instance"); } return RegisterNetworkTypeInternal(instance.GetType(), instance, modId, mask); } public IDisposable RegisterNetworkType(Type type, uint modId, int mask = 0) { if (type == null) { throw new ArgumentNullException("type"); } return RegisterNetworkTypeInternal(type, null, modId, mask); } private IDisposable RegisterNetworkTypeInternal(Type type, object? instance, uint modId, int mask) { object instance2 = instance; int num = 0; List registeredHandlers = new List(); RegistrationToken token = new RegistrationToken(); BindingFlags bindingAttr = ((instance2 == null) ? (BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)); lock (rpcLock) { if (instance2 == null) { MethodInfo methodInfo = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).FirstOrDefault((MethodInfo method) => method.IsDefined(typeof(CustomRPCAttribute), inherit: false)); if (methodInfo != null) { throw new InvalidOperationException("Cannot register instance RPC method " + type.FullName + "." + methodInfo.Name + " without an instance."); } } MethodInfo[] methods = type.GetMethods(bindingAttr); MethodInfo[] array = methods; foreach (MethodInfo method2 in array) { if (method2.IsDefined(typeof(CustomRPCAttribute), inherit: false)) { if (!rpcs.ContainsKey(modId)) { rpcs[modId] = new Dictionary>(); } if (!rpcs[modId].ContainsKey(method2.Name)) { rpcs[modId][method2.Name] = new List(); } List list = rpcs[modId][method2.Name]; if (!((instance2 == null) ? list.Any((MessageHandler existing) => existing.Mask == mask && existing.Method == method2) : list.Any((MessageHandler existing) => existing.Mask == mask && existing.Method == method2 && existing.Target == instance2))) { MessageHandler messageHandler = new MessageHandler { Target = (method2.IsStatic ? null : instance2), Method = method2, Parameters = method2.GetParameters(), TakesInfo = false, Mask = mask }; messageHandler.TakesInfo = messageHandler.Parameters.Length != 0 && RpcInfoParameterTypeHelper.IsRpcInfoParameterType(messageHandler.Parameters.Last().ParameterType); messageHandler.ParameterCountWithoutRpcInfo = (messageHandler.TakesInfo ? (messageHandler.Parameters.Length - 1) : messageHandler.Parameters.Length); messageHandler.OverloadKey = BuildOverloadKey(messageHandler); list.Add(messageHandler); registeredHandlers.Add(new HandlerRegistration { MethodName = method2.Name, Handler = messageHandler }); num++; } } } if (registeredHandlers.Count > 0) { runtimeRegistrations.Add(new RuntimeRegistration { Instance = instance2, Type = type, ModId = modId, Mask = mask, Token = token }); } } if (instance2 != null) { LogInfo($"Registered {num} RPCs for mod {modId} on {instance2} ({instance2.GetType().FullName})"); } else { LogInfo($"Registered {num} static RPCs for mod {modId} on type {type.FullName}"); } token.SetDisposeAction(delegate { DeregisterHandlers(modId, registeredHandlers); RemoveRuntimeRegistration(token); }); return token; } public void DeregisterNetworkObject(object instance, uint modId, int mask = 0) { if (instance == null) { throw new ArgumentNullException("instance"); } DeregisterNetworkObjectInternal(instance.GetType(), instance, modId, mask); } public void DeregisterNetworkType(Type type, uint modId, int mask = 0) { if (type == null) { throw new ArgumentNullException("type"); } DeregisterNetworkObjectInternal(type, null, modId, mask); } private void DeregisterNetworkObjectInternal(Type type, object? instanceOrNull, uint modId, int mask) { object instanceOrNull2 = instanceOrNull; Type type2 = type; lock (rpcLock) { if (!rpcs.TryGetValue(modId, out Dictionary> value)) { NetLog.Warning("SteamNetworkingService", $"No RPCs for mod {modId}"); return; } int num = 0; string[] array = value.Keys.ToArray(); string[] array2 = array; foreach (string key in array2) { if (!value.TryGetValue(key, out var value2)) { continue; } for (int num2 = value2.Count - 1; num2 >= 0; num2--) { MessageHandler messageHandler = value2[num2]; if (instanceOrNull2 != null && messageHandler.Target != null && messageHandler.Target == instanceOrNull2 && messageHandler.Mask == mask) { value2.RemoveAt(num2); num++; } else if (instanceOrNull2 == null && messageHandler.Target == null && messageHandler.Method.DeclaringType == type2 && messageHandler.Mask == mask) { value2.RemoveAt(num2); num++; } } if (value2.Count == 0) { value.Remove(key); } } if (value.Count == 0) { rpcs.Remove(modId); } runtimeRegistrations.RemoveAll((RuntimeRegistration registration) => registration.ModId == modId && registration.Mask == mask && ((instanceOrNull2 == null) ? (registration.Instance == null && registration.Type == type2) : (registration.Instance == instanceOrNull2))); LogInfo($"Deregistered {num} RPCs for mod {modId} (type/instance {type2.FullName})"); } } private void DeregisterHandlers(uint modId, List handlersToRemove) { lock (rpcLock) { if (!rpcs.TryGetValue(modId, out Dictionary> value) || handlersToRemove.Count == 0) { return; } foreach (HandlerRegistration item in handlersToRemove) { if (value.TryGetValue(item.MethodName, out var value2)) { value2.Remove(item.Handler); if (value2.Count == 0) { value.Remove(item.MethodName); } } } if (value.Count == 0) { rpcs.Remove(modId); } } } private void RemoveRuntimeRegistration(RegistrationToken token) { RegistrationToken token2 = token; lock (rpcLock) { runtimeRegistrations.RemoveAll((RuntimeRegistration registration) => registration.Token == token2); } } public void RPC(uint modId, string methodName, ReliableType reliable, params object[] parameters) { //IL_004b: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_009f: 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) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (!InLobby) { NetLog.Error("SteamNetworkingService", "RPC called while not in lobby"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, null); if (message == null || !TryGetLocalSteamId(out var steamId, "SteamNetworkingService.LocalSteamId", "RPC")) { return; } CSteamID[] array = players; foreach (CSteamID val in array) { if (!(val == CSteamID.Nil) && !(val == steamId)) { EnqueueOrSend(BuildFramedBytesWithMeta(message, modId, reliable), val, reliable, DeterminePriority(methodName)); } } InvokeLocalMessage(new Message(message.ToArray(), messageSizePolicy), steamId); } public void RPC(uint modId, string methodName, ReliableType reliable, Type[] parameterTypes, params object?[] parameters) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) if (!InLobby) { NetLog.Error("SteamNetworkingService", "RPC called while not in lobby"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, parameterTypes); if (message == null || !TryGetLocalSteamId(out var steamId, "SteamNetworkingService.LocalSteamId", "RPC")) { return; } CSteamID[] array = players; foreach (CSteamID val in array) { if (!(val == CSteamID.Nil) && !(val == steamId)) { EnqueueOrSend(BuildFramedBytesWithMeta(message, modId, reliable), val, reliable, DeterminePriority(methodName)); } } InvokeLocalMessage(new Message(message.ToArray(), messageSizePolicy), steamId); } public void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, params object[] parameters) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) RPCTarget(modId, methodName, new CSteamID(targetSteamId64), reliable, parameters); } public void RPCTarget(uint modId, string methodName, CSteamID target, ReliableType reliable, params object[] parameters) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_005a: 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_0075: Unknown result type (might be due to invalid IL or missing references) if (!InLobby) { NetLog.Error("SteamNetworkingService", "Cannot RPC target when not in lobby"); return; } if (target == CSteamID.Nil) { NetLog.Error("SteamNetworkingService", "Cannot RPC target invalid SteamID 0"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, null); if (message != null) { if (TryGetLocalSteamId(out var steamId, "SteamNetworkingService.LocalSteamId", "RPCTarget") && target == steamId) { InvokeLocalMessage(new Message(message.ToArray(), messageSizePolicy), steamId); return; } byte[] framed = BuildFramedBytesWithMeta(message, modId, reliable); EnqueueOrSend(framed, target, reliable, DeterminePriority(methodName)); } } public void RPCTarget(uint modId, string methodName, ulong targetSteamId64, ReliableType reliable, Type[] parameterTypes, params object?[] parameters) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) RPCTarget(modId, methodName, new CSteamID(targetSteamId64), reliable, parameterTypes, parameters); } public void RPCTarget(uint modId, string methodName, CSteamID target, ReliableType reliable, Type[] parameterTypes, params object?[] parameters) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) if (!InLobby) { NetLog.Error("SteamNetworkingService", "Cannot RPC target when not in lobby"); return; } if (target == CSteamID.Nil) { NetLog.Error("SteamNetworkingService", "Cannot RPC target invalid SteamID 0"); return; } Message message = BuildMessage(modId, methodName, 0, parameters, parameterTypes); if (message != null) { if (TryGetLocalSteamId(out var steamId, "SteamNetworkingService.LocalSteamId", "RPCTarget") && target == steamId) { InvokeLocalMessage(new Message(message.ToArray(), messageSizePolicy), steamId); return; } byte[] framed = BuildFramedBytesWithMeta(message, modId, reliable); EnqueueOrSend(framed, target, reliable, DeterminePriority(methodName)); } } public void RPCToHost(uint modId, string methodName, ReliableType reliable, params object[] parameters) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: 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) CSteamID owner; if (!InLobby) { NetLog.Error("SteamNetworkingService", "Not in lobby"); } else if (!TryGetLobbyOwner(Lobby, out owner, "SteamNetworkingService.LobbyOwner", "RPCToHost")) { NetLog.Error("SteamNetworkingService", "No host set"); } else if (owner == CSteamID.Nil) { NetLog.Error("SteamNetworkingService", "No host set"); } else { RPCTarget(modId, methodName, owner, reliable, parameters); } } private Priority DeterminePriority(string methodName) { if (methodName.IndexOf("admin", StringComparison.OrdinalIgnoreCase) >= 0 || methodName.IndexOf("control", StringComparison.OrdinalIgnoreCase) >= 0 || methodName.IndexOf("critical", StringComparison.OrdinalIgnoreCase) >= 0 || methodName.IndexOf("sync", StringComparison.OrdinalIgnoreCase) >= 0) { return Priority.High; } return Priority.Normal; } private void EnqueueOrSend(byte[] framed, CSteamID target, ReliableType reliable, Priority p) { //IL_0001: 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_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) SlidingWindowRateLimiter orCreateRateLimiter = GetOrCreateRateLimiter(target.m_SteamID); if (!orCreateRateLimiter.Allowed()) { return; } if (p == Priority.High) { SendWithPossibleAck(framed, target, reliable); return; } lock (queueLock) { Queue queue = ((p == Priority.Normal) ? normalQueue : lowQueue); int num = ((p == Priority.Normal) ? 256 : 128); if (queue.Count >= num) { queue.Dequeue(); string text = ((p == Priority.Normal) ? "normalQueue" : "lowQueue"); string key = "EnqueueOrSend.Overflow." + text; if (NetLog.TryEnterCooldown(key, 2.0)) { NetLog.Warning("SteamNetworkingService", $"Outbound {text} overflow (max={num}), dropped oldest message."); } } queue.Enqueue(new QueuedSend { Framed = framed, Target = target, Reliable = reliable, Enqueued = DateTime.UtcNow }); } } private void FlushQueues(int maxPerFrame = 8) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (!InLobby) { return; } int num = 0; while (num < maxPerFrame) { QueuedSend queuedSend = null; lock (queueLock) { if (normalQueue.Count > 0) { queuedSend = normalQueue.Dequeue(); } else { if (lowQueue.Count <= 0) { break; } queuedSend = lowQueue.Dequeue(); } } if (queuedSend != null) { SendWithPossibleAck(queuedSend.Framed, queuedSend.Target, queuedSend.Reliable); num++; } } } private static void WriteU16LE(Stream stream, ushort value) { Span span = stackalloc byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(span, value); stream.Write(span); } private static void WriteI32LE(Stream stream, int value) { Span span = stackalloc byte[4]; BinaryPrimitives.WriteInt32LittleEndian(span, value); stream.Write(span); } private static void WriteU64LE(Stream stream, ulong value) { Span span = stackalloc byte[8]; BinaryPrimitives.WriteUInt64LittleEndian(span, value); stream.Write(span); } private byte[] BuildFramedBytesWithMeta(Message msg, uint modId, ReliableType reliable) { byte[] array = msg.ToArray(); bool flag = array.Length > 1024; if (flag) { array = msg.CompressPayload(array); } byte b = 0; if (flag) { b = (byte)(b | 2u); } bool flag2; byte[] array2; Func func; lock (cryptoStateLock) { flag2 = globalHmac != null; array2 = ((globalSharedSecret != null) ? ((byte[])globalSharedSecret.Clone()) : null); func = (modSigners.TryGetValue(modId, out Func value) ? value : null); } try { if (flag2) { b = (byte)(b | 4u); } if (func != null) { b = (byte)(b | 8u); } if (reliable == ReliableType.Reliable) { b = (byte)(b | 0x10u); } ulong value3; lock (outgoingSequencePerMod) { if (!outgoingSequencePerMod.TryGetValue(modId, out var value2)) { value2 = 0uL; } value3 = ++value2; outgoingSequencePerMod[modId] = value2; } ulong value4 = NextMessageId(); using MemoryStream memoryStream = new MemoryStream(); memoryStream.WriteByte(b); WriteU64LE(memoryStream, value4); WriteU64LE(memoryStream, value3); WriteI32LE(memoryStream, 1); WriteI32LE(memoryStream, 0); memoryStream.Write(array, 0, array.Length); byte[] array3 = memoryStream.ToArray(); if (func != null) { byte[] array4 = func(array3); using MemoryStream memoryStream2 = new MemoryStream(); memoryStream2.Write(array3, 0, array3.Length); ushort value5 = (ushort)array4.Length; WriteU16LE(memoryStream2, value5); memoryStream2.Write(array4, 0, array4.Length); array3 = memoryStream2.ToArray(); } if (flag2 && array2 != null) { byte[] array5 = HmacSha256RawStatic(array2, array3); using MemoryStream memoryStream3 = new MemoryStream(); memoryStream3.Write(array3, 0, array3.Length); memoryStream3.Write(array5, 0, array5.Length); array3 = memoryStream3.ToArray(); } return array3; } finally { if (array2 != null) { CryptographicOperations.ZeroMemory(array2); } } } private void SendWithPossibleAck(byte[] framed, CSteamID target, ReliableType reliable) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) byte[] array; lock (cryptoStateLock) { array = (perPeerSymmetricKey.TryGetValue(target.m_SteamID, out byte[] value) ? ((byte[])value.Clone()) : null); } if (array != null) { try { framed[0] = (byte)(framed[0] | 4u); framed = AppendFrameMac(framed, array); } finally { CryptographicOperations.ZeroMemory(array); } } if ((framed[0] & 0x10u) != 0) { ulong item = BinaryPrimitives.ReadUInt64LittleEndian(framed.AsSpan(1, 8)); (ulong, ulong) key = (target.m_SteamID, item); lock (unackedLock) { unacked[key] = new UnackedMessage { Framed = framed, Target = target, Reliable = reliable, LastSent = DateTime.UtcNow, Attempts = 1 }; } } SendBytes(framed, target, reliable); } private void SendBytes(byte[] data, CSteamID target, ReliableType reliable) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (data.Length > messageSizePolicy.MaxSize) { NetLog.Error("SteamNetworkingService", $"Send length {data.Length} exceeds configured max {messageSizePolicy.MaxSize}"); return; } if (TryGetLocalSteamId(out var steamId, "SteamNetworkingService.LocalSteamId", "SendBytes") && target == steamId) { ProcessIncomingFrame(data, steamId); return; } SteamNetworkingIdentity val = default(SteamNetworkingIdentity); ((SteamNetworkingIdentity)(ref val)).SetSteamID(target); GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned); try { IntPtr intPtr = gCHandle.AddrOfPinnedObject(); int num = 0x20 | ResolveSendModeFlag(reliable); EResult val2 = SteamNetworkingMessages.SendMessageToUser(ref val, intPtr, (uint)data.Length, num, 120); if ((int)val2 != 1) { NetLog.Error("SteamNetworkingService", $"SendMessageToUser failed: {val2} to {target}"); } } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"SendBytes exception: {arg}"); } finally { if (gCHandle.IsAllocated) { gCHandle.Free(); } } } private bool TryGetLocalSteamId(out CSteamID steamId, string cooldownKey, string context) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_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) steamId = CSteamID.Nil; try { steamId = getLocalSteamId(); return true; } catch (Exception ex) { NetLog.DebugThrottled("SteamNetworkingService", cooldownKey, 2.0, context + " local SteamID lookup failed: " + ex.GetType().Name + ": " + ex.Message); return false; } } private bool TryGetLobbyOwner(CSteamID lobby, out CSteamID owner, string cooldownKey, string context) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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) owner = CSteamID.Nil; try { owner = getLobbyOwner(lobby); return true; } catch (Exception ex) { NetLog.DebugThrottled("SteamNetworkingService", cooldownKey, 2.0, context + " lobby owner lookup failed: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static int ResolveSendModeFlag(ReliableType reliable) { switch (reliable) { case ReliableType.Unreliable: return 0; case ReliableType.Reliable: return 8; case ReliableType.UnreliableNoDelay: return 5; default: NetLog.Warning("SteamNetworkingService", string.Format("Unknown {0} value '{1}' ({2}).", "ReliableType", reliable, (int)reliable)); NetLog.Warning("SteamNetworkingService", $"Falling back to {ReliableType.Reliable} send mode."); return 8; } } public void PollReceive() { FlushQueues(); RetransmitUnacked(); ReceiveMessages(); } private void RetransmitUnacked() { //IL_015b: Unknown result type (might be due to invalid IL or missing references) (ulong, ulong)[] array = null; UnackedMessage[] array2 = null; int num = 0; lock (unackedLock) { DateTime utcNow = DateTime.UtcNow; if (unacked.Count == 0) { return; } array = ArrayPool<(ulong, ulong)>.Shared.Rent(unacked.Count); int num2 = 0; foreach (var key2 in unacked.Keys) { array[num2++] = key2; } array2 = ArrayPool.Shared.Rent(num2); for (int i = 0; i < num2; i++) { (ulong, ulong) key = array[i]; if (unacked.TryGetValue(key, out UnackedMessage value) && utcNow - value.LastSent > ackTimeout) { if (value.Attempts >= maxRetransmitAttempts) { unacked.Remove(key); continue; } value.Attempts++; value.LastSent = utcNow; unacked[key] = value; array2[num++] = value; } } } try { for (int j = 0; j < num; j++) { UnackedMessage unackedMessage = array2[j]; try { SendBytes(unackedMessage.Framed, unackedMessage.Target, unackedMessage.Reliable); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"RetransmitUnacked: retransmit SendBytes exception: {arg}"); } } } finally { if (array != null) { ArrayPool<(ulong, ulong)>.Shared.Return(array); } if (array2 != null) { Array.Clear(array2, 0, num); ArrayPool.Shared.Return(array2); } } } private void ReceiveMessages() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) try { int num = receiveMessagesOnChannel(120, inMessages, 500); if (num <= 0) { return; } for (int i = 0; i < num; i++) { IntPtr intPtr = inMessages[i]; if (intPtr == IntPtr.Zero) { continue; } try { SteamNetworkingMessage_t val = Marshal.PtrToStructure(intPtr); int cbSize = val.m_cbSize; if (cbSize <= 0 || cbSize > messageSizePolicy.MaxSize) { continue; } CSteamID steamID = ((SteamNetworkingIdentity)(ref val.m_identityPeer)).GetSteamID(); if (steamID == CSteamID.Nil) { continue; } byte[] array = null; try { array = ArrayPool.Shared.Rent(cbSize); Marshal.Copy(val.m_pData, array, 0, cbSize); ProcessIncomingFrameCore(array.AsSpan(0, cbSize), steamID); } catch (Exception ex) { LogReceiveProcessIncomingFrameException(ex); } finally { if (array != null) { ArrayPool.Shared.Return(array); } } } finally { SteamNetworkingMessage_t.Release(intPtr); } } } catch (Exception ex2) { LogReceiveMessagesOuterException(ex2); } } private static void LogReceiveProcessIncomingFrameException(Exception ex) { NetLog.ErrorThrottled("SteamNetworkingService", "SteamNetworkingService.ReceiveMessages.ProcessIncomingFrameException", 2.0, $"ProcessIncomingFrame exception: {ex}"); } private static void LogReceiveMessagesOuterException(Exception ex) { NetLog.ErrorThrottled("SteamNetworkingService", "SteamNetworkingService.ReceiveMessages.OuterException", 2.0, $"ReceiveMessages outer exception: {ex}"); } private void ProcessIncomingFrame(byte[] frame, CSteamID sender) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (frame == null) { throw new ArgumentNullException("frame"); } ProcessIncomingFrameCore(frame, sender); } private void ProcessIncomingFrameCore(ReadOnlySpan frame, CSteamID sender) { //IL_05cd: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_0559: Unknown result type (might be due to invalid IL or missing references) //IL_0528: Unknown result type (might be due to invalid IL or missing references) //IL_054c: Unknown result type (might be due to invalid IL or missing references) //IL_0593: Unknown result type (might be due to invalid IL or missing references) //IL_05b9: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) if (frame.Length < 25) { return; } int num = frame[0]; bool flag = (num & 2) != 0; bool flag2 = (num & 4) != 0; bool flag3 = (num & 8) != 0; bool flag4 = (num & 0x10) != 0; try { if (flag2) { byte[] frameWithMacs = frame.ToArray(); if (!TryVerifyAndStripFrameMacs(frameWithMacs, sender.m_SteamID, out byte[] verifiedFrame)) { return; } frame = verifiedFrame; } int num2 = 1; if (frame.Length < num2 + 8 + 8 + 4 + 4) { throw new EndOfStreamException("Frame header is truncated."); } ulong num3 = BinaryPrimitives.ReadUInt64LittleEndian(frame.Slice(num2, 8)); num2 += 8; ulong seq = BinaryPrimitives.ReadUInt64LittleEndian(frame.Slice(num2, 8)); num2 += 8; int num4 = BinaryPrimitives.ReadInt32LittleEndian(frame.Slice(num2, 4)); num2 += 4; int num5 = BinaryPrimitives.ReadInt32LittleEndian(frame.Slice(num2, 4)); num2 += 4; if (num4 < 1 || num5 < 0 || num5 >= num4) { NetLog.Warning("SteamNetworkingService", $"ProcessIncomingFrame: malformed fragment header total={num4} index={num5} from {sender}"); return; } byte[] array = ((flag3 && num4 == 1) ? frame.Slice(0, num2).ToArray() : Array.Empty()); int num6 = frame.Length - num2; if (num6 <= 0) { return; } byte[] array2 = frame.Slice(num2, num6).ToArray(); byte[] array3; if (num4 > 1) { (ulong, ulong) key = (sender.m_SteamID, num3); DateTime utcNow = DateTime.UtcNow; FragmentBuffer fragmentBuffer; lock (fragmentLock) { if (!fragmentBuffers.TryGetValue(key, out FragmentBuffer value)) { value = new FragmentBuffer { Total = num4, FirstSeen = utcNow }; fragmentBuffers[key] = value; } value.Fragments[num5] = array2; if (utcNow >= nextFragmentCleanupAt) { nextFragmentCleanupAt = utcNow + FragmentCleanupInterval; staleFragmentKeys.Clear(); foreach (KeyValuePair<(ulong, ulong), FragmentBuffer> fragmentBuffer2 in fragmentBuffers) { if (!(utcNow - fragmentBuffer2.Value.FirstSeen <= FragmentTimeout)) { staleFragmentKeys.Add(fragmentBuffer2.Key); } } for (int i = 0; i < staleFragmentKeys.Count; i++) { fragmentBuffers.Remove(staleFragmentKeys[i]); } staleFragmentKeys.Clear(); } if (value.Fragments.Count != value.Total) { return; } fragmentBuffer = value; fragmentBuffers.Remove(key); } using MemoryStream memoryStream = new MemoryStream(); for (int j = 0; j < fragmentBuffer.Total; j++) { if (!fragmentBuffer.Fragments.TryGetValue(j, out byte[] value2)) { return; } memoryStream.Write(value2, 0, value2.Length); } array3 = memoryStream.ToArray(); } else { array3 = array2; } byte[] array4 = array3; byte[] array5 = array4; if (flag3) { if (array5.Length < 3) { return; } RSAParameters[] array6; lock (cryptoStateLock) { array6 = modPublicKeysSnapshot; if (array6.Length == 0) { return; } } bool flag5 = false; for (int k = 0; k < array6.Length; k++) { RSAParameters parameters = array6[k]; byte[]? modulus = parameters.Modulus; int num7 = ((modulus != null) ? modulus.Length : 0); if (num7 <= 0 || array5.Length < num7 + 2) { continue; } int num8 = array5.Length - num7 - 2; if (num8 < 0) { continue; } ushort num9 = BinaryPrimitives.ReadUInt16LittleEndian(array5.AsSpan(num8, 2)); if (num9 != num7) { continue; } byte[] array7 = new byte[num7]; Array.Copy(array5, num8 + 2, array7, 0, num7); byte[] array8 = new byte[num8]; Array.Copy(array5, 0, array8, 0, num8); byte[] data = ((array.Length == 0) ? array8 : CombineArrays(array, array8)); try { using RSA rSA = RSA.Create(); rSA.ImportParameters(parameters); if (!rSA.VerifyData(data, array7, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) { continue; } array5 = array8; flag5 = true; break; } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"ProcessIncomingFrame: Signature verification error: {arg}"); } } if (!flag5) { return; } } if (flag) { try { using Message message = new Message(0u, string.Empty, 0, messageSizePolicy); array5 = message.DecompressPayloadForCurrentPolicy(array5); } catch (Exception arg2) { NetLog.Error("SteamNetworkingService", $"ProcessIncomingFrame: Decompression failed: {arg2}"); return; } } Message message2 = new Message(array5, messageSizePolicy); if (message2.ModID == 0) { HandleInternalMessage(message2, sender, num3, seq, flag4); if (flag4 && message2.MethodName != "NETWORK_INTERNAL_ACK") { SendAckToSender(sender, num3); } return; } ulong steamID = sender.m_SteamID; SlidingWindowRateLimiter orCreateRateLimiter = GetOrCreateRateLimiter(steamID); if (orCreateRateLimiter.IncomingAllowed() && CheckAndUpdateSequence(steamID, message2.ModID, seq)) { if (flag4) { SendAckToSender(sender, num3); } if (IncomingValidator == null || IncomingValidator(message2, steamID)) { DispatchIncoming(message2, sender); } } } catch (EndOfStreamException ex) { NetLog.Warning("SteamNetworkingService", $"ProcessIncomingFrame: malformed frame from {sender}, dropping. {ex.Message}"); } catch (Exception arg3) { NetLog.Error("SteamNetworkingService", $"ProcessIncomingFrame top-level exception: {arg3}"); } } private byte[] AppendFrameMac(byte[] frameWithoutTrailingMac, byte[] key) { using HMACSHA256 hMACSHA = new HMACSHA256(key); byte[] array = hMACSHA.ComputeHash(frameWithoutTrailingMac); using MemoryStream memoryStream = new MemoryStream(frameWithoutTrailingMac.Length + array.Length); memoryStream.Write(frameWithoutTrailingMac, 0, frameWithoutTrailingMac.Length); memoryStream.Write(array, 0, array.Length); return memoryStream.ToArray(); } private static byte[] CombineArrays(byte[] first, byte[] second) { byte[] array = new byte[first.Length + second.Length]; Buffer.BlockCopy(first, 0, array, 0, first.Length); Buffer.BlockCopy(second, 0, array, first.Length, second.Length); return array; } private bool VerifyAndStripSingleFrameMac(byte[] framedWithMac, byte[] key, out byte[] strippedFrame) { strippedFrame = Array.Empty(); if (framedWithMac.Length < 57) { return false; } int num = framedWithMac.Length - 32; byte[] array = new byte[num]; Buffer.BlockCopy(framedWithMac, 0, array, 0, num); using HMACSHA256 hMACSHA = new HMACSHA256(key); byte[] array2 = hMACSHA.ComputeHash(array); byte[] array3 = new byte[32]; Buffer.BlockCopy(framedWithMac, num, array3, 0, array3.Length); if (!CryptographicOperations.FixedTimeEquals(array2, array3)) { return false; } strippedFrame = array; return true; } private bool TryVerifyAndStripFrameMacs(byte[] frameWithMacs, ulong senderSteamId, out byte[] verifiedFrame) { verifiedFrame = Array.Empty(); byte[] array; byte[] array2; lock (cryptoStateLock) { array = (perPeerSymmetricKey.TryGetValue(senderSteamId, out byte[] value) ? ((byte[])value.Clone()) : null); array2 = ((globalSharedSecret != null) ? ((byte[])globalSharedSecret.Clone()) : null); } try { bool flag = array != null; bool flag2 = array2 != null; if (!flag && !flag2) { return false; } byte[] strippedFrame = frameWithMacs; if (flag && !VerifyAndStripSingleFrameMac(strippedFrame, array, out strippedFrame)) { return false; } if (flag2 && !VerifyAndStripSingleFrameMac(strippedFrame, array2, out strippedFrame)) { return false; } verifiedFrame = strippedFrame; return true; } finally { if (array != null) { CryptographicOperations.ZeroMemory(array); } if (array2 != null) { CryptographicOperations.ZeroMemory(array2); } } } private static void ReadExact(Stream s, byte[] buffer, int count) { int num; for (int i = 0; i < count; i += num) { num = s.Read(buffer, i, count - i); if (num <= 0) { throw new EndOfStreamException($"Expected {count} bytes but only received {i}."); } } } private static ulong ReadU64(Stream s) { byte[] array = new byte[8]; ReadExact(s, array, array.Length); return BinaryPrimitives.ReadUInt64LittleEndian(array); } private static int ReadI32(Stream s) { byte[] array = new byte[4]; ReadExact(s, array, array.Length); return BinaryPrimitives.ReadInt32LittleEndian(array); } private void SendAckToSender(CSteamID sender, ulong msgId) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) Message message = new Message(0u, "NETWORK_INTERNAL_ACK", 0, messageSizePolicy); message.WriteULong(msgId); byte[] array = BuildFramedBytesWithMeta(message, 0u, ReliableType.Reliable); if ((array[0] & 0x10u) != 0) { array[0] = (byte)(array[0] & 0xFFFFFFEFu); } SendBytes(array, sender, ReliableType.Reliable); } private void HandleInternalMessage(Message message, CSteamID sender, ulong msgId, ulong seq, bool requiresAck) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) try { switch (message.MethodName) { case "NETWORK_INTERNAL_HANDSHAKE_PUBKEY": { string peerPubKeySerialized = message.ReadString(); string peerNonce = message.ReadString(); StartHandshakeReply(sender, peerPubKeySerialized, peerNonce); break; } case "NETWORK_INTERNAL_HANDSHAKE_SECRET": { byte[] encSecret = (byte[])message.ReadObject(typeof(byte[])); string initiatorNonce2 = message.ReadString(); CompleteHandshakeReceiver(sender, encSecret, initiatorNonce2); break; } case "NETWORK_INTERNAL_HANDSHAKE_CONFIRM": { string initiatorNonce = message.ReadString(); byte[] confirmHmac = (byte[])message.ReadObject(typeof(byte[])); CompleteHandshakeInitiator(sender, initiatorNonce, confirmHmac); break; } case "NETWORK_INTERNAL_ACK": { ulong item = message.ReadULong(); (ulong, ulong) key = (sender.m_SteamID, item); lock (unackedLock) { if (unacked.ContainsKey(key)) { unacked.Remove(key); } break; } } default: NetLog.Warning("SteamNetworkingService", "Unknown internal method " + message.MethodName); break; } } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"HandleInternalMessage error: {arg}"); } } private bool CheckAndUpdateSequence(ulong sender64, uint modId, ulong seq) { lock (lastSeenSequence) { if (!lastSeenSequence.TryGetValue(sender64, out Dictionary value)) { Dictionary dictionary2 = (lastSeenSequence[sender64] = new Dictionary()); value = dictionary2; } if (!value.TryGetValue(modId, out var value2)) { value2 = 0uL; } if (seq <= value2) { return false; } value[modId] = seq; return true; } } private void DispatchIncoming(Message message, CSteamID sender) { //IL_0012: 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) Message message2 = message; MessageHandler[] handlersSnapshot; lock (rpcLock) { if (!rpcs.TryGetValue(message2.ModID, out Dictionary> value)) { NetLog.Warning("SteamNetworkingService", $"Dropping message for unknown mod {message2.ModID}"); return; } if (!value.TryGetValue(message2.MethodName, out var value2)) { NetLog.Warning("SteamNetworkingService", $"Dropping message for method {message2.MethodName} not registered for {message2.ModID}"); return; } handlersSnapshot = value2.ToArray(); } MessageHandler chosenHandler = null; object[] chosenParams = null; MessageHandler fallbackHandler = null; object[] fallbackParams = null; bool flag = !string.IsNullOrEmpty(message2.OverloadKey); bool flag2 = false; if (flag) { foreach (MessageHandler messageHandler in handlersSnapshot) { if (messageHandler.Mask == message2.Mask && !(messageHandler.OverloadKey != message2.OverloadKey)) { flag2 = true; break; } } } if (flag2) { TryDispatch(true); if (chosenHandler == null) { TryDispatch(false); } } else { TryDispatch(null); } if (chosenHandler == null) { chosenHandler = fallbackHandler; chosenParams = fallbackParams; } if (chosenHandler == null || chosenParams == null) { NetLog.Warning("SteamNetworkingService", $"No handler matched for {message2.ModID}:{message2.MethodName} mask={message2.Mask}"); return; } try { chosenHandler.Method.Invoke(chosenHandler.Target, chosenParams); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"Invoke RPC error: {arg}"); } void TryDispatch(bool? preferOverloadKeyMatch) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) foreach (MessageHandler messageHandler2 in handlersSnapshot) { if (messageHandler2.Mask == message2.Mask) { if (preferOverloadKeyMatch.HasValue) { bool flag3 = messageHandler2.OverloadKey == message2.OverloadKey; if (preferOverloadKeyMatch.Value != flag3) { continue; } } if (TryDeserializeForHandler(message2, messageHandler2, sender, isLocalLoopback: false, out object[] callParams, out int unread)) { if (unread == 0) { chosenHandler = messageHandler2; chosenParams = callParams; break; } if (fallbackHandler == null) { fallbackHandler = messageHandler2; } if (fallbackParams == null) { fallbackParams = callParams; } } } } } } private object CreateRpcInfoInstance(Type infoType, CSteamID sender, bool isLocalLoopback) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) try { ConstructorInfo constructor = infoType.GetConstructor(new Type[3] { typeof(ulong), typeof(string), typeof(bool) }); if (constructor != null) { return constructor.Invoke(new object[3] { sender.m_SteamID, ((object)(CSteamID)(ref sender)).ToString(), isLocalLoopback }); } ConstructorInfo constructor2 = infoType.GetConstructor(new Type[1] { typeof(CSteamID) }); if (constructor2 != null) { return constructor2.Invoke(new object[1] { sender }); } ConstructorInfo constructor3 = infoType.GetConstructor(new Type[1] { typeof(ulong) }); if (constructor3 != null) { return constructor3.Invoke(new object[1] { sender.m_SteamID }); } ConstructorInfo constructor4 = infoType.GetConstructor(Type.EmptyTypes); if (constructor4 != null) { object obj = constructor4.Invoke(null); AssignRpcIdentityMembers(obj, infoType, sender, isLocalLoopback); return obj; } } catch (Exception ex) { NetLog.DebugThrottled("SteamNetworkingService", "CreateRpcInfoInstance", 2.0, "CreateRpcInfoInstance failed for " + (infoType?.FullName ?? "") + ": " + ex.GetType().Name + ": " + ex.Message); } return null; } private static void AssignRpcIdentityMembers(object instance, Type infoType, CSteamID sender, bool isLocalLoopback) { //IL_0000: 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_002b: 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_0049: Unknown result type (might be due to invalid IL or missing references) ulong steamID = sender.m_SteamID; string steamIdString = ((object)(CSteamID)(ref sender)).ToString(); AssignRpcIdentityMember(instance, infoType, "SenderSteamID", sender, steamID, steamIdString); AssignRpcIdentityMember(instance, infoType, "Sender", sender, steamID, steamIdString); AssignRpcIdentityMember(instance, infoType, "SteamId64", sender, steamID, steamIdString); AssignRpcIdentityMember(instance, infoType, "SteamIdString", sender, steamID, steamIdString); AssignRpcLoopbackMember(instance, infoType, isLocalLoopback); } private static void AssignRpcIdentityMember(object instance, Type infoType, string memberName, CSteamID sender, ulong steamId64, string steamIdString) { //IL_0041: 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) object instance2 = instance; FieldInfo field = infoType.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { TryAssignMemberValue(field.FieldType, delegate(object value) { field.SetValue(instance2, value); }, sender, steamId64, steamIdString); return; } PropertyInfo property = infoType.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(property == null) && property.CanWrite) { TryAssignMemberValue(property.PropertyType, delegate(object value) { property.SetValue(instance2, value); }, sender, steamId64, steamIdString); } } private static void AssignRpcLoopbackMember(object instance, Type infoType, bool isLocalLoopback) { object instance2 = instance; FieldInfo field = infoType.GetField("IsLocalLoopback", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { TryAssignLoopbackMemberValue(field.FieldType, delegate(object value) { field.SetValue(instance2, value); }, isLocalLoopback); return; } PropertyInfo property = infoType.GetProperty("IsLocalLoopback", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!(property == null) && property.CanWrite) { TryAssignLoopbackMemberValue(property.PropertyType, delegate(object value) { property.SetValue(instance2, value); }, isLocalLoopback); } } private static void TryAssignLoopbackMemberValue(Type memberType, Action assign, bool isLocalLoopback) { if (memberType == typeof(bool)) { assign(isLocalLoopback); } else if (memberType == typeof(bool?)) { assign(isLocalLoopback); } } private static void TryAssignMemberValue(Type memberType, Action assign, CSteamID sender, ulong steamId64, string steamIdString) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) Type type = Nullable.GetUnderlyingType(memberType) ?? memberType; if (type == typeof(CSteamID)) { assign(sender); } else if (type == typeof(string)) { assign(steamIdString); } else if (IsCompatibleIntegralType(type)) { assign(ConvertIntegral(steamId64, type)); } } private static bool IsCompatibleIntegralType(Type t) { if (!(t == typeof(ulong)) && !(t == typeof(long)) && !(t == typeof(uint)) && !(t == typeof(int)) && !(t == typeof(ushort)) && !(t == typeof(short)) && !(t == typeof(byte))) { return t == typeof(sbyte); } return true; } private static object ConvertIntegral(ulong value, Type targetType) { if (targetType == typeof(ulong)) { return value; } checked { if (targetType == typeof(long)) { return (long)value; } if (targetType == typeof(uint)) { return (uint)value; } if (targetType == typeof(int)) { return (int)value; } if (targetType == typeof(ushort)) { return (ushort)value; } if (targetType == typeof(short)) { return (short)value; } if (targetType == typeof(byte)) { return (byte)value; } if (targetType == typeof(sbyte)) { return (sbyte)value; } throw new InvalidCastException($"Unsupported integral conversion to {targetType}."); } } public void StartHandshake(CSteamID target) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) if (LocalRsa == null) { return; } string v = SerializeRsaPublicKey(LocalRsa); using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); byte[] array = new byte[16]; randomNumberGenerator.GetBytes(array); string text = Convert.ToBase64String(array); lock (cryptoStateLock) { handshakeStates[target.m_SteamID] = new HandshakeState { PeerPub = null, LocalNonce = text, Completed = false }; } Message message = new Message(0u, "NETWORK_INTERNAL_HANDSHAKE_PUBKEY", 0, messageSizePolicy); message.WriteString(v); message.WriteString(text); byte[] data = BuildFramedBytesWithMeta(message, 0u, ReliableType.Reliable); SendBytes(data, target, ReliableType.Reliable); } private void StartHandshakeReply(CSteamID sender, string peerPubKeySerialized, string peerNonce) { //IL_0196: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) if (LocalRsa == null) { return; } using RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create(); byte[] array = new byte[16]; randomNumberGenerator.GetBytes(array); string localNonce = Convert.ToBase64String(array); string text = null; string text2 = null; string text3 = null; lock (cryptoStateLock) { handshakeStates.TryGetValue(sender.m_SteamID, out HandshakeState value); if (value == null) { value = new HandshakeState(); } value.PeerPub = peerPubKeySerialized; if (string.IsNullOrEmpty(value.LocalNonce)) { value.LocalNonce = localNonce; handshakeStates[sender.m_SteamID] = value; text = value.LocalNonce; } else if (!value.Completed && !string.IsNullOrEmpty(value.PeerPub)) { text2 = value.PeerPub; text3 = value.LocalNonce; } } if (!string.IsNullOrEmpty(text)) { string v = SerializeRsaPublicKey(LocalRsa); Message message = new Message(0u, "NETWORK_INTERNAL_HANDSHAKE_PUBKEY", 0, messageSizePolicy); message.WriteString(v); message.WriteString(text); byte[] data = BuildFramedBytesWithMeta(message, 0u, ReliableType.Reliable); SendBytes(data, sender, ReliableType.Reliable); } else { if (string.IsNullOrEmpty(text2) || string.IsNullOrEmpty(text3)) { return; } byte[] array2 = new byte[32]; randomNumberGenerator.GetBytes(array2); byte[] v2; try { using RSA rSA = RSA.Create(); RSAParameters parameters = DeserializeRsaPublicKey(text2); rSA.ImportParameters(parameters); v2 = rSA.Encrypt(array2, RSAEncryptionPadding.Pkcs1); } catch (Exception ex) { NetLog.Warning("SteamNetworkingService", $"Handshake reply rejected invalid peer RSA key from {sender}: {ex.Message}"); CryptographicOperations.ZeroMemory(array2); return; } Message message2 = new Message(0u, "NETWORK_INTERNAL_HANDSHAKE_SECRET", 0, messageSizePolicy); message2.WriteBytes(v2); message2.WriteString(text3); byte[] data2 = BuildFramedBytesWithMeta(message2, 0u, ReliableType.Reliable); SendBytes(data2, sender, ReliableType.Reliable); lock (cryptoStateLock) { handshakeStates.TryGetValue(sender.m_SteamID, out HandshakeState value2); if (value2 == null) { value2 = new HandshakeState(); } SetPeerSymmetricKeyUnderLock(sender.m_SteamID, value2, array2); value2.Completed = true; handshakeStates[sender.m_SteamID] = value2; return; } } } private void CompleteHandshakeReceiver(CSteamID sender, byte[] encSecret, string initiatorNonce) { //IL_0036: 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_006c: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (LocalRsa == null) { return; } try { byte[] array = LocalRsa.Decrypt(encSecret, RSAEncryptionPadding.Pkcs1); lock (cryptoStateLock) { handshakeStates.TryGetValue(sender.m_SteamID, out HandshakeState value); if (value == null) { value = new HandshakeState(); } SetPeerSymmetricKeyUnderLock(sender.m_SteamID, value, array); value.Completed = true; handshakeStates[sender.m_SteamID] = value; } byte[] v = HmacSha256Raw(array, Encoding.UTF8.GetBytes(initiatorNonce)); Message message = new Message(0u, "NETWORK_INTERNAL_HANDSHAKE_CONFIRM", 0, messageSizePolicy); message.WriteString(initiatorNonce); message.WriteBytes(v); byte[] data = BuildFramedBytesWithMeta(message, 0u, ReliableType.Reliable); SendBytes(data, sender, ReliableType.Reliable); } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"Handshake decryption error: {arg}"); } } private void CompleteHandshakeInitiator(CSteamID sender, string initiatorNonce, byte[] confirmHmac) { //IL_0018: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) HandshakeState value; byte[] array; lock (cryptoStateLock) { handshakeStates.TryGetValue(sender.m_SteamID, out value); array = value?.Sym; } if (value == null || array == null) { NetLog.Warning("SteamNetworkingService", "Handshake confirm received but no state"); return; } byte[] first = HmacSha256Raw(array, Encoding.UTF8.GetBytes(initiatorNonce)); if (!first.SequenceEqual(confirmHmac)) { NetLog.Warning("SteamNetworkingService", "Handshake confirm HMAC mismatch"); return; } lock (cryptoStateLock) { if (handshakeStates.TryGetValue(sender.m_SteamID, out HandshakeState value2) && value2.Sym != null) { if (!value2.Sym.SequenceEqual(array)) { NetLog.Warning("SteamNetworkingService", "Handshake state changed before confirm commit; discarding stale confirmation"); return; } value2.Completed = true; handshakeStates[sender.m_SteamID] = value2; perPeerSymmetricKey[sender.m_SteamID] = array; } } } private static byte[] HmacSha256Raw(byte[] key, byte[] payload) { using HMACSHA256 hMACSHA = new HMACSHA256(key); return hMACSHA.ComputeHash(payload); } private static string SerializeRsaPublicKey(RSA rsa) { RSAParameters rSAParameters = rsa.ExportParameters(includePrivateParameters: false); string text = Convert.ToBase64String(rSAParameters.Modulus ?? Array.Empty()); string text2 = Convert.ToBase64String(rSAParameters.Exponent ?? Array.Empty()); return text + ":" + text2; } private static RSAParameters DeserializeRsaPublicKey(string s) { if (string.IsNullOrWhiteSpace(s)) { throw new FormatException("Serialized RSA key is empty."); } string[] array = s.Split(':'); if (array.Length != 2 || string.IsNullOrWhiteSpace(array[0]) || string.IsNullOrWhiteSpace(array[1])) { throw new FormatException("Serialized RSA key must contain modulus and exponent segments."); } byte[] array2 = Convert.FromBase64String(array[0]); byte[] array3 = Convert.FromBase64String(array[1]); if (array2.Length == 0 || array3.Length == 0) { throw new FormatException("Serialized RSA key modulus and exponent must not be empty."); } RSAParameters result = default(RSAParameters); result.Modulus = array2; result.Exponent = array3; return result; } public void SetSharedSecret(byte[]? secret) { lock (cryptoStateLock) { ClearGlobalSharedSecretUnderLock(); if (secret == null) { globalHmac?.Dispose(); globalHmac = null; } else { globalSharedSecret = (byte[])secret.Clone(); globalHmac?.Dispose(); globalHmac = new HMACSHA256(globalSharedSecret); } } } public void RegisterModSigner(uint modId, Func signerDelegate) { if (signerDelegate == null) { throw new ArgumentNullException("signerDelegate"); } lock (cryptoStateLock) { modSigners[modId] = signerDelegate; } } public void RegisterModPublicKey(uint modId, RSAParameters pub) { if (pub.Modulus == null || pub.Modulus.Length == 0) { throw new ArgumentException("RSA public key modulus must not be empty.", "pub"); } if (pub.Exponent == null || pub.Exponent.Length == 0) { throw new ArgumentException("RSA public key exponent must not be empty.", "pub"); } lock (cryptoStateLock) { modPublicKeys[modId] = CloneRsaParameters(pub); modPublicKeysSnapshot = BuildModPublicKeySnapshotUnderLock(); } } public void CopyRuntimeStateTo(INetworkingService target) { if (target == null) { throw new ArgumentNullException("target"); } RuntimeRegistration[] array; string[] array2; string[] array3; lock (rpcLock) { array = runtimeRegistrations.ToArray(); array2 = lobbyDataKeys.ToArray(); array3 = playerDataKeys.ToArray(); } KeyValuePair>[] array4; KeyValuePair[] array5; lock (cryptoStateLock) { array4 = modSigners.ToArray(); array5 = modPublicKeys.Select((KeyValuePair entry) => new KeyValuePair(entry.Key, CloneRsaParameters(entry.Value))).ToArray(); } List list = new List(); Func incomingValidator = target.IncomingValidator; try { target.IncomingValidator = IncomingValidator; RuntimeRegistration[] array6 = array; foreach (RuntimeRegistration runtimeRegistration in array6) { list.Add((runtimeRegistration.Instance != null) ? target.RegisterNetworkObject(runtimeRegistration.Instance, runtimeRegistration.ModId, runtimeRegistration.Mask) : target.RegisterNetworkType(runtimeRegistration.Type, runtimeRegistration.ModId, runtimeRegistration.Mask)); } string[] array7 = array2; foreach (string key in array7) { target.RegisterLobbyDataKey(key); } string[] array8 = array3; foreach (string key2 in array8) { target.RegisterPlayerDataKey(key2); } KeyValuePair>[] array9 = array4; for (int l = 0; l < array9.Length; l++) { KeyValuePair> keyValuePair = array9[l]; target.RegisterModSigner(keyValuePair.Key, keyValuePair.Value); } KeyValuePair[] array10 = array5; for (int m = 0; m < array10.Length; m++) { KeyValuePair keyValuePair2 = array10[m]; target.RegisterModPublicKey(keyValuePair2.Key, keyValuePair2.Value); } } catch { try { target.IncomingValidator = incomingValidator; } catch { } for (int num = list.Count - 1; num >= 0; num--) { list[num].Dispose(); } throw; } for (int n = 0; n < array.Length; n++) { array[n].Token.SetDisposeAction(list[n].Dispose); } } private static RSAParameters CloneRsaParameters(RSAParameters source) { RSAParameters result = default(RSAParameters); result.Modulus = ((source.Modulus == null) ? null : ((byte[])source.Modulus.Clone())); result.Exponent = ((source.Exponent == null) ? null : ((byte[])source.Exponent.Clone())); return result; } private RSAParameters[] BuildModPublicKeySnapshotUnderLock() { RSAParameters[] array = new RSAParameters[modPublicKeys.Count]; int num = 0; foreach (RSAParameters value in modPublicKeys.Values) { array[num++] = CloneRsaParameters(value); } return array; } private void InvokeLocalMessage(Message message, CSteamID localSender) { //IL_0012: 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_0019: Unknown result type (might be due to invalid IL or missing references) Message message2 = message; ulong steamID = localSender.m_SteamID; if (IncomingValidator != null && !IncomingValidator(message2, steamID)) { return; } MessageHandler[] handlersSnapshot; lock (rpcLock) { if (!rpcs.TryGetValue(message2.ModID, out Dictionary> value) || !value.TryGetValue(message2.MethodName, out var value2)) { return; } handlersSnapshot = value2.ToArray(); } List<(MessageHandler Handler, object[] CallParams, int Unread, string OverloadKey)> deserialized = new List<(MessageHandler, object[], int, string)>(); bool flag = !string.IsNullOrEmpty(message2.OverloadKey); bool flag2 = false; if (flag) { foreach (MessageHandler messageHandler in handlersSnapshot) { if (messageHandler.Mask == message2.Mask && !(messageHandler.OverloadKey != message2.OverloadKey)) { flag2 = true; break; } } } if (flag2) { CollectCandidates(true); CollectCandidates(false); } else { CollectCandidates(null); } if (deserialized.Count == 0) { NetLog.Warning("SteamNetworkingService", $"No handler matched for local {message2.ModID}:{message2.MethodName} mask={message2.Mask}"); return; } int num = deserialized.FindIndex(((MessageHandler Handler, object[] CallParams, int Unread, string OverloadKey) c) => c.Unread == 0); if (num < 0) { num = 0; } string item = deserialized[num].OverloadKey; bool flag3 = false; for (int j = 0; j < deserialized.Count; j++) { (MessageHandler, object[], int, string) tuple = deserialized[j]; if (!(tuple.Item4 != item)) { try { tuple.Item1.Method.Invoke(tuple.Item1.Target, tuple.Item2); flag3 = true; } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"Local invoke error: {arg}"); } } } if (!flag3) { NetLog.Warning("SteamNetworkingService", $"No handler matched for local {message2.ModID}:{message2.MethodName} mask={message2.Mask}"); } void CollectCandidates(bool? preferOverloadKeyMatch) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) foreach (MessageHandler messageHandler2 in handlersSnapshot) { if (messageHandler2.Mask == message2.Mask) { if (preferOverloadKeyMatch.HasValue) { bool flag4 = messageHandler2.OverloadKey == message2.OverloadKey; if (preferOverloadKeyMatch.Value != flag4) { continue; } } if (TryDeserializeForHandler(message2, messageHandler2, localSender, isLocalLoopback: true, out object[] callParams, out int unread)) { deserialized.Add((messageHandler2, callParams, unread, messageHandler2.OverloadKey)); } } } } } private bool TryDeserializeForHandler(Message source, MessageHandler handler, CSteamID sender, bool isLocalLoopback, out object[] callParams, out int unread) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) callParams = null; unread = int.MaxValue; Message.ReadCursor readCursor = source.SaveReadCursor(); Message.ReadCursor cursor = readCursor; try { if (readCursor.Position == 0) { cursor = AdvanceCursorPastHeader(source); } source.RestoreReadCursor(cursor); ParameterInfo[] parameters = handler.Parameters; int parameterCountWithoutRpcInfo = handler.ParameterCountWithoutRpcInfo; callParams = new object[parameters.Length]; for (int i = 0; i < parameterCountWithoutRpcInfo; i++) { callParams[i] = source.ReadObject(parameters[i].ParameterType); } if (handler.TakesInfo) { Type parameterType = parameters[^1].ParameterType; callParams[parameters.Length - 1] = CreateRpcInfoInstance(parameterType, sender, isLocalLoopback); } unread = source.UnreadLength(); return true; } catch { return false; } finally { source.RestoreReadCursor(readCursor); } } private static Message.ReadCursor AdvanceCursorPastHeader(Message message) { message.ReadByte(); message.ReadUInt(); message.ReadString(); message.ReadInt(); if (message.ProtocolVersion >= 3 && message.ReadBool()) { message.ReadString(); } return message.SaveReadCursor(); } private Message? BuildMessage(uint modId, string methodName, int mask, object?[] parameters, Type[]? parameterTypes) { object?[] parameters2 = parameters; Type[] parameterTypes2 = parameterTypes; string methodName2 = methodName; MessageHandler[] handlersSnapshot; try { Message message = new Message(modId, methodName2, mask, messageSizePolicy); handlersSnapshot = Array.Empty(); lock (rpcLock) { if (rpcs.TryGetValue(modId, out Dictionary> value) && value.TryGetValue(methodName2, out var value2) && value2.Count > 0) { handlersSnapshot = value2.ToArray(); } } if (handlersSnapshot.Length != 0) { if (parameterTypes2 != null && parameterTypes2.Length != parameters2.Length) { throw new ArgumentException($"Parameter type count mismatch: expected {parameterTypes2.Length}, got {parameters2.Length}", "parameters"); } MessageHandler messageHandler = null; if (parameterTypes2 != null) { messageHandler = FindTypedHandler(exactMatch: true) ?? FindTypedHandler(exactMatch: false); } if (messageHandler == null) { messageHandler = FindUntypedHandler(allowNullableConversions: false) ?? FindUntypedHandler(allowNullableConversions: true); } if (messageHandler == null && parameterTypes2 == null) { messageHandler = handlersSnapshot.FirstOrDefault((MessageHandler h) => h.ParameterCountWithoutRpcInfo == parameters2.Length && h.Mask == mask); } if (messageHandler == null) { NetLog.Error("SteamNetworkingService", $"No RPC overload matched method '{methodName2}' for mask {mask} and parameter list."); return null; } message = new Message(modId, methodName2, mask, messageHandler.OverloadKey, messageSizePolicy); ParameterInfo[] parameters3 = messageHandler.Parameters; int parameterCountWithoutRpcInfo = messageHandler.ParameterCountWithoutRpcInfo; for (int i = 0; i < parameterCountWithoutRpcInfo; i++) { Type parameterType = parameters3[i].ParameterType; object obj = parameters2[i]; if (obj == null) { if (parameterType.IsValueType && Nullable.GetUnderlyingType(parameterType) == null) { throw new ArgumentNullException("parameters", $"Parameter {i} for {methodName2} cannot be null; expected non-nullable {parameterType}."); } message.WriteObject(parameterType, null); } else { if (!IsParameterValueCompatible(parameterType, obj)) { throw new ArgumentException($"Parameter {i} type mismatch: expected {parameterType}, got {obj.GetType()}", "parameters"); } message.WriteObject(parameterType, obj); } } } else if (parameterTypes2 != null) { if (parameterTypes2.Length != parameters2.Length) { throw new ArgumentException($"Parameter type count mismatch: expected {parameterTypes2.Length}, got {parameters2.Length}", "parameters"); } for (int j = 0; j < parameters2.Length; j++) { Type type = parameterTypes2[j]; object obj2 = parameters2[j]; if (obj2 == null) { if (type.IsValueType && Nullable.GetUnderlyingType(type) == null) { throw new ArgumentNullException("parameters", $"Parameter {j} for {methodName2} cannot be null; expected non-nullable {type}."); } message.WriteObject(type, null); } else { if (!IsParameterValueCompatible(type, obj2)) { throw new ArgumentException($"Parameter {j} type mismatch: expected {type}, got {obj2.GetType()}", "parameters"); } message.WriteObject(type, obj2); } } } else { for (int k = 0; k < parameters2.Length; k++) { object obj3 = parameters2[k] ?? throw new ArgumentNullException("parameters", $"Parameter {k} is null for unregistered RPC {methodName2}; use typed RPC overload."); message.WriteObject(obj3.GetType(), obj3); } } if (message.Length() > messageSizePolicy.MaxLogicalSize) { NetLog.Error("SteamNetworkingService", "Message exceeds maximum allowed overall size."); return null; } return message; } catch (Exception arg) { NetLog.Error("SteamNetworkingService", $"BuildMessage failed: {arg}"); return null; } MessageHandler? FindTypedHandler(bool exactMatch) { MessageHandler[] array2 = handlersSnapshot; foreach (MessageHandler messageHandler3 in array2) { if (messageHandler3.Mask == mask) { ParameterInfo[] parameters5 = messageHandler3.Parameters; int parameterCountWithoutRpcInfo3 = messageHandler3.ParameterCountWithoutRpcInfo; if (parameterCountWithoutRpcInfo3 == parameterTypes2.Length) { bool flag2 = true; for (int num = 0; num < parameterCountWithoutRpcInfo3; num++) { Type parameterType3 = parameters5[num].ParameterType; Type suppliedType = parameterTypes2[num] ?? throw new ArgumentNullException("parameterTypes", $"Parameter type {num} for {methodName2} cannot be null."); if (!IsParameterTypeCompatible(parameterType3, suppliedType, exactMatch)) { flag2 = false; break; } object obj5 = parameters2[num]; if (obj5 == null) { if (parameterType3.IsValueType && Nullable.GetUnderlyingType(parameterType3) == null) { flag2 = false; break; } } else if (!IsParameterValueCompatible(parameterType3, obj5)) { flag2 = false; break; } } if (flag2) { return messageHandler3; } } } } return null; } MessageHandler? FindUntypedHandler(bool allowNullableConversions) { MessageHandler[] array = handlersSnapshot; foreach (MessageHandler messageHandler2 in array) { if (messageHandler2.Mask == mask) { ParameterInfo[] parameters4 = messageHandler2.Parameters; int parameterCountWithoutRpcInfo2 = messageHandler2.ParameterCountWithoutRpcInfo; if (parameterCountWithoutRpcInfo2 == parameters2.Length) { bool flag = true; for (int m = 0; m < parameterCountWithoutRpcInfo2; m++) { Type parameterType2 = parameters4[m].ParameterType; object obj4 = parameters2[m]; if (obj4 == null) { if (parameterType2.IsValueType && Nullable.GetUnderlyingType(parameterType2) == null) { flag = false; break; } } else if (allowNullableConversions ? (!IsParameterValueCompatible(parameterType2, obj4)) : (!IsParameterValueCompatibleWithoutNullableFallback(parameterType2, obj4))) { flag = false; break; } } if (flag) { return messageHandler2; } } } } return null; } } private static string BuildOverloadKey(MessageHandler handler) { ParameterInfo[] parameters = handler.Parameters; int num = (handler.TakesInfo ? (parameters.Length - 1) : parameters.Length); if (num <= 0) { return string.Empty; } return string.Join("|", from p in parameters.Take(num) select p.ParameterType.AssemblyQualifiedName ?? p.ParameterType.FullName ?? p.ParameterType.Name); } private static bool IsParameterValueCompatible(Type parameterType, object value) { Type underlyingType = Nullable.GetUnderlyingType(parameterType); if (!(underlyingType != null)) { return parameterType.IsAssignableFrom(value.GetType()); } return underlyingType.IsAssignableFrom(value.GetType()); } private static bool IsParameterValueCompatibleWithoutNullableFallback(Type parameterType, object value) { if (Nullable.GetUnderlyingType(parameterType) != null) { return false; } return parameterType.IsAssignableFrom(value.GetType()); } private static bool IsParameterTypeCompatible(Type parameterType, Type suppliedType, bool exactMatch) { if (exactMatch) { return parameterType == suppliedType; } Type underlyingType = Nullable.GetUnderlyingType(parameterType); if (!parameterType.IsAssignableFrom(suppliedType)) { return underlyingType?.IsAssignableFrom(suppliedType) ?? false; } return true; } private SlidingWindowRateLimiter GetOrCreateRateLimiter(ulong steam64) { lock (rateLimiters) { if (!rateLimiters.TryGetValue(steam64, out SlidingWindowRateLimiter value)) { value = new SlidingWindowRateLimiter(100, TimeSpan.FromSeconds(1.0)); rateLimiters[steam64] = value; } return value; } } private static byte[] HmacSha256RawStatic(byte[] key, byte[] payload) { using HMACSHA256 hMACSHA = new HMACSHA256(key); return hMACSHA.ComputeHash(payload); } } } namespace NetworkingLibrary.Modules { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] public class CustomRPCAttribute : Attribute { } internal static class DataValueConverter { public static T ConvertTo(string value) { Type typeFromHandle = typeof(T); if (typeFromHandle == typeof(string)) { return (T)(object)value; } Type type = Nullable.GetUnderlyingType(typeFromHandle) ?? typeFromHandle; object obj = (type.IsEnum ? Enum.Parse(type, value, ignoreCase: true) : Convert.ChangeType(value, type, CultureInfo.InvariantCulture)); return (T)obj; } } public sealed class MessageSizePolicy { public int MaxSize { get; } public int MaxLogicalSize { get; } public MessageSizePolicy(int maxSize) { Message.ValidateMaxSize(maxSize); MaxSize = maxSize; MaxLogicalSize = checked(maxSize * 16); } } public class Message : IDisposable { public readonly struct ReadCursor { private readonly Message owner; internal readonly int Position; internal ReadCursor(Message owner, int position) { this.owner = owner; Position = position; } internal bool IsFrom(Message message) { return owner == message; } } public const byte PROTOCOL_VERSION = 3; public const int DefaultMaxSize = 65536; public const int MinMaxSize = 1024; public const int MaxMaxSize = 134217727; private static readonly object DefaultSizePolicyLock; [Obsolete("Use SetMaxSize(int bytes) so validation and size policy rebuild happen under lock. This field remains for binary compatibility.", false)] public static int MaxSize; private static MessageSizePolicy defaultSizePolicy; public byte ProtocolVersion; public uint ModID; public string MethodName = string.Empty; public int Mask; public string? OverloadKey; private List buffer = new List(); internal byte[] readableBuffer = Array.Empty(); internal int readPos; private bool readableBufferDirty = true; private bool _disposed; private readonly MessageSizePolicy sizePolicy; private static readonly ConcurrentDictionary> writeCasters; private static readonly ConcurrentDictionary> readCasters; public static int MaxLogicalSize => ResolveDefaultSizePolicy().MaxLogicalSize; public static MessageSizePolicy DefaultSizePolicy { get { return Volatile.Read(ref defaultSizePolicy); } private set { Volatile.Write(ref defaultSizePolicy, value); } } public MessageSizePolicy SizePolicy => sizePolicy; private bool UsesReferencePresenceFlags => ProtocolVersion >= 2; public static int GetMaxSize() { return ResolveDefaultSizePolicy().MaxSize; } public static void SetMaxSize(int bytes) { ValidateMaxSize(bytes); lock (DefaultSizePolicyLock) { MessageSizePolicy messageSizePolicy = new MessageSizePolicy(bytes); DefaultSizePolicy = messageSizePolicy; Volatile.Write(ref MaxSize, bytes); } } private static MessageSizePolicy ResolveDefaultSizePolicy() { MessageSizePolicy messageSizePolicy = DefaultSizePolicy; int num = Volatile.Read(ref MaxSize); if (messageSizePolicy.MaxSize == num) { return messageSizePolicy; } lock (DefaultSizePolicyLock) { num = Volatile.Read(ref MaxSize); messageSizePolicy = DefaultSizePolicy; if (messageSizePolicy.MaxSize != num) { ValidateMaxSize(num); messageSizePolicy = (DefaultSizePolicy = new MessageSizePolicy(num)); } return messageSizePolicy; } } public static void ValidateMaxSize(int bytes) { if (bytes < 1024 || bytes > 134217727) { throw new ArgumentOutOfRangeException("bytes", bytes, $"Message max size must be between {1024} and {134217727} bytes."); } } public Message(uint modId, string methodName, int mask) : this(modId, methodName, mask, null, null) { } public Message(uint modId, string methodName, int mask, MessageSizePolicy sizePolicy) : this(modId, methodName, mask, null, sizePolicy) { } public Message(uint modId, string methodName, int mask, string? overloadKey) : this(modId, methodName, mask, overloadKey, null) { } public Message(uint modId, string methodName, int mask, string? overloadKey, MessageSizePolicy? sizePolicy) { this.sizePolicy = sizePolicy ?? ResolveDefaultSizePolicy(); ProtocolVersion = (byte)((overloadKey == null) ? 2 : 3); ModID = modId; MethodName = methodName; Mask = mask; OverloadKey = overloadKey; WriteByte(ProtocolVersion); WriteUInt(ModID); WriteString(MethodName); WriteInt(Mask); if (ProtocolVersion >= 3) { WriteBool(v: true); WriteString(overloadKey); } } public Message(byte[] data) : this(data, null) { } public Message(byte[] data, MessageSizePolicy? sizePolicy) { this.sizePolicy = sizePolicy ?? ResolveDefaultSizePolicy(); SetBytes(data); ProtocolVersion = ReadByte(); if (ProtocolVersion < 1 || ProtocolVersion > 3) { throw new FormatException($"Unsupported message protocol version {ProtocolVersion}. Supported range is 1-{(byte)3}."); } ModID = ReadUInt(); MethodName = ReadString(); Mask = ReadInt(); if (ProtocolVersion >= 3) { bool flag = ReadBool(); OverloadKey = (flag ? ReadString() : null); } else { OverloadKey = null; } } public void SetBytes(byte[] data) { ThrowIfDisposed(); if (data == null) { throw new ArgumentNullException("data"); } if (data.Length > sizePolicy.MaxLogicalSize) { throw new InvalidDataException($"Message payload exceeds max allowed size {sizePolicy.MaxLogicalSize}"); } buffer.Clear(); buffer.AddRange(data); readableBufferDirty = true; readPos = 0; } public byte[] ToArray() { ThrowIfDisposed(); return buffer.ToArray(); } public int Length() { ThrowIfDisposed(); return buffer.Count; } public int UnreadLength() { ThrowIfDisposed(); return Length() - readPos; } public ReadCursor SaveReadCursor() { ThrowIfDisposed(); return new ReadCursor(this, readPos); } public void RestoreReadCursor(ReadCursor cursor) { ThrowIfDisposed(); if (!cursor.IsFrom(this)) { throw new InvalidOperationException("Cursor does not belong to this message."); } EnsureReadableBuffer(); if (cursor.Position < 0 || cursor.Position > readableBuffer.Length) { throw new InvalidDataException("Read cursor out of range."); } readPos = cursor.Position; } public void Reset() { ThrowIfDisposed(); buffer.Clear(); readableBuffer = Array.Empty(); readableBufferDirty = true; readPos = 0; } [Obsolete("Reset always clears message state. The bool parameter is ignored and exists only for compatibility.", false)] public void Reset(bool zero) { Reset(); } private void EnsureCanAppend(int bytesToAppend, string opName) { if (bytesToAppend < 0) { throw new InvalidDataException(opName + " size out of range"); } if ((long)buffer.Count + (long)bytesToAppend > sizePolicy.MaxLogicalSize) { throw new InvalidDataException($"{opName} exceeds max message size {sizePolicy.MaxLogicalSize}"); } } private void AppendSpan(ReadOnlySpan bytes) { if (bytes.Length == 0) { return; } EnsureCanAppend(bytes.Length, "AppendSpan"); if (bytes.Length <= 32) { for (int i = 0; i < bytes.Length; i++) { buffer.Add(bytes[i]); } } else { byte[] collection = bytes.ToArray(); buffer.AddRange(collection); } readableBufferDirty = true; } private void WriteInt32LE(int value) { Span span = stackalloc byte[4]; BinaryPrimitives.WriteInt32LittleEndian(span, value); AppendSpan(span); } private void WriteUInt32LE(uint value) { Span span = stackalloc byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(span, value); AppendSpan(span); } private void WriteInt16LE(short value) { Span span = stackalloc byte[2]; BinaryPrimitives.WriteInt16LittleEndian(span, value); AppendSpan(span); } private void WriteUInt16LE(ushort value) { Span span = stackalloc byte[2]; BinaryPrimitives.WriteUInt16LittleEndian(span, value); AppendSpan(span); } private void WriteInt64LE(long value) { Span span = stackalloc byte[8]; BinaryPrimitives.WriteInt64LittleEndian(span, value); AppendSpan(span); } private void WriteUInt64LE(ulong value) { Span span = stackalloc byte[8]; BinaryPrimitives.WriteUInt64LittleEndian(span, value); AppendSpan(span); } private void WriteSingleLE(float value) { WriteInt32LE(BitConverter.SingleToInt32Bits(value)); } private static int ReadInt32LE(byte[] bytes, int offset) { return BinaryPrimitives.ReadInt32LittleEndian(bytes.AsSpan(offset, 4)); } private static uint ReadUInt32LE(byte[] bytes, int offset) { return BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(offset, 4)); } private static short ReadInt16LE(byte[] bytes, int offset) { return BinaryPrimitives.ReadInt16LittleEndian(bytes.AsSpan(offset, 2)); } private static ushort ReadUInt16LE(byte[] bytes, int offset) { return BinaryPrimitives.ReadUInt16LittleEndian(bytes.AsSpan(offset, 2)); } private static long ReadInt64LE(byte[] bytes, int offset) { return BinaryPrimitives.ReadInt64LittleEndian(bytes.AsSpan(offset, 8)); } private static ulong ReadUInt64LE(byte[] bytes, int offset) { return BinaryPrimitives.ReadUInt64LittleEndian(bytes.AsSpan(offset, 8)); } private static float ReadSingleLE(byte[] bytes, int offset) { return BitConverter.Int32BitsToSingle(ReadInt32LE(bytes, offset)); } public Message WriteByte(byte v) { ThrowIfDisposed(); EnsureCanAppend(1, "WriteByte"); buffer.Add(v); readableBufferDirty = true; return this; } public Message WriteSByte(sbyte v) { return WriteByte((byte)v); } public Message WriteBytes(byte[] v) { ThrowIfDisposed(); if (v == null) { throw new ArgumentNullException("v"); } int num = v.Length; EnsureCanAppend(4 + num, "WriteBytes"); WriteInt32LE(num); if (num > 0) { buffer.AddRange(v); } readableBufferDirty = true; return this; } public Message WriteShort(short v) { ThrowIfDisposed(); WriteInt16LE(v); return this; } public Message WriteUShort(ushort v) { ThrowIfDisposed(); WriteUInt16LE(v); return this; } public Message WriteInt(int v) { ThrowIfDisposed(); WriteInt32LE(v); return this; } public Message WriteUInt(uint v) { ThrowIfDisposed(); WriteUInt32LE(v); return this; } public Message WriteLong(long v) { ThrowIfDisposed(); WriteInt64LE(v); return this; } public Message WriteULong(ulong v) { ThrowIfDisposed(); WriteUInt64LE(v); return this; } public Message WriteFloat(float v) { ThrowIfDisposed(); WriteSingleLE(v); return this; } public Message WriteBool(bool v) { ThrowIfDisposed(); EnsureCanAppend(1, "WriteBool"); buffer.Add(v ? ((byte)1) : ((byte)0)); readableBufferDirty = true; return this; } public Message WriteString(string v) { ThrowIfDisposed(); string s = v ?? ""; byte[] bytes = Encoding.UTF8.GetBytes(s); int num = bytes.Length; EnsureCanAppend(4 + num, "WriteString"); WriteInt32LE(num); if (num > 0) { buffer.AddRange(bytes); } readableBufferDirty = true; return this; } public Message WriteVector3(Vector3 v) { //IL_0001: 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) //IL_001b: Unknown result type (might be due to invalid IL or missing references) WriteFloat(v.x); WriteFloat(v.y); WriteFloat(v.z); return this; } public Message WriteQuaternion(Quaternion q) { //IL_0001: 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) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) WriteFloat(q.x); WriteFloat(q.y); WriteFloat(q.z); WriteFloat(q.w); return this; } public void WriteObject(Type type, object value) { ThrowIfDisposed(); if (type == null) { throw new ArgumentNullException("type"); } Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { bool flag = value != null; WriteBool(flag); if (flag) { WriteObject(underlyingType, value); } return; } Type type2 = TryGetGenericListElementType(type); if (value != null) { if (type.IsArray && !(value is Array)) { throw new ArgumentException("Cannot serialize value of type " + value.GetType().FullName + " as array type " + type.FullName + ".", "value"); } if (type2 != null && !(value is IEnumerable)) { throw new ArgumentException("Cannot serialize value of type " + value.GetType().FullName + " as list type " + type.FullName + ".", "value"); } } bool flag2 = !type.IsValueType && UsesReferencePresenceFlags; if (flag2) { if (value == null) { WriteBool(v: false); return; } } else if (value == null) { throw new ArgumentNullException("value", "Cannot serialize null for non-nullable value type " + type.FullName + "."); } if (writeCasters.TryGetValue(type, out Action value2)) { if (flag2) { WriteBool(v: true); } value2(this, value); return; } if (type.IsEnum) { Type underlyingType2 = Enum.GetUnderlyingType(type); WriteObject(underlyingType2, Convert.ChangeType(value, underlyingType2)); return; } if (type.IsArray) { if (flag2) { WriteBool(v: true); } Type elementType = type.GetElementType(); Array array = (Array)value; WriteInt(array.Length); for (int i = 0; i < array.Length; i++) { WriteObject(elementType, array.GetValue(i)); } return; } if (type2 != null && value is IEnumerable enumerable) { if (flag2) { WriteBool(v: true); } if (value is ICollection collection) { WriteInt(collection.Count); { foreach (object item in collection) { WriteObject(type2, item); } return; } } List list = new List(); foreach (object item2 in enumerable) { list.Add(item2); } WriteInt(list.Count); for (int j = 0; j < list.Count; j++) { WriteObject(type2, list[j]); } return; } if (typeof(IList).IsAssignableFrom(type)) { Type type3 = null; if (type.IsGenericType) { Type[] genericArguments = type.GetGenericArguments(); if (genericArguments.Length == 1) { type3 = genericArguments[0]; } } if (type3 == null) { throw new NotSupportedException("Cannot serialize non-generic IList (heterogeneous lists) without explicit serializer registration."); } if (!(value is IList list2)) { throw new ArgumentException("Cannot serialize value of type " + value.GetType().FullName + " as list type " + type.FullName + ".", "value"); } if (flag2) { WriteBool(v: true); } WriteInt(list2.Count); for (int k = 0; k < list2.Count; k++) { WriteObject(type3, list2[k]); } return; } throw new NotSupportedException("Unsupported type for WriteObject: " + type.FullName + ". Register a serializer using Message.RegisterSerializer. Null handling: reference-like types (including arrays/lists/string/byte[]) are encoded with a leading presence flag and may be null; non-null values for unsupported reference types still require registration."); } public static void RegisterSerializer(Action writer, Func reader) { Action writer2 = writer; Func reader2 = reader; if (writer2 == null) { throw new ArgumentNullException("writer"); } if (reader2 == null) { throw new ArgumentNullException("reader"); } writeCasters[typeof(T)] = delegate(Message m, object o) { writer2(m, (T)o); }; readCasters[typeof(T)] = (Message m) => reader2(m); } public static void UnregisterSerializer() { writeCasters.TryRemove(typeof(T), out Action _); readCasters.TryRemove(typeof(T), out Func _); } internal static void ResetSerializersForTests() { writeCasters.Clear(); readCasters.Clear(); RegisterBuiltInSerializers(); } static Message() { DefaultSizePolicyLock = new object(); MaxSize = 65536; defaultSizePolicy = new MessageSizePolicy(65536); writeCasters = new ConcurrentDictionary>(); readCasters = new ConcurrentDictionary>(); RegisterBuiltInSerializers(); } private static void RegisterBuiltInSerializers() { writeCasters[typeof(byte)] = delegate(Message m, object o) { m.WriteByte((byte)o); }; writeCasters[typeof(sbyte)] = delegate(Message m, object o) { m.WriteSByte((sbyte)o); }; writeCasters[typeof(byte[])] = delegate(Message m, object o) { m.WriteBytes((byte[])o); }; writeCasters[typeof(short)] = delegate(Message m, object o) { m.WriteShort((short)o); }; writeCasters[typeof(ushort)] = delegate(Message m, object o) { m.WriteUShort((ushort)o); }; writeCasters[typeof(int)] = delegate(Message m, object o) { m.WriteInt((int)o); }; writeCasters[typeof(uint)] = delegate(Message m, object o) { m.WriteUInt((uint)o); }; writeCasters[typeof(long)] = delegate(Message m, object o) { m.WriteLong((long)o); }; writeCasters[typeof(ulong)] = delegate(Message m, object o) { m.WriteULong((ulong)o); }; writeCasters[typeof(float)] = delegate(Message m, object o) { m.WriteFloat((float)o); }; writeCasters[typeof(bool)] = delegate(Message m, object o) { m.WriteBool((bool)o); }; writeCasters[typeof(string)] = delegate(Message m, object o) { m.WriteString((string)o); }; writeCasters[typeof(Vector3)] = delegate(Message m, object o) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) m.WriteVector3((Vector3)o); }; writeCasters[typeof(Quaternion)] = delegate(Message m, object o) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) m.WriteQuaternion((Quaternion)o); }; writeCasters[typeof(CSteamID)] = delegate(Message m, object o) { m.WriteULong(((CSteamID)o).m_SteamID); }; writeCasters[typeof(int[])] = delegate(Message m, object o) { int[] array5 = ((int[])o) ?? Array.Empty(); m.WriteInt(array5.Length); for (int l = 0; l < array5.Length; l++) { m.WriteInt(array5[l]); } }; writeCasters[typeof(string[])] = delegate(Message m, object o) { string[] array4 = ((string[])o) ?? Array.Empty(); m.WriteInt(array4.Length); for (int k = 0; k < array4.Length; k++) { m.WriteString(array4[k]); } }; readCasters[typeof(byte)] = (Message m) => m.ReadByte(); readCasters[typeof(sbyte)] = (Message m) => m.ReadSByte(); readCasters[typeof(byte[])] = delegate(Message m) { string name = typeof(byte[]).Name; int num3 = m.ReadCollectionLength(name); if (num3 == 0) { return Array.Empty(); } m.EnsureReadable(num3, name); byte[] array3 = new byte[num3]; m.readableBuffer.AsSpan(m.readPos, num3).CopyTo(array3); m.readPos += num3; return array3; }; readCasters[typeof(short)] = (Message m) => m.ReadShort(); readCasters[typeof(ushort)] = (Message m) => m.ReadUShort(); readCasters[typeof(int)] = (Message m) => m.ReadInt(); readCasters[typeof(uint)] = (Message m) => m.ReadUInt(); readCasters[typeof(long)] = (Message m) => m.ReadLong(); readCasters[typeof(ulong)] = (Message m) => m.ReadULong(); readCasters[typeof(float)] = (Message m) => m.ReadFloat(); readCasters[typeof(bool)] = (Message m) => m.ReadBool(); readCasters[typeof(string)] = (Message m) => m.ReadString(); readCasters[typeof(Vector3)] = (Message m) => m.ReadVector3(); readCasters[typeof(Quaternion)] = (Message m) => m.ReadQuaternion(); readCasters[typeof(CSteamID)] = (Message m) => (object)new CSteamID(m.ReadULong()); readCasters[typeof(int[])] = delegate(Message m) { int num2 = m.ReadCollectionLength(typeof(int[]).Name); if (num2 == 0) { return Array.Empty(); } m.EnsureCollectionPayloadAvailable(typeof(int), num2, typeof(int[]).Name); int[] array2 = new int[num2]; for (int j = 0; j < num2; j++) { array2[j] = m.ReadInt(); } return array2; }; readCasters[typeof(string[])] = delegate(Message m) { int num = m.ReadCollectionLength(typeof(string[]).Name); if (num == 0) { return Array.Empty(); } m.EnsureCollectionPayloadAvailable(4, num, typeof(string[]).Name); string[] array = new string[num]; for (int i = 0; i < num; i++) { array[i] = m.ReadString(); } return array; }; } private void EnsureReadable(int count, string opName) { EnsureReadableBuffer(); if (count < 0 || readPos < 0 || readPos > readableBuffer.Length || count > readableBuffer.Length - readPos) { throw new InvalidDataException(opName + " out of range"); } } private void EnsureReadableBuffer() { if (readableBufferDirty) { readableBuffer = buffer.ToArray(); readableBufferDirty = false; } } private int ReadCollectionLength(string opName) { int num = ReadInt(); if (num < 0) { throw new InvalidDataException(opName + " length out of range"); } if (num > sizePolicy.MaxLogicalSize) { throw new InvalidDataException($"{opName} length exceeds max {sizePolicy.MaxLogicalSize}"); } return num; } private void EnsureCollectionPayloadAvailable(Type elementType, int count, string opName) { EnsureCollectionPayloadAvailable(GetMinimumSerializedBytes(elementType), count, opName); } private void EnsureCollectionPayloadAvailable(int minimumBytesPerItem, int count, string opName) { if (count > 0 && minimumBytesPerItem > 0) { EnsureReadableBuffer(); int num = readableBuffer.Length - readPos; if (count > num / minimumBytesPerItem) { throw new InvalidDataException(opName + " length exceeds available payload"); } } } private int GetMinimumSerializedBytes(Type type) { Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { return 1; } if (!type.IsValueType && UsesReferencePresenceFlags) { return 1; } if (type.IsEnum) { return GetMinimumSerializedBytes(Enum.GetUnderlyingType(type)); } if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(bool)) { return 1; } if (type == typeof(short) || type == typeof(ushort)) { return 2; } if (type == typeof(int) || type == typeof(uint) || type == typeof(float)) { return 4; } if (type == typeof(long) || type == typeof(ulong) || type == typeof(CSteamID)) { return 8; } if (type == typeof(Vector3)) { return 12; } if (type == typeof(Quaternion)) { return 16; } if (type == typeof(string) || type == typeof(byte[]) || type.IsArray || TryGetGenericListElementType(type) != null) { return 4; } return 0; } private static bool IsConcreteConstructible(Type type) { if (type.IsAbstract || type.IsInterface) { return false; } if (type.IsValueType) { return true; } return type.GetConstructor(Type.EmptyTypes) != null; } private static Type? TryGetGenericListElementType(Type type) { if (type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>)) { return type.GetGenericArguments()[0]; } Type[] interfaces = type.GetInterfaces(); foreach (Type type2 in interfaces) { if (type2.IsGenericType && type2.GetGenericTypeDefinition() == typeof(IList<>)) { return type2.GetGenericArguments()[0]; } } return null; } private static bool ImplementsExactGenericIList(Type type) { if (type.IsInterface && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IList<>)) { return true; } Type[] interfaces = type.GetInterfaces(); foreach (Type type2 in interfaces) { if (type2.IsGenericType && type2.GetGenericTypeDefinition() == typeof(IList<>)) { return true; } } return false; } private static bool TryCopyItemsToListTarget(object target, Type elementType, IList source) { if (target is IList list) { if (list.IsReadOnly || list.IsFixedSize) { return false; } for (int i = 0; i < source.Count; i++) { list.Add(source[i]); } return true; } MethodInfo method = target.GetType().GetMethod("Add", new Type[1] { elementType }); if (method == null) { return false; } for (int j = 0; j < source.Count; j++) { try { method.Invoke(target, new object[1] { source[j] }); } catch (TargetInvocationException) { return false; } catch (ArgumentException) { return false; } } return true; } private static void TraceListMaterializationFallback(Type targetType, Type elementType, string reason) { string text = "List deserialization fallback to List<" + elementType.Name + "> for " + targetType.FullName + ": " + reason; try { if (Debug.unityLogger != null) { Debug.unityLogger.LogWarning("Message", (object)text); return; } } catch (Exception) { } Trace.TraceWarning(text); } public byte ReadByte() { ThrowIfDisposed(); EnsureReadable(1, "ReadByte"); byte result = readableBuffer[readPos]; readPos++; return result; } public sbyte ReadSByte() { return (sbyte)ReadByte(); } public short ReadShort() { ThrowIfDisposed(); EnsureReadable(2, "ReadShort"); short result = ReadInt16LE(readableBuffer, readPos); readPos += 2; return result; } public ushort ReadUShort() { ThrowIfDisposed(); EnsureReadable(2, "ReadUShort"); ushort result = ReadUInt16LE(readableBuffer, readPos); readPos += 2; return result; } public int ReadInt() { ThrowIfDisposed(); EnsureReadable(4, "ReadInt"); int result = ReadInt32LE(readableBuffer, readPos); readPos += 4; return result; } public uint ReadUInt() { ThrowIfDisposed(); EnsureReadable(4, "ReadUInt"); uint result = ReadUInt32LE(readableBuffer, readPos); readPos += 4; return result; } public long ReadLong() { ThrowIfDisposed(); EnsureReadable(8, "ReadLong"); long result = ReadInt64LE(readableBuffer, readPos); readPos += 8; return result; } public ulong ReadULong() { ThrowIfDisposed(); EnsureReadable(8, "ReadULong"); ulong result = ReadUInt64LE(readableBuffer, readPos); readPos += 8; return result; } public float ReadFloat() { ThrowIfDisposed(); EnsureReadable(4, "ReadFloat"); float result = ReadSingleLE(readableBuffer, readPos); readPos += 4; return result; } public bool ReadBool() { ThrowIfDisposed(); EnsureReadable(1, "ReadBool"); bool result = readableBuffer[readPos] != 0; readPos++; return result; } public string ReadString() { ThrowIfDisposed(); int num = ReadInt(); if (num < 0) { throw new InvalidDataException("ReadString out of range"); } if (num > sizePolicy.MaxLogicalSize) { throw new InvalidDataException($"ReadString length exceeds max {sizePolicy.MaxLogicalSize}"); } if (num == 0) { return string.Empty; } EnsureReadable(num, "ReadString"); string @string = Encoding.UTF8.GetString(readableBuffer, readPos, num); readPos += num; return @string; } public Vector3 ReadVector3() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(ReadFloat(), ReadFloat(), ReadFloat()); } public Quaternion ReadQuaternion() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) return new Quaternion(ReadFloat(), ReadFloat(), ReadFloat(), ReadFloat()); } public object ReadObject(Type type) { ThrowIfDisposed(); if (type == null) { throw new ArgumentNullException("type"); } Type underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) { if (!ReadBool()) { return null; } return ReadObject(underlyingType); } if (!type.IsValueType && UsesReferencePresenceFlags && !ReadBool()) { return null; } if (readCasters.TryGetValue(type, out Func value)) { return value(this); } if (type.IsEnum) { Type underlyingType2 = Enum.GetUnderlyingType(type); object value2 = ReadObject(underlyingType2); return Enum.ToObject(type, value2); } if (type.IsArray) { Type elementType = type.GetElementType(); int num = ReadCollectionLength(type.FullName ?? "Array"); EnsureCollectionPayloadAvailable(elementType, num, type.FullName ?? "Array"); Array array = Array.CreateInstance(elementType, num); for (int i = 0; i < num; i++) { object value3 = ReadObject(elementType); array.SetValue(value3, i); } return array; } if (type.IsGenericType) { Type genericTypeDefinition = type.GetGenericTypeDefinition(); if (genericTypeDefinition == typeof(List<>) || genericTypeDefinition == typeof(IList<>)) { Type type2 = type.GetGenericArguments()[0]; int num2 = ReadCollectionLength(type.FullName ?? "List"); EnsureCollectionPayloadAvailable(type2, num2, type.FullName ?? "List"); Type type3 = typeof(List<>).MakeGenericType(type2); IList list = (IList)Activator.CreateInstance(type3); for (int j = 0; j < num2; j++) { object value4 = ReadObject(type2); list.Add(value4); } return list; } } Type type4 = TryGetGenericListElementType(type); bool flag = typeof(IList).IsAssignableFrom(type) || ImplementsExactGenericIList(type); if (type4 != null && flag) { int num3 = ReadCollectionLength(type.FullName ?? "List"); EnsureCollectionPayloadAvailable(type4, num3, type.FullName ?? "List"); Type type5 = typeof(List<>).MakeGenericType(type4); IList list2 = (IList)Activator.CreateInstance(type5); for (int k = 0; k < num3; k++) { list2.Add(ReadObject(type4)); } if (!IsConcreteConstructible(type)) { TraceListMaterializationFallback(type, type4, "target type is not concrete/constructible"); return list2; } try { object obj = Activator.CreateInstance(type); if (!TryCopyItemsToListTarget(obj, type4, list2)) { TraceListMaterializationFallback(type, type4, "target rejected copied items"); return list2; } return obj; } catch (Exception ex) when (ex is MissingMethodException || ex is MemberAccessException || ex is TargetInvocationException || ex is ArgumentException || ex is InvalidOperationException) { TraceListMaterializationFallback(type, type4, ex.GetType().Name); return list2; } } throw new NotSupportedException("Unsupported read type " + type.FullName + ". Register a deserializer using Message.RegisterSerializer. Null handling: reference-like types (including arrays/lists/string/byte[]) are decoded from a leading presence flag and may be null; non-null payloads for unsupported reference types still require registration."); } public byte[] CompressPayload() { return CompressPayload(ToArray()); } public byte[] CompressPayload(byte[] payload) { if (payload == null) { throw new ArgumentNullException("payload"); } return CompressPayload(payload.AsSpan()); } public byte[] CompressPayload(ReadOnlySpan payload) { using MemoryStream memoryStream = new MemoryStream(); using (GZipStream gZipStream = new GZipStream(memoryStream, CompressionLevel.Optimal, leaveOpen: true)) { gZipStream.Write(payload); } return memoryStream.ToArray(); } public byte[] DecompressPayloadForCurrentPolicy(byte[] compressed, int maxOutputSize = -1) { if (maxOutputSize < 0) { maxOutputSize = sizePolicy.MaxLogicalSize; } return DecompressPayloadCore(compressed, maxOutputSize); } public static byte[] DecompressPayload(byte[] compressed, int maxOutputSize = -1) { if (maxOutputSize < 0) { maxOutputSize = ResolveDefaultSizePolicy().MaxLogicalSize; } return DecompressPayloadCore(compressed, maxOutputSize); } private static byte[] DecompressPayloadCore(byte[] compressed, int maxOutputSize) { if (compressed == null) { throw new ArgumentNullException("compressed"); } if (maxOutputSize <= 0) { throw new ArgumentOutOfRangeException("maxOutputSize", maxOutputSize, "Decompression max output size must be greater than zero."); } using MemoryStream stream = new MemoryStream(compressed); using GZipStream gZipStream = new GZipStream(stream, CompressionMode.Decompress); using MemoryStream memoryStream = new MemoryStream(); byte[] array = new byte[8192]; long num = 0L; while (true) { int num2 = gZipStream.Read(array, 0, array.Length); if (num2 == 0) { break; } num += num2; if (num > maxOutputSize) { throw new InvalidDataException($"Decompressed payload exceeds max allowed size {maxOutputSize}"); } memoryStream.Write(array, 0, num2); } return memoryStream.ToArray(); } public void Dispose() { if (!_disposed) { _disposed = true; buffer.Clear(); readableBuffer = Array.Empty(); readableBufferDirty = true; readPos = 0; GC.SuppressFinalize(this); } } private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException("Message"); } } } public static class ModId { public static uint FromGuid(string guid) { if (string.IsNullOrWhiteSpace(guid)) { throw new ArgumentException("ModId requires a non-empty mod identifier.", "guid"); } using MD5 mD = MD5.Create(); byte[] array = mD.ComputeHash(Encoding.UTF8.GetBytes(guid.Trim().ToLowerInvariant())); return BinaryPrimitives.ReadUInt32LittleEndian(array); } public static uint FromGuidV2(string guid) { Guid guid2 = ParseGuid(guid); using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(guid2.ToString("D"))); return FoldHashToUInt32(array); } private static Guid ParseGuid(string guid) { if (string.IsNullOrWhiteSpace(guid)) { throw new ArgumentException("ModId requires a non-empty GUID string (for example, \"123e4567-e89b-12d3-a456-426614174000\").", "guid"); } if (!Guid.TryParse(guid, out var result)) { throw new ArgumentException("ModId requires a valid GUID string in a recognized format.", "guid"); } return result; } private static uint FoldHashToUInt32(ReadOnlySpan hashBytes) { if (hashBytes.Length == 0 || hashBytes.Length % 4 != 0) { throw new ArgumentException("Hash bytes must be non-empty and divisible by four.", "hashBytes"); } uint num = 0u; for (int i = 0; i < hashBytes.Length; i += 4) { num ^= BinaryPrimitives.ReadUInt32LittleEndian(hashBytes.Slice(i, 4)); } return num; } } internal static class NetLog { private readonly struct ThrottleState { internal readonly double LastAt; internal readonly double LastSeenAt; internal ThrottleState(double lastAt, double lastSeenAt) { LastAt = lastAt; LastSeenAt = lastSeenAt; } } private const double ThrottleRetentionSeconds = 1200.0; private const int CleanupCheckInterval = 64; private const double CleanupMinIntervalSeconds = 30.0; private const double FallbackLogCooldownSeconds = 2.0; private static readonly object throttleLock = new object(); private static readonly Dictionary throttleByKey = new Dictionary(); private static readonly Dictionary suppressedByKey = new Dictionary(); private static int cleanupCallCount; private static double lastCleanupAt = double.NegativeInfinity; private static readonly object fallbackThrottleLock = new object(); private static readonly Dictionary fallbackThrottleByKey = new Dictionary(); private static readonly Dictionary fallbackSuppressedByKey = new Dictionary(); private static int fallbackCleanupCallCount; private static double fallbackLastCleanupAt = double.NegativeInfinity; internal static Func TimeProvider = () => DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; public static void Info(string source, string message) { Guarded(source, "info", message, WriteInfo, fallbackAsError: false, includeOriginalMessage: true); } public static void Debug(string source, string message, bool includeOriginalMessageInFallback = false) { Guarded(source, "debug", message, WriteDebug, fallbackAsError: false, includeOriginalMessageInFallback); } public static void Warning(string source, string message) { Guarded(source, "warning", message, WriteWarning, fallbackAsError: false, includeOriginalMessage: true); } public static void Error(string source, string message) { Guarded(source, "error", message, WriteError, fallbackAsError: true, includeOriginalMessage: true); } public static bool TryEnterCooldown(string key, double cooldownSeconds, double? now = null) { lock (throttleLock) { double num = ((now.HasValue && IsFinite(now.Value)) ? now.Value : ResolveTime(null)); MaybeCleanupStaleEntries(num, throttleByKey, suppressedByKey, ref cleanupCallCount, ref lastCleanupAt); if (throttleByKey.TryGetValue(key, out var value)) { bool flag = IsInCooldown(num, value.LastAt, cooldownSeconds); throttleByKey[key] = (flag ? new ThrottleState(value.LastAt, num) : new ThrottleState(num, num)); if (!flag) { suppressedByKey.Remove(key); } return !flag; } throttleByKey[key] = new ThrottleState(num, num); return true; } } public static void DebugThrottled(string source, string key, double cooldownSeconds, string message, Func? timeProvider = null, bool includeOriginalMessageInFallback = false) { double value = ResolveTime(timeProvider); if (TryEnterCooldown(key, cooldownSeconds, value)) { Debug(source, message, includeOriginalMessageInFallback); } } public static void ErrorThrottled(string source, string key, double cooldownSeconds, string message, Func? timeProvider = null) { double num = ResolveTime(timeProvider); if (TryEnterSuppressedCooldown(key, cooldownSeconds, num, out var suppressed)) { string text = ((suppressed > 0) ? $" Suppressed {suppressed} similar exceptions." : string.Empty); Guarded(source, "error", message + text, WriteError, fallbackAsError: true, includeOriginalMessage: true, num); } } private static bool TryEnterSuppressedCooldown(string key, double cooldownSeconds, double now, out int suppressed) { lock (throttleLock) { return TryEnterSuppressedCooldownUnderLock(key, cooldownSeconds, now, throttleByKey, suppressedByKey, ref cleanupCallCount, ref lastCleanupAt, out suppressed); } } private static bool TryEnterFallbackCooldown(string key, double cooldownSeconds, double now, out int suppressed) { lock (fallbackThrottleLock) { return TryEnterSuppressedCooldownUnderLock(key, cooldownSeconds, now, fallbackThrottleByKey, fallbackSuppressedByKey, ref fallbackCleanupCallCount, ref fallbackLastCleanupAt, out suppressed); } } private static bool TryEnterSuppressedCooldownUnderLock(string key, double cooldownSeconds, double now, Dictionary throttle, Dictionary suppressedCounts, ref int localCleanupCallCount, ref double localLastCleanupAt, out int suppressed) { suppressed = 0; MaybeCleanupStaleEntries(now, throttle, suppressedCounts, ref localCleanupCallCount, ref localLastCleanupAt); if (throttle.TryGetValue(key, out var value) && IsInCooldown(now, value.LastAt, cooldownSeconds)) { throttle[key] = new ThrottleState(value.LastAt, now); suppressedCounts[key] = ((!suppressedCounts.TryGetValue(key, out var value2)) ? 1 : (value2 + 1)); return false; } throttle[key] = new ThrottleState(now, now); if (suppressedCounts.TryGetValue(key, out suppressed)) { suppressedCounts.Remove(key); } return true; } private static double ResolveTime(Func? timeProvider) { if (timeProvider != null && TryGetFiniteTime(timeProvider, out var time)) { return time; } if (TryGetFiniteTime(TimeProvider, out time)) { return time; } return DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; } private static bool TryGetFiniteTime(Func timeProvider, out double time) { try { time = timeProvider(); return IsFinite(time); } catch { time = 0.0; return false; } } private static bool IsFinite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } private static bool IsInCooldown(double currentTime, double lastAt, double cooldownSeconds) { if (currentTime >= lastAt) { return currentTime - lastAt < cooldownSeconds; } return false; } private static void WriteInfo(string message) { Net.Logger.LogInfo((object)message); } private static void WriteDebug(string message) { Net.Logger.LogDebug((object)message); } private static void WriteWarning(string message) { Net.Logger.LogWarning((object)message); } private static void WriteError(string message) { Net.Logger.LogError((object)message); } private static void Guarded(string source, string level, string message, Action write, bool fallbackAsError, bool includeOriginalMessage, double? fallbackTime = null) { try { write(message); } catch (Exception ex) { string text = (includeOriginalMessage ? (". Original message: " + message) : string.Empty); string text2 = "[" + source + "] Failed to write " + level + " log. Exception: " + ex.GetType().Name + ": " + ex.Message + text; double now = ((fallbackTime.HasValue && IsFinite(fallbackTime.Value)) ? fallbackTime.Value : ResolveTime(null)); if (!TryEnterFallbackCooldown("fallback:" + source + ":" + level, 2.0, now, out var suppressed)) { return; } if (suppressed > 0) { text2 += $" Suppressed {suppressed} similar fallback logs."; } try { if (fallbackAsError) { Debug.LogError((object)text2); } else { Debug.LogWarning((object)text2); } } catch { if (fallbackAsError) { Trace.TraceError(text2); } else { Trace.TraceWarning(text2); } } } } internal static void ResetForTests() { lock (throttleLock) { throttleByKey.Clear(); suppressedByKey.Clear(); cleanupCallCount = 0; lastCleanupAt = double.NegativeInfinity; } lock (fallbackThrottleLock) { fallbackThrottleByKey.Clear(); fallbackSuppressedByKey.Clear(); fallbackCleanupCallCount = 0; fallbackLastCleanupAt = double.NegativeInfinity; } TimeProvider = () => DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; } private static void MaybeCleanupStaleEntries(double now, Dictionary throttle, Dictionary suppressedCounts, ref int localCleanupCallCount, ref double localLastCleanupAt) { localCleanupCallCount++; if (now >= localLastCleanupAt && ((now - localLastCleanupAt < 30.0 && localCleanupCallCount % 64 != 0) || now - localLastCleanupAt < 30.0)) { return; } localLastCleanupAt = now; List list = null; foreach (KeyValuePair item in throttle) { double num = ((now >= item.Value.LastSeenAt) ? (now - item.Value.LastSeenAt) : (item.Value.LastSeenAt - now)); if (!(num <= 1200.0)) { if (list == null) { list = new List(); } list.Add(item.Key); } } if (list == null) { return; } foreach (string item2 in list) { throttle.Remove(item2); suppressedCounts.Remove(item2); } } } public static class NetworkingPhotonExtensions { private struct PersonaNameCacheEntry { public string Name; public DateTime UpdatedUtc; } private static readonly TimeSpan UnresolvedMappingWarningCooldown = TimeSpan.FromSeconds(7.0); private static readonly TimeSpan UnresolvedMappingWarningPruneAge = TimeSpan.FromSeconds(UnresolvedMappingWarningCooldown.TotalSeconds * 4.0); private const int UnresolvedMappingWarningThrottleMaxEntries = 2048; private static readonly Dictionary UnresolvedMappingWarningThrottle = new Dictionary(StringComparer.Ordinal); private static readonly object UnresolvedMappingWarningThrottleLock = new object(); private static DateTime UnresolvedMappingWarningNextPruneUtc = DateTime.MinValue; private static readonly TimeSpan PersonaLookupExceptionCooldown = TimeSpan.FromSeconds(5.0); private static DateTime lastPersonaLookupExceptionUtc = DateTime.MinValue; private static int suppressedPersonaLookupExceptions; private static readonly object personaLookupExceptionLock = new object(); private static readonly TimeSpan PersonaNameCacheTtl = TimeSpan.FromSeconds(5.0); private static readonly TimeSpan PersonaNameCachePruneAge = TimeSpan.FromSeconds(PersonaNameCacheTtl.TotalSeconds * 6.0); private const int PersonaNameCacheMaxEntries = 4096; private static readonly Dictionary PersonaNameCache = new Dictionary(); private static readonly object PersonaNameCacheLock = new object(); private static DateTime PersonaNameCacheNextPruneUtc = DateTime.MinValue; private const ulong SteamId64Base = 76561197960265728uL; internal static Func PhotonInRoom = () => PhotonNetwork.InRoom; internal static Func CurrentPhotonRoom = () => PhotonNetwork.CurrentRoom; internal static Func PersonaNameLookup = (ulong sid) => SteamFriends.GetFriendPersonaName(new CSteamID(sid)); internal static Func UtcNow = () => DateTime.UtcNow; private static readonly string[] StableIdPropertyKeys = new string[10] { "steam64", "steamid64", "steam_id64", "steamid", "steam_id", "steam", "authid", "auth_id", "userid", "user_id" }; public static Dictionary MapPhotonActorsToSteam(INetworkingService svc) { Dictionary dictionary = new Dictionary(); if (svc == null) { return dictionary; } if (!TryGetCurrentPhotonRoom(out Room room)) { return dictionary; } ulong[] lobbyMemberSteamIds = svc.GetLobbyMemberSteamIds(); if (lobbyMemberSteamIds == null || lobbyMemberSteamIds.Length == 0) { return dictionary; } HashSet lobbyIdSet = new HashSet(lobbyMemberSteamIds); Dictionary> dictionary2 = BuildPersonaLookupIndex(lobbyMemberSteamIds); foreach (KeyValuePair player in room.Players) { int key = player.Key; Player value = player.Value; List value2; if (TryResolveStableIdentity(value, lobbyIdSet, out ulong matchedSteamId, out string issue)) { dictionary[key] = matchedSteamId; } else if (!string.IsNullOrEmpty(value.NickName) && dictionary2.TryGetValue(value.NickName, out value2)) { if (value2.Count == 1) { dictionary[key] = value2[0]; } else { LogUnresolvedMappingWarning(key, $"ambiguous nickname '{value.NickName}' across {value2.Count} lobby members", $"Photon actor {key} nickname '{value.NickName}' is ambiguous across {value2.Count} lobby members; actor left unmapped."); } } else { string text = issue ?? "no stable identity present and no nickname match found"; LogUnresolvedMappingWarning(key, text, $"Photon actor {key} has no Steam mapping ({text}); actor left unmapped."); } } return dictionary; } private static bool TryGetCurrentPhotonRoom(out Room? room) { room = null; try { if (!PhotonInRoom()) { return false; } room = CurrentPhotonRoom(); return room != null; } catch (Exception ex) { LogWarning("Photon room state unavailable while mapping actors; actor map left empty. Exception: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static bool TryResolveStableIdentity(Player player, HashSet lobbyIdSet, out ulong matchedSteamId, out string? issue) { matchedSteamId = 0uL; issue = null; HashSet hashSet = new HashSet(); foreach (ulong stableIdCandidate in GetStableIdCandidates(player)) { if (lobbyIdSet.Contains(stableIdCandidate)) { hashSet.Add(stableIdCandidate); } } if (hashSet.Count == 1) { using HashSet.Enumerator enumerator2 = hashSet.GetEnumerator(); if (enumerator2.MoveNext()) { ulong current2 = enumerator2.Current; matchedSteamId = current2; return true; } } if (hashSet.Count > 1) { List list = new List(hashSet); list.Sort(); issue = "multiple stable IDs matched lobby members (" + string.Join(",", list) + ")"; LogUnresolvedMappingWarning(player.ActorNumber, issue, $"Photon actor {player.ActorNumber} has ambiguous stable identity: {issue}."); return false; } issue = "no stable ID candidate matched lobby members"; return false; } private static IEnumerable GetStableIdCandidates(Player player) { if (player == null) { yield break; } if (TryParseSteamId(player.UserId, out var steamId)) { yield return steamId; } if (player.CustomProperties == null || ((Dictionary)(object)player.CustomProperties).Count == 0) { yield break; } DictionaryEntryEnumerator val = player.CustomProperties.GetEnumerator(); try { while (((DictionaryEntryEnumerator)(ref val)).MoveNext()) { DictionaryEntry current = ((DictionaryEntryEnumerator)(ref val)).Current; if (current.Key is string key && LooksLikeStableIdKey(key) && TryParseSteamId(current.Value, out var steamId2)) { yield return steamId2; } } } finally { ((IDisposable)(DictionaryEntryEnumerator)(ref val)).Dispose(); } val = default(DictionaryEntryEnumerator); } private static bool LooksLikeStableIdKey(string key) { if (string.IsNullOrEmpty(key)) { return false; } for (int i = 0; i < StableIdPropertyKeys.Length; i++) { if (key.Equals(StableIdPropertyKeys[i], StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static bool TryParseSteamId(object raw, out ulong steamId) { //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) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) steamId = 0uL; if (raw == null) { return false; } if (!(raw is CSteamID val)) { if (!(raw is ulong num)) { if (!(raw is long num2)) { if (raw is string text) { string raw2 = text; if (TryParseSteamIdString(raw2, out var steamId2)) { steamId = steamId2; return true; } } } else { long num3 = num2; if (num3 > 0 && IsPlausibleSteam64((ulong)num3)) { steamId = (ulong)num3; return true; } } } else { ulong num4 = num; if (IsPlausibleSteam64(num4)) { steamId = num4; return true; } } } else { CSteamID val2 = val; if (IsPlausibleSteam64(val2.m_SteamID)) { steamId = val2.m_SteamID; return true; } } return false; } private static bool TryParseSteamIdString(string raw, out ulong steamId) { steamId = 0uL; if (string.IsNullOrWhiteSpace(raw)) { return false; } string text = raw.Trim(); if (ulong.TryParse(text, out var result) && IsPlausibleSteam64(result)) { steamId = result; return true; } if (text.StartsWith("STEAM_", StringComparison.OrdinalIgnoreCase)) { string[] array = text.Substring(6).Split(':'); if (array.Length != 3) { return false; } if (!int.TryParse(array[0], out var result2) || (result2 != 0 && result2 != 1)) { return false; } if (!int.TryParse(array[1], out var result3) || (result3 != 0 && result3 != 1)) { return false; } if (!ulong.TryParse(array[2], out var result4)) { return false; } if (result4 > (ulong)(-1L - (long)result3) / 2uL) { return false; } return TryComposePublicSteam64(result4 * 2 + (ulong)result3, out steamId); } if (text.Length > 2 && text[0] == '[' && text[text.Length - 1] == ']') { text = text.Substring(1, text.Length - 2); } string[] array2 = text.Split(':'); if (array2.Length != 3 || !array2[0].Equals("U", StringComparison.OrdinalIgnoreCase)) { return false; } if (!int.TryParse(array2[1], out var result5) || result5 != 1) { return false; } if (!ulong.TryParse(array2[2], out var result6)) { return false; } return TryComposePublicSteam64(result6, out steamId); } private static bool TryComposePublicSteam64(ulong accountId, out ulong steamId) { steamId = 0uL; if (accountId > 18370182875749285887uL) { return false; } steamId = 76561197960265728L + accountId; return IsPlausibleSteam64(steamId); } private static bool IsPlausibleSteam64(ulong value) { return value >= 76561197960265728L; } internal static bool TryParseSteamIdForTests(object raw, out ulong steamId) { return TryParseSteamId(raw, out steamId); } private static Dictionary> BuildPersonaLookupIndex(ulong[] lobbyIds) { Dictionary> dictionary = new Dictionary>(StringComparer.Ordinal); if (lobbyIds == null || lobbyIds.Length == 0) { return dictionary; } HashSet hashSet = new HashSet(); foreach (ulong num in lobbyIds) { if (IsPlausibleSteam64(num) && hashSet.Add(num) && TryGetPersonaName(num, out string name) && !string.IsNullOrEmpty(name)) { if (!dictionary.TryGetValue(name, out var value)) { value = (dictionary[name] = new List(1)); } value.Add(num); } } return dictionary; } private static bool TryGetPersonaName(ulong sid, out string? name) { DateTime dateTime = UtcNow(); lock (PersonaNameCacheLock) { if (dateTime >= PersonaNameCacheNextPruneUtc) { PrunePersonaNameCache(dateTime); PersonaNameCacheNextPruneUtc = dateTime + PersonaNameCacheTtl; } if (PersonaNameCache.TryGetValue(sid, out var value) && dateTime >= value.UpdatedUtc && dateTime - value.UpdatedUtc < PersonaNameCacheTtl) { name = value.Name; return true; } } try { name = PersonaNameLookup(sid) ?? string.Empty; } catch (Exception ex) { LogPersonaLookupExceptionThrottled(ex, sid); name = string.Empty; return false; } lock (PersonaNameCacheLock) { PersonaNameCache[sid] = new PersonaNameCacheEntry { Name = name, UpdatedUtc = dateTime }; CapPersonaNameCache(); } return true; } private static void PrunePersonaNameCache(DateTime nowUtc) { if (PersonaNameCache.Count == 0) { return; } DateTime dateTime = nowUtc - PersonaNameCachePruneAge; List list = null; foreach (KeyValuePair item in PersonaNameCache) { if (!(item.Value.UpdatedUtc >= dateTime)) { if (list == null) { list = new List(); } list.Add(item.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { PersonaNameCache.Remove(list[i]); } } } private static void CapPersonaNameCache() { if (PersonaNameCache.Count <= 4096) { return; } ulong num = 0uL; DateTime dateTime = DateTime.MaxValue; foreach (KeyValuePair item in PersonaNameCache) { if (!(item.Value.UpdatedUtc >= dateTime)) { dateTime = item.Value.UpdatedUtc; num = item.Key; } } if (num != 0L) { PersonaNameCache.Remove(num); } } internal static Dictionary> BuildPersonaLookupIndexForTests(ulong[] lobbyIds) { return BuildPersonaLookupIndex(lobbyIds); } internal static void ResetPersonaNameCacheForTests() { lock (PersonaNameCacheLock) { PersonaNameCache.Clear(); PersonaNameCacheNextPruneUtc = DateTime.MinValue; } lock (personaLookupExceptionLock) { lastPersonaLookupExceptionUtc = DateTime.MinValue; suppressedPersonaLookupExceptions = 0; } PersonaNameLookup = (ulong sid) => SteamFriends.GetFriendPersonaName(new CSteamID(sid)); UtcNow = () => DateTime.UtcNow; } internal static void ResetPhotonRoomStateForTests() { PhotonInRoom = () => PhotonNetwork.InRoom; CurrentPhotonRoom = () => PhotonNetwork.CurrentRoom; } private static void LogWarning(string message) { try { if (TryLogWarningWithNetLogger(message)) { return; } } catch (Exception ex) { LogWarningOrTrace("[NetworkingPhotonExtensions] Failed to write warning log. Exception: " + ex.GetType().Name + ": " + ex.Message + ". Original message: " + message); return; } LogWarningOrTrace(message); } private static bool TryLogWarningWithNetLogger(string message) { ManualLogSource logger = Net.Logger; if (logger == null) { return false; } logger.LogWarning((object)message); return true; } private static void LogWarningOrTrace(string message) { try { Debug.LogWarning((object)message); } catch { Trace.TraceWarning(message); } } private static void LogPersonaLookupExceptionThrottled(Exception ex, ulong steamId) { DateTime dateTime = UtcNow(); lock (personaLookupExceptionLock) { if (lastPersonaLookupExceptionUtc != DateTime.MinValue && dateTime >= lastPersonaLookupExceptionUtc && dateTime - lastPersonaLookupExceptionUtc < PersonaLookupExceptionCooldown) { suppressedPersonaLookupExceptions++; return; } int num = suppressedPersonaLookupExceptions; suppressedPersonaLookupExceptions = 0; lastPersonaLookupExceptionUtc = dateTime; string text = ((num > 0) ? $" Suppressed {num} similar exceptions." : string.Empty); LogWarning($"Steam persona lookup failed for lobby member {steamId}. Exception: {ex.GetType().Name}: {ex.Message}.{text}"); } } private static void LogUnresolvedMappingWarning(int actorNumber, string issueText, string message) { if (ShouldEmitUnresolvedMappingWarning(actorNumber, issueText, UtcNow())) { LogWarning(message); } } internal static bool ShouldEmitUnresolvedMappingWarning(int actorNumber, string issueText, DateTime nowUtc) { string key = BuildUnresolvedMappingWarningKey(actorNumber, issueText); lock (UnresolvedMappingWarningThrottleLock) { if (nowUtc >= UnresolvedMappingWarningNextPruneUtc) { PruneUnresolvedMappingWarningThrottle(nowUtc); UnresolvedMappingWarningNextPruneUtc = nowUtc + UnresolvedMappingWarningCooldown; } if (UnresolvedMappingWarningThrottle.TryGetValue(key, out var value) && nowUtc >= value && nowUtc - value < UnresolvedMappingWarningCooldown) { return false; } UnresolvedMappingWarningThrottle[key] = nowUtc; CapUnresolvedMappingWarningThrottle(); return true; } } internal static void ResetUnresolvedMappingWarningThrottleForTests() { lock (UnresolvedMappingWarningThrottleLock) { UnresolvedMappingWarningThrottle.Clear(); UnresolvedMappingWarningNextPruneUtc = DateTime.MinValue; } } internal static int GetUnresolvedMappingWarningThrottleCountForTests() { lock (UnresolvedMappingWarningThrottleLock) { return UnresolvedMappingWarningThrottle.Count; } } private static void PruneUnresolvedMappingWarningThrottle(DateTime nowUtc) { if (UnresolvedMappingWarningThrottle.Count == 0) { return; } DateTime dateTime = nowUtc - UnresolvedMappingWarningPruneAge; List list = null; foreach (KeyValuePair item in UnresolvedMappingWarningThrottle) { if (!(item.Value >= dateTime)) { if (list == null) { list = new List(); } list.Add(item.Key); } } if (list != null) { for (int i = 0; i < list.Count; i++) { UnresolvedMappingWarningThrottle.Remove(list[i]); } } } private static void CapUnresolvedMappingWarningThrottle() { if (UnresolvedMappingWarningThrottle.Count <= 2048) { return; } string text = string.Empty; DateTime dateTime = DateTime.MaxValue; foreach (KeyValuePair item in UnresolvedMappingWarningThrottle) { if (!(item.Value >= dateTime)) { dateTime = item.Value; text = item.Key; } } if (!string.IsNullOrEmpty(text)) { UnresolvedMappingWarningThrottle.Remove(text); } } private static string BuildUnresolvedMappingWarningKey(int actorNumber, string issueText) { string text = (string.IsNullOrWhiteSpace(issueText) ? "unknown_issue" : issueText.Trim().ToLowerInvariant()); return actorNumber + "|" + text; } } public readonly struct RPCInfo { public readonly ulong SteamId64; public readonly string SteamIdString; public readonly bool IsLocalLoopback; public RPCInfo(ulong steamId64, string steamIdString, bool isLocalLoopback = false) { SteamId64 = steamId64; SteamIdString = (string.IsNullOrWhiteSpace(steamIdString) ? steamId64.ToString() : steamIdString); IsLocalLoopback = isLocalLoopback; } public RPCInfo(ulong steamId64) : this(steamId64, steamId64.ToString()) { } public RPCInfo(CSteamID sid) : this(sid.m_SteamID, ((object)(CSteamID)(ref sid)).ToString()) { }//IL_0001: Unknown result type (might be due to invalid IL or missing references) } internal static class RpcInfoParameterTypeHelper { private static readonly HashSet CompatibleTypeNames = new HashSet(StringComparer.Ordinal) { "NetworkingLibrary.Modules.RPCInfo" }; internal static bool IsRpcInfoParameterType(Type parameterType) { if (parameterType == typeof(RPCInfo)) { return true; } string fullName = parameterType.FullName; if (fullName != null) { return CompatibleTypeNames.Contains(fullName); } return false; } } public class SteamCallbackPump : MonoBehaviour { private const string LogSource = "SteamCallbackPump"; private const float ErrorLogCooldownSeconds = 2f; internal static Func IsSteamReady = ProbeSteamReady; internal static Func TimeProvider = () => Time.unscaledTime; internal static Action RunCallbacks = SteamAPI.RunCallbacks; private string? lastSkipReason; private bool runCallbacksFaulted; public static bool CallbackPumpingEnabled { get; private set; } public static void EnablePumping() { CallbackPumpingEnabled = true; } public static void DisablePumping() { CallbackPumpingEnabled = false; } public static void DisableAndDestroyExisting() { DisablePumping(); SteamCallbackPump[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); if (array == null || array.Length == 0) { return; } foreach (SteamCallbackPump steamCallbackPump in array) { if ((Object)(object)steamCallbackPump == (Object)null || (Object)(object)((Component)steamCallbackPump).gameObject == (Object)null) { continue; } bool flag = ((Object)((Component)steamCallbackPump).gameObject).name == "SteamCallbackPump"; if (flag) { Component[] components = ((Component)steamCallbackPump).gameObject.GetComponents(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null) && !(val is Transform) && !((Object)(object)val == (Object)(object)steamCallbackPump)) { flag = false; break; } } } if (flag) { if (Application.isPlaying) { Object.Destroy((Object)(object)((Component)steamCallbackPump).gameObject); } else { Object.DestroyImmediate((Object)(object)((Component)steamCallbackPump).gameObject); } } else if (Application.isPlaying) { Object.Destroy((Object)(object)steamCallbackPump); } else { Object.DestroyImmediate((Object)(object)steamCallbackPump); } } } private void Update() { if (!CallbackPumpingEnabled) { return; } if (!TryIsSteamReady(out var isReady) || !isReady) { LogSkipReasonOnce("Steam callbacks skipped because Steam is unavailable or uninitialized."); return; } lastSkipReason = null; try { RunCallbacks(); if (runCallbacksFaulted) { runCallbacksFaulted = false; } } catch (Exception ex) { runCallbacksFaulted = true; LogExceptionWithCooldown("RunCallbacks", "SteamAPI.RunCallbacks failed. Exception: " + ex.GetType().Name + ": " + ex.Message); } } private static bool ProbeSteamReady() { try { return NetworkingServiceFactory.IsSteamClientRunning() && NetworkingServiceFactory.IsSteamApiInitialized(); } catch (Exception ex) { LogExceptionWithCooldown("ProbeSteamReady", "ProbeSteamReady failed while checking Steam readiness. Exception: " + ex.GetType().Name + ": " + ex.Message); return false; } } private bool TryIsSteamReady(out bool isReady) { isReady = false; try { isReady = IsSteamReady(); return true; } catch (Exception ex) { LogExceptionWithCooldown($"TryIsSteamReady:{((object)this).GetHashCode()}", "TryIsSteamReady failed while invoking IsSteamReady hook. Exception: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void LogExceptionWithCooldown(string key, string message) { if (!NetLog.TryEnterCooldown("SteamCallbackPump." + key, 2.0, ResolveTime())) { return; } try { NetLog.Error("SteamCallbackPump", message); } catch { string text = "[SteamCallbackPump] " + message; try { Debug.LogError((object)text); } catch { Trace.TraceError(text); } } } private static double ResolveTime() { try { float num = TimeProvider(); if (!float.IsNaN(num) && !float.IsInfinity(num)) { return num; } } catch { } return DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; } private void LogSkipReasonOnce(string message) { if (!(lastSkipReason == message)) { lastSkipReason = message; runCallbacksFaulted = false; NetLog.Info("SteamCallbackPump", message); } } } public static class StringId { private const uint FnvOffset = 2166136261u; private const uint FnvPrime = 16777619u; public static uint Map(string s) { if (s == null) { throw new ArgumentNullException("s"); } uint num = 2166136261u; foreach (char c in s) { if (c > '\u007f') { return MapUtf8(s); } num ^= c; num *= 16777619; } return num; } private static uint MapUtf8(string s) { uint num = 2166136261u; Encoding uTF = Encoding.UTF8; int byteCount = uTF.GetByteCount(s); byte[] array = null; Span span = ((byteCount > 512) ? ((Span)(array = ArrayPool.Shared.Rent(byteCount))) : stackalloc byte[byteCount]); Span bytes = span; try { int bytes2 = uTF.GetBytes(s.AsSpan(), bytes); for (int i = 0; i < bytes2; i++) { num ^= bytes[i]; num *= 16777619; } return num; } finally { if (array != null) { ArrayPool.Shared.Return(array, clearArray: true); } } } } public class UnityMainThreadDispatcher : MonoBehaviour { internal enum QueueOverflowBehavior { RejectNewWork, DropOldest, Coalesce } private sealed class DelayedEnqueueWork { private readonly UnityMainThreadDispatcher dispatcher; private readonly Action action; private readonly float delaySeconds; internal DelayedEnqueueWork(UnityMainThreadDispatcher dispatcher, Action action, float delaySeconds) { this.dispatcher = dispatcher; this.action = action; this.delaySeconds = delaySeconds; } internal void Invoke() { if ((Object)(object)dispatcher == (Object)null || !Object.op_Implicit((Object)(object)dispatcher)) { NotifyDelayedEnqueueRejected(CreateDelayedDispatchUnavailableRejection("dispatcher unavailable during delayed enqueue dispatch: dispatcher was destroyed")); return; } if (!((Behaviour)dispatcher).isActiveAndEnabled) { NotifyDelayedEnqueueRejected(CreateDelayedDispatchUnavailableRejection("dispatcher unavailable during delayed enqueue dispatch: dispatcher inactive or disabled")); return; } try { ((MonoBehaviour)dispatcher).StartCoroutine(dispatcher.EnqueueDelayed(action, delaySeconds)); } catch (Exception) { NotifyDelayedEnqueueRejected(CreateDelayedDispatchUnavailableRejection("dispatcher unavailable during delayed enqueue dispatch")); } } public override bool Equals(object? obj) { if (!(obj is DelayedEnqueueWork delayedEnqueueWork)) { return false; } if (dispatcher == delayedEnqueueWork.dispatcher && action.Method == delayedEnqueueWork.action.Method && object.Equals(action.Target, delayedEnqueueWork.action.Target)) { return delaySeconds.Equals(delayedEnqueueWork.delaySeconds); } return false; } public override int GetHashCode() { return HashCode.Combine(dispatcher, action.Method, action.Target, delaySeconds); } } private readonly struct EnqueueRejectionInfo { internal readonly string Mode; internal readonly QueueOverflowBehavior Behavior; internal readonly int QueueDepth; internal readonly int MaxDepth; internal readonly string Reason; private EnqueueRejectionInfo(string mode, QueueOverflowBehavior behavior, int queueDepth, int maxDepth, string reason) { Mode = mode; Behavior = behavior; QueueDepth = queueDepth; MaxDepth = maxDepth; Reason = reason; } internal static EnqueueRejectionInfo Create(bool delayed, QueueOverflowBehavior behavior, int queueDepth, int maxDepth, string reason) { return new EnqueueRejectionInfo(delayed ? "delayed" : "immediate", behavior, queueDepth, maxDepth, reason); } } internal readonly struct DelayedEnqueueObservation { internal readonly QueueOverflowBehavior Behavior; internal readonly int QueueDepth; internal readonly int MaxDepth; internal readonly string Reason; internal readonly string Message; internal DelayedEnqueueObservation(QueueOverflowBehavior behavior, int queueDepth, int maxDepth, string reason, string message) { Behavior = behavior; QueueDepth = queueDepth; MaxDepth = maxDepth; Reason = reason; Message = message; } } internal static class TestHooks { internal static int CreateRequestQueuedForTests => Volatile.Read(ref createRequestQueued); internal static int CurrentMainThreadIdForTests => Volatile.Read(ref mainThreadId); internal static int QueueDepthForTests { get { lock (queue) { return queue.Count; } } } internal static int QueueHighWaterMarkForTests => Volatile.Read(ref queueHighWaterMark); internal static int DroppedEnqueueCountForTests => Volatile.Read(ref droppedEnqueueCount); internal static int RejectedEnqueueCountForTests => Volatile.Read(ref rejectedEnqueueCount); internal static int CoalescedEnqueueCountForTests => Volatile.Read(ref coalescedEnqueueCount); internal static int SuppressedOverflowWarningsForTests => Volatile.Read(ref suppressedOverflowWarnings); internal static int EmittedOverflowWarningCountForTests => Volatile.Read(ref emittedOverflowWarningCountForTests); internal static string? LastOverflowWarningMessageForTests => Volatile.Read(ref lastOverflowWarningMessageForTests); internal static long LastOverflowWarningTicksForTests { get { return Interlocked.Read(ref lastOverflowWarningTicks); } set { Interlocked.Exchange(ref lastOverflowWarningTicks, value); } } internal static int MaxQueueDepthForTests => ResolveMaxQueueDepth(); internal static int MaxActionsPerFrameForTests => ResolveMaxActionsPerFrame(); internal static double MaxFrameWorkMillisecondsForTests => ResolveMaxFrameWorkMilliseconds(); internal static int BacklogWarningFrameThresholdForTests => ResolveBacklogWarningFrameThreshold(); internal static int ConsecutiveBacklogFramesForTests => Volatile.Read(ref consecutiveBacklogFrames); internal static int SuppressedBacklogWarningsForTests => Volatile.Read(ref suppressedBacklogWarnings); internal static int EmittedBacklogWarningCountForTests => Volatile.Read(ref emittedBacklogWarningCountForTests); internal static long LastBacklogWarningTicksForTests { get { return Interlocked.Read(ref lastBacklogWarningTicks); } set { Interlocked.Exchange(ref lastBacklogWarningTicks, value); } } internal static int SuppressedActionErrorLogsForTests => Volatile.Read(ref suppressedActionErrorLogs); internal static int EmittedActionErrorLogCountForTests => Volatile.Read(ref emittedActionErrorLogCountForTests); internal static string? LastActionErrorMessageForTests => Volatile.Read(ref lastActionErrorMessageForTests); internal static long LastActionErrorLogTicksForTests { get { return Interlocked.Read(ref lastActionErrorLogTicks); } set { Interlocked.Exchange(ref lastActionErrorLogTicks, value); } } internal static void ResetForTests() { lock (instanceLock) { instance = null; createRequestQueued = 0; mainThreadId = -1; lock (queue) { while (queue.Count > 0) { queue.Dequeue(); } } instanceReady.Reset(); queueHighWaterMark = 0; droppedEnqueueCount = 0; rejectedEnqueueCount = 0; coalescedEnqueueCount = 0; suppressedOverflowWarnings = 0; emittedOverflowWarningCountForTests = 0; lastOverflowWarningMessageForTests = null; lastOverflowWarningTicks = 0L; suppressedOverflowLoggerFailures = 0; lastOverflowLoggerFailureTicks = 0L; consecutiveBacklogFrames = 0; suppressedBacklogWarnings = 0; emittedBacklogWarningCountForTests = 0; lastBacklogWarningTicks = 0L; suppressedActionErrorLogs = 0; emittedActionErrorLogCountForTests = 0; lastActionErrorMessageForTests = null; lastActionErrorLogTicks = 0L; CreateInstanceOnMainThreadFactory = CreateOrFindDispatcherOnMainThread; BackgroundThreadInstanceWaitTimeout = defaultBackgroundThreadWaitTimeout; MaxQueueDepthProvider = null; QueueOverflowBehaviorProvider = null; MaxActionsPerFrameProvider = null; MaxFrameWorkMillisecondsProvider = null; BacklogWarningFrameThresholdProvider = null; DelayedEnqueueRejectedObserver = null; } } internal static void SetMainThreadIdForTests(int id) { mainThreadId = id; } } private const int defaultMaxQueueDepth = 2048; internal const int defaultMaxActionsPerFrame = 128; internal const double defaultMaxFrameWorkMilliseconds = 4.0; internal const int defaultBacklogWarningFrameThreshold = 120; private static readonly TimeSpan overflowWarningCooldown = TimeSpan.FromSeconds(5.0); private static UnityMainThreadDispatcher? instance; private static readonly object instanceLock = new object(); private static readonly Queue queue = new Queue(); private static int mainThreadId = -1; private static int createRequestQueued; private static int queueHighWaterMark; private static int droppedEnqueueCount; private static int rejectedEnqueueCount; private static int coalescedEnqueueCount; private static int suppressedOverflowWarnings; private static int emittedOverflowWarningCountForTests; private static string? lastOverflowWarningMessageForTests; private static long lastOverflowWarningTicks; private static int suppressedOverflowLoggerFailures; private static long lastOverflowLoggerFailureTicks; private static int consecutiveBacklogFrames; private static int suppressedBacklogWarnings; private static int emittedBacklogWarningCountForTests; private static long lastBacklogWarningTicks; private static int suppressedActionErrorLogs; private static int emittedActionErrorLogCountForTests; private static string? lastActionErrorMessageForTests; private static long lastActionErrorLogTicks; private static readonly ManualResetEventSlim instanceReady = new ManualResetEventSlim(initialState: false); private static readonly TimeSpan defaultBackgroundThreadWaitTimeout = TimeSpan.FromSeconds(2.0); internal static Func CreateInstanceOnMainThreadFactory = CreateOrFindDispatcherOnMainThread; internal static TimeSpan BackgroundThreadInstanceWaitTimeout = defaultBackgroundThreadWaitTimeout; internal static Func? MaxQueueDepthProvider; internal static Func? QueueOverflowBehaviorProvider; internal static Func? MaxActionsPerFrameProvider; internal static Func? MaxFrameWorkMillisecondsProvider; internal static Func? BacklogWarningFrameThresholdProvider; internal static Action? DelayedEnqueueRejectedObserver; public static UnityMainThreadDispatcher Instance() { if (instance != null) { return instance; } if (IsMainThread() || CanCaptureMainThreadFromCurrentContext()) { CaptureMainThreadIfUnknown(); return EnsureInstanceOnMainThread(); } QueueCreateRequest(); if (WaitForInstanceFromBackgroundThread() && instance != null) { return instance; } if (instance != null) { return instance; } throw new InvalidOperationException("UnityMainThreadDispatcher.Instance() was called from a non-main thread before the main thread could create the dispatcher."); } private static bool WaitForInstanceFromBackgroundThread() { TimeSpan backgroundThreadInstanceWaitTimeout = BackgroundThreadInstanceWaitTimeout; if (backgroundThreadInstanceWaitTimeout <= TimeSpan.Zero) { backgroundThreadInstanceWaitTimeout = defaultBackgroundThreadWaitTimeout; } Stopwatch stopwatch = Stopwatch.StartNew(); TimeSpan timeSpan = TimeSpan.FromMilliseconds(10.0); TimeSpan timeSpan2 = TimeSpan.FromMilliseconds(200.0); while (stopwatch.Elapsed < backgroundThreadInstanceWaitTimeout) { TimeSpan timeSpan3 = backgroundThreadInstanceWaitTimeout - stopwatch.Elapsed; if (timeSpan3 <= TimeSpan.Zero) { break; } TimeSpan timeout = ((timeSpan <= timeSpan3) ? timeSpan : timeSpan3); if (instanceReady.Wait(timeout)) { return true; } timeSpan = ((timeSpan < timeSpan2) ? TimeSpan.FromMilliseconds(Math.Min(timeSpan.TotalMilliseconds * 2.0, timeSpan2.TotalMilliseconds)) : timeSpan2); } return false; } private static bool CanCaptureMainThreadFromCurrentContext() { if (Volatile.Read(ref mainThreadId) != -1) { return false; } return string.Equals(SynchronizationContext.Current?.GetType().FullName, "UnityEngine.UnitySynchronizationContext", StringComparison.Ordinal); } private static void QueueCreateRequest() { Interlocked.Exchange(ref createRequestQueued, 1); } private static bool IsMainThread() { int num = Volatile.Read(ref mainThreadId); if (num != -1) { return num == Thread.CurrentThread.ManagedThreadId; } return false; } private static void CaptureMainThreadIfUnknown() { if (Volatile.Read(ref mainThreadId) == -1) { Interlocked.CompareExchange(ref mainThreadId, Thread.CurrentThread.ManagedThreadId, -1); } } private static UnityMainThreadDispatcher EnsureInstanceOnMainThread() { if (!IsMainThread()) { throw new InvalidOperationException("Dispatcher creation must run on the Unity main thread."); } lock (instanceLock) { if (instance != null) { return instance; } instance = CreateInstanceOnMainThreadFactory(); instanceReady.Set(); return instance; } } private static UnityMainThreadDispatcher CreateOrFindDispatcherOnMainThread() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown GameObject val = GameObject.Find("UnityMainThreadDispatcher"); if ((Object)(object)val == (Object)null) { val = new GameObject("UnityMainThreadDispatcher"); Object.DontDestroyOnLoad((Object)(object)val); return val.AddComponent(); } UnityMainThreadDispatcher result = val.GetComponent() ?? val.AddComponent(); Object.DontDestroyOnLoad((Object)(object)val); return result; } internal static void ProcessPendingMainThreadWork() { if (!IsMainThread()) { if (!CanCaptureMainThreadFromCurrentContext()) { return; } CaptureMainThreadIfUnknown(); if (!IsMainThread()) { return; } } if (Interlocked.Exchange(ref createRequestQueued, 0) != 1 || instance != null) { return; } try { EnsureInstanceOnMainThread(); } catch { if (instance == null) { QueueCreateRequest(); } throw; } } [RuntimeInitializeOnLoadMethod(/*Could not decode attribute arguments.*/)] private static void InitializeMainThreadContext() { CaptureMainThreadIfUnknown(); } private void Awake() { CaptureMainThreadIfUnknown(); lock (instanceLock) { if (instance == null) { instance = this; instanceReady.Set(); Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } else { if (!((Object)(object)instance != (Object)(object)this)) { return; } bool flag = true; Component[] components = ((Component)this).gameObject.GetComponents(); foreach (Component val in components) { if (!((Object)(object)val == (Object)null) && !(val is Transform) && !((Object)(object)val == (Object)(object)this)) { flag = false; break; } } Object val2 = (Object)(object)(flag ? ((UnityMainThreadDispatcher)(object)((Component)this).gameObject) : this); if (Application.isPlaying) { Object.Destroy(val2); } else { Object.DestroyImmediate(val2); } } } } private void OnDestroy() { lock (instanceLock) { if (instance == this) { instance = null; instanceReady.Reset(); } } } public bool TryEnqueue(Action a, float delaySeconds = 0f) { if (a == null) { throw new ArgumentNullException("a"); } EnqueueRejectionInfo? rejection; if (delaySeconds <= 0f) { return TryEnqueueBounded(a, delayed: false, out rejection); } return TryEnqueueBounded(CreateDelayedEnqueueAction(a, delaySeconds), delayed: true, out rejection); } public void Enqueue(Action a, float delaySeconds = 0f) { Enqueue(a, delaySeconds, throwOnRejection: false); } public void Enqueue(Action a, float delaySeconds, bool throwOnRejection) { if (a == null) { throw new ArgumentNullException("a"); } if (!((delaySeconds > 0f) ? TryEnqueueBounded(CreateDelayedEnqueueAction(a, delaySeconds), delayed: true, out var rejection) : TryEnqueueBounded(a, delayed: false, out rejection)) && rejection.HasValue) { string message = FormatEnqueueRejectionMessage(rejection.Value); if (throwOnRejection) { throw new InvalidOperationException(message); } } } private Action CreateDelayedEnqueueAction(Action action, float delaySeconds) { DelayedEnqueueWork @object = new DelayedEnqueueWork(this, action, delaySeconds); return @object.Invoke; } private IEnumerator EnqueueDelayed(Action a, float d) { if (a == null) { throw new ArgumentNullException("a"); } yield return (object)new WaitForSeconds(d); if (!TryEnqueueBounded(a, delayed: true, out var rejection) && rejection.HasValue) { NotifyDelayedEnqueueRejected(rejection.Value); } } private static EnqueueRejectionInfo CreateDelayedDispatchUnavailableRejection(string reason) { QueueOverflowBehavior behavior = ResolveQueueOverflowBehavior(); int maxDepth = ResolveMaxQueueDepth(); lock (queue) { return EnqueueRejectionInfo.Create(delayed: true, behavior, queue.Count, maxDepth, reason); } } private static bool TryEnqueueBounded(Action action, bool delayed, out EnqueueRejectionInfo? rejection) { int num = ResolveMaxQueueDepth(); QueueOverflowBehavior queueOverflowBehavior = ResolveQueueOverflowBehavior(); rejection = null; lock (queue) { if (queue.Count < num) { queue.Enqueue(action); if (queue.Count > queueHighWaterMark) { queueHighWaterMark = queue.Count; } return true; } switch (queueOverflowBehavior) { case QueueOverflowBehavior.DropOldest: queue.Dequeue(); droppedEnqueueCount++; queue.Enqueue(action); if (queue.Count > queueHighWaterMark) { queueHighWaterMark = queue.Count; } EmitOverflowWarning(string.Format("UnityMainThreadDispatcher queue depth limit ({0}) reached; dropping oldest pending action to enqueue new {1} work.", num, delayed ? "delayed" : "immediate")); return true; case QueueOverflowBehavior.Coalesce: if (HasEquivalentPendingAction(action)) { coalescedEnqueueCount++; rejection = EnqueueRejectionInfo.Create(delayed, queueOverflowBehavior, queue.Count, num, "coalesced duplicate action"); EmitOverflowWarning(FormatEnqueueRejectionMessage(rejection.Value)); return false; } rejectedEnqueueCount++; rejection = EnqueueRejectionInfo.Create(delayed, queueOverflowBehavior, queue.Count, num, "coalesce fallback rejected non-duplicate action"); EmitOverflowWarning(FormatEnqueueRejectionMessage(rejection.Value)); return false; default: rejectedEnqueueCount++; rejection = EnqueueRejectionInfo.Create(delayed, queueOverflowBehavior, queue.Count, num, "rejected new action"); EmitOverflowWarning(FormatEnqueueRejectionMessage(rejection.Value)); return false; } } } private static string FormatEnqueueRejectionMessage(EnqueueRejectionInfo rejection) { return $"UnityMainThreadDispatcher enqueue rejected {rejection.Mode} work ({rejection.Reason}); overflowBehavior={rejection.Behavior}, queueDepth={rejection.QueueDepth}, maxDepth={rejection.MaxDepth}."; } private static void NotifyDelayedEnqueueRejected(EnqueueRejectionInfo rejection) { Action delayedEnqueueRejectedObserver = DelayedEnqueueRejectedObserver; if (delayedEnqueueRejectedObserver == null) { return; } string text = FormatEnqueueRejectionMessage(rejection); try { delayedEnqueueRejectedObserver(new DelayedEnqueueObservation(rejection.Behavior, rejection.QueueDepth, rejection.MaxDepth, rejection.Reason, text)); } catch (Exception ex) { LogWarningOrTrace("[UnityMainThreadDispatcher] Delayed enqueue rejection observer failed. Exception: " + ex.GetType().Name + ": " + ex.Message + ". Original message: " + text); } } private static bool HasEquivalentPendingAction(Action action) { foreach (Action item in queue) { if (item != null && item.Method == action.Method && object.Equals(item.Target, action.Target)) { return true; } } return false; } private static int ResolveMaxQueueDepth() { int? num = TryResolveProvider(MaxQueueDepthProvider, "MaxQueueDepthProvider"); if (num.HasValue && num.Value > 0) { return num.Value; } return 2048; } private static QueueOverflowBehavior ResolveQueueOverflowBehavior() { if (QueueOverflowBehaviorProvider == null) { return QueueOverflowBehavior.DropOldest; } try { return QueueOverflowBehaviorProvider(); } catch (Exception ex) { LogWarningOrTrace("[UnityMainThreadDispatcher] QueueOverflowBehaviorProvider failed. Exception: " + ex.GetType().Name + ": " + ex.Message + "."); return QueueOverflowBehavior.DropOldest; } } private static int? TryResolveProvider(Func? provider, string name) { if (provider == null) { return null; } try { return provider(); } catch (Exception ex) { LogWarningOrTrace("[UnityMainThreadDispatcher] " + name + " failed. Exception: " + ex.GetType().Name + ": " + ex.Message + "."); return null; } } private static double? TryResolveProvider(Func? provider, string name) { if (provider == null) { return null; } try { return provider(); } catch (Exception ex) { LogWarningOrTrace("[UnityMainThreadDispatcher] " + name + " failed. Exception: " + ex.GetType().Name + ": " + ex.Message + "."); return null; } } private static void EmitOverflowWarning(string message) { long num = 0L; long num2; do { num = DateTime.UtcNow.Ticks; num2 = Interlocked.Read(ref lastOverflowWarningTicks); if (IsWarningCooldownActive(num, num2)) { Interlocked.Increment(ref suppressedOverflowWarnings); return; } } while (Interlocked.CompareExchange(ref lastOverflowWarningTicks, num, num2) != num2); try { int num3 = Interlocked.Exchange(ref suppressedOverflowWarnings, 0); if (num3 > 0) { message = $"{message} Suppressed {num3} similar warnings."; } Interlocked.Increment(ref emittedOverflowWarningCountForTests); Volatile.Write(ref lastOverflowWarningMessageForTests, message); ManualLogSource logger = Net.Logger; if (logger != null) { logger.LogWarning((object)message); } } catch (Exception ex) { EmitOverflowLoggerFailureWarning(ex, message, num); } } private static void EmitOverflowLoggerFailureWarning(Exception ex, string originalMessage, long nowTicks) { long num; do { num = Interlocked.Read(ref lastOverflowLoggerFailureTicks); if (IsWarningCooldownActive(nowTicks, num)) { Interlocked.Increment(ref suppressedOverflowLoggerFailures); return; } } while (Interlocked.CompareExchange(ref lastOverflowLoggerFailureTicks, nowTicks, num) != num); int num2 = Interlocked.Exchange(ref suppressedOverflowLoggerFailures, 0); string text = ((num2 > 0) ? $" Suppressed {num2} similar logger failures." : string.Empty); LogWarningOrTrace("[UnityMainThreadDispatcher] Failed to write overflow warning. Exception: " + ex.GetType().Name + ": " + ex.Message + ". Original message: " + originalMessage + "." + text); } private void Update() { int num = ResolveMaxActionsPerFrame(); double num2 = ResolveMaxFrameWorkMilliseconds(); Stopwatch stopwatch = ((num2 > 0.0) ? Stopwatch.StartNew() : null); int num3 = 0; do { Action action = null; lock (queue) { if (queue.Count > 0) { action = queue.Dequeue(); goto IL_0063; } } break; IL_0063: try { action?.Invoke(); } catch (Exception ex) { EmitActionErrorLog(ex); } num3++; } while ((num <= 0 || num3 < num) && (stopwatch == null || !(stopwatch.Elapsed.TotalMilliseconds >= num2))); TrackBacklogWarning(); } private static int ResolveMaxActionsPerFrame() { int? num = TryResolveProvider(MaxActionsPerFrameProvider, "MaxActionsPerFrameProvider"); if (num.HasValue && num.Value > 0) { return num.Value; } return 128; } private static double ResolveMaxFrameWorkMilliseconds() { double? num = TryResolveProvider(MaxFrameWorkMillisecondsProvider, "MaxFrameWorkMillisecondsProvider"); if (num.HasValue && num.Value > 0.0) { return num.Value; } return 4.0; } private static int ResolveBacklogWarningFrameThreshold() { int? num = TryResolveProvider(BacklogWarningFrameThresholdProvider, "BacklogWarningFrameThresholdProvider"); if (num.HasValue && num.Value > 0) { return num.Value; } return 120; } private static void TrackBacklogWarning() { int num = 0; lock (queue) { num = queue.Count; } if (num <= 0) { Interlocked.Exchange(ref consecutiveBacklogFrames, 0); return; } int num2 = Interlocked.Increment(ref consecutiveBacklogFrames); int num3 = ResolveBacklogWarningFrameThreshold(); if (num3 > 0 && num2 >= num3) { EmitBacklogWarning($"UnityMainThreadDispatcher backlog persisted for {num2} frames with {num} actions still queued."); } } private static void EmitBacklogWarning(string message) { long ticks; long num; do { ticks = DateTime.UtcNow.Ticks; num = Interlocked.Read(ref lastBacklogWarningTicks); if (IsWarningCooldownActive(ticks, num)) { Interlocked.Increment(ref suppressedBacklogWarnings); return; } } while (Interlocked.CompareExchange(ref lastBacklogWarningTicks, ticks, num) != num); try { int num2 = Interlocked.Exchange(ref suppressedBacklogWarnings, 0); if (num2 > 0) { message = $"{message} Suppressed {num2} similar warnings."; } Interlocked.Increment(ref emittedBacklogWarningCountForTests); ManualLogSource logger = Net.Logger; if (logger != null) { logger.LogWarning((object)message); } } catch (Exception ex) { LogWarningOrTrace("[UnityMainThreadDispatcher] Failed to write backlog warning. Exception: " + ex.GetType().Name + ": " + ex.Message + ". Original message: " + message); } } private static void EmitActionErrorLog(Exception ex) { long ticks; long num; do { ticks = DateTime.UtcNow.Ticks; num = Interlocked.Read(ref lastActionErrorLogTicks); if (IsWarningCooldownActive(ticks, num)) { Interlocked.Increment(ref suppressedActionErrorLogs); return; } } while (Interlocked.CompareExchange(ref lastActionErrorLogTicks, ticks, num) != num); int num2 = Interlocked.Exchange(ref suppressedActionErrorLogs, 0); string text = $"Dispatcher action error: {ex}"; if (num2 > 0) { text = $"{text} Suppressed {num2} similar action exceptions."; } Interlocked.Increment(ref emittedActionErrorLogCountForTests); Volatile.Write(ref lastActionErrorMessageForTests, text); LogErrorOrTrace(text); } private static void LogWarningOrTrace(string message) { try { Debug.LogWarning((object)message); } catch { Trace.TraceWarning(message); } } private static bool IsWarningCooldownActive(long nowTicks, long previousTicks) { if (previousTicks != 0L && nowTicks >= previousTicks) { return new TimeSpan(nowTicks - previousTicks) < overflowWarningCooldown; } return false; } private static void LogErrorOrTrace(string message) { try { Debug.LogError((object)message); } catch { Trace.TraceError(message); } } } } namespace NetworkingLibrary.Features { public static class FileManager { private const string VersionSection = "Version"; private const string VersionKey = "ConfigSchemaVersion"; private const string LegacyVersionKey = "Current Version"; private const string PluginVersionKey = "PluginVersion"; private const string CurrentConfigSchemaVersion = "1"; private const string SchemaVersionInfo = "Tracks config schema version for non-destructive migrations."; private const string PluginVersionInfo = "Tracks plugin release version."; private const string DaModsFolderName = "DAa Mods"; private const string ConfigFileName = "config.cfg"; private const int ConfigSaveRetryCount = 4; private const int ConfigIoRetryDelayMilliseconds = 15; private static readonly object ConfigMigrationLock = new object(); private static readonly ConcurrentDictionary> RemoveMethodCache = new ConcurrentDictionary>(); private static readonly ConcurrentDictionary> OrphanedEntriesPropertyCache = new ConcurrentDictionary>(); internal static ConfigEntry BindConfig(string header, string features, T value, string? info = "") { return Net.Instance.config.Bind(header, features, value, info); } internal static void InitializeConfig() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, "DAa Mods", "NetworkingLibrary"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } Net.Instance.config = new ConfigFile(BuildConfigPath(text), true); MigrateConfigIfNeeded(Net.Instance.config, "1"); DefineConfig(); Net.Logger.LogInfo((object)"Config initialization complete."); } internal static string BuildConfigPath(string configFolderPath) { return Path.Combine(configFolderPath, "config.cfg"); } internal static void DefineConfig() { BindSchemaVersion(Net.Instance.config, "1"); BindPluginVersion(Net.Instance.config, "1.0.9"); } internal static void MigrateConfigIfNeeded(ConfigFile config, string currentVersion) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown lock (ConfigMigrationLock) { bool saveOnConfigSet = config.SaveOnConfigSet; config.SaveOnConfigSet = false; try { ConfigEntry val = BindSchemaVersion(config, string.Empty); string legacyVersion; bool flag = TryReadLegacyVersion(config, out legacyVersion); ConfigDefinition legacyDefinition = new ConfigDefinition("Version", "Current Version"); bool flag2 = flag || HasLegacyVersionInConfig(config, legacyDefinition); string storedVersion = GetStoredVersion(val.Value, legacyVersion); if (TryParseSchemaVersion(storedVersion, out var schemaVersion) && TryParseSchemaVersion(currentVersion, out var schemaVersion2) && schemaVersion > schemaVersion2) { ManualLogSource logger = Net.Logger; if (logger != null) { logger.LogWarning((object)("Stored config schema version '" + storedVersion + "' is newer than supported schema '" + currentVersion + "'. Migration skipped to avoid destructive downgrade.")); } return; } bool flag3 = string.IsNullOrWhiteSpace(val.Value) && flag; if (storedVersion == currentVersion && !flag3 && !flag2) { return; } bool flag4; if (string.IsNullOrWhiteSpace(storedVersion)) { if (!TryParseSchemaVersion(currentVersion, out var _)) { throw new InvalidOperationException("Current config schema version '" + currentVersion + "' is not a valid schema identifier."); } flag4 = true; } else { flag4 = MigrateConfig(val, storedVersion, currentVersion, legacyVersion); } if (!flag4) { return; } val.Value = currentVersion; Exception removeException = null; Exception orphanedEntriesException = null; bool flag5 = !flag2 || DropLegacyVersionFromConfig(config, legacyDefinition, out removeException, out orphanedEntriesException); SaveConfig(config); Exception exception = null; bool flag6 = (!flag && flag5) || ClearLegacyVersionInFile(config, out exception); if (!flag5 && !flag6) { ManualLogSource logger2 = Net.Logger; if (logger2 != null) { logger2.LogWarning((object)("Legacy config key 'Current Version' could not be removed during migration." + BuildCleanupFailureContext(removeException, orphanedEntriesException, exception))); } } } finally { config.SaveOnConfigSet = saveOnConfigSet; } } } private static string GetStoredVersion(string schemaVersion, string legacyVersion) { if (!string.IsNullOrWhiteSpace(schemaVersion)) { return schemaVersion; } return legacyVersion; } private static bool MigrateConfig(ConfigEntry schemaVersionEntry, string previousVersion, string currentVersion, string legacyVersion) { if (!TryParseSchemaVersion(currentVersion, out var schemaVersion)) { throw new InvalidOperationException("Current config schema version '" + currentVersion + "' is not a valid schema identifier."); } string text = (string.IsNullOrWhiteSpace(schemaVersionEntry.Value) ? legacyVersion : previousVersion); if (!TryParseSchemaVersion(text, out var schemaVersion2) || schemaVersion2 < 0) { schemaVersion2 = 0; if (!string.IsNullOrWhiteSpace(text)) { ManualLogSource logger = Net.Logger; if (logger != null) { logger.LogWarning((object)("Invalid source schema version '" + text + "'. Starting migration at schema 0.")); } } } else if (schemaVersion2 > schemaVersion) { ManualLogSource logger2 = Net.Logger; if (logger2 != null) { logger2.LogWarning((object)$"Config schema version downgrade detected ({schemaVersion2} -> {schemaVersion}). Skipping downgrade migrations and retaining existing schema data."); } return false; } for (int i = schemaVersion2; i < schemaVersion; i++) { if (i == 0) { Migrate_0_to_1(schemaVersionEntry, legacyVersion); continue; } throw new InvalidOperationException($"No migration path exists from schema {i} to {i + 1}."); } return true; } private static bool TryParseSchemaVersion(string version, out int schemaVersion) { schemaVersion = 0; if (string.IsNullOrWhiteSpace(version) || !int.TryParse(version, NumberStyles.Integer, CultureInfo.InvariantCulture, out schemaVersion)) { return false; } return schemaVersion >= 0; } private static void Migrate_0_to_1(ConfigEntry schemaVersionEntry, string legacyVersion) { if (string.IsNullOrWhiteSpace(schemaVersionEntry.Value) && !string.IsNullOrWhiteSpace(legacyVersion)) { schemaVersionEntry.Value = legacyVersion; } } private static ConfigEntry BindSchemaVersion(ConfigFile config, string value) { return config.Bind("Version", "ConfigSchemaVersion", value, "Tracks config schema version for non-destructive migrations."); } private static bool TryReadLegacyVersion(ConfigFile config, out string legacyVersion) { legacyVersion = string.Empty; string configFilePath = config.ConfigFilePath; if (!File.Exists(configFilePath)) { return false; } bool flag = false; string[] array = ReadAllLinesWithRetry(configFilePath); foreach (string text in array) { string text2 = text.Trim(); if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal)) { string text3 = text2; string text4 = text3.Substring(1, text3.Length - 1 - 1).Trim(); flag = text4.Equals("Version", StringComparison.OrdinalIgnoreCase); } else { if (!flag || string.IsNullOrWhiteSpace(text2) || IsConfigComment(text2)) { continue; } int num = text.IndexOf('='); if (num > 0) { string text5 = text.Substring(0, num).Trim(); if (text5.Equals("Current Version", StringComparison.OrdinalIgnoreCase)) { string text3 = text; int num2 = num + 1; legacyVersion = NormalizeLegacyVersionValue(text3.Substring(num2, text3.Length - num2)); return true; } } } } return false; } private static string NormalizeLegacyVersionValue(string value) { string text = value; int num = text.IndexOf(';'); int num2 = text.IndexOf('#'); int num3 = ((num < 0) ? num2 : ((num2 < 0) ? num : Math.Min(num, num2))); if (num3 >= 0) { text = text.Substring(0, num3); } return text.Trim(); } private static bool IsConfigComment(string trimmed) { if (!trimmed.StartsWith("#", StringComparison.Ordinal)) { return trimmed.StartsWith(";", StringComparison.Ordinal); } return true; } private static bool DropLegacyVersionFromConfig(ConfigFile config, ConfigDefinition legacyDefinition, out Exception? removeException, out Exception? orphanedEntriesException) { if (TryRemoveViaConfigApi(config, legacyDefinition, out removeException)) { orphanedEntriesException = null; return true; } return TryRemoveViaOrphanedEntries(config, legacyDefinition, out orphanedEntriesException); } private static bool HasLegacyVersionInConfig(ConfigFile config, ConfigDefinition legacyDefinition) { try { if (config.ContainsKey(legacyDefinition)) { return true; } return GetOrphanedEntries(config)?.Contains(legacyDefinition) ?? false; } catch { return true; } } private static void SaveConfig(ConfigFile config) { int num = 1; while (true) { try { config.Save(); break; } catch (IOException) when (num < 4) { Thread.Sleep(15 * num); } catch (UnauthorizedAccessException) when (num < 4) { Thread.Sleep(15 * num); } num++; } } private static string[] ReadAllLinesWithRetry(string path) { int num = 1; while (true) { try { return File.ReadAllLines(path); } catch (IOException) when (num < 4) { Thread.Sleep(15 * num); } catch (UnauthorizedAccessException) when (num < 4) { Thread.Sleep(15 * num); } num++; } } private static bool TryRemoveViaConfigApi(ConfigFile config, ConfigDefinition legacyDefinition, out Exception? exception) { exception = null; MethodInfo removeMethod = GetRemoveMethod(config); if (removeMethod == null) { return false; } try { if (removeMethod.Invoke(config, new object[1] { legacyDefinition }) is bool result) { return result; } return true; } catch (Exception ex) { exception = ex; return false; } } private static bool TryRemoveViaOrphanedEntries(ConfigFile config, ConfigDefinition legacyDefinition, out Exception? exception) { exception = null; PropertyInfo orphanedEntriesProperty = GetOrphanedEntriesProperty(config); if (orphanedEntriesProperty == null) { return false; } try { IDictionary orphanedEntries = GetOrphanedEntries(config, orphanedEntriesProperty); if (orphanedEntries == null) { return false; } bool flag = orphanedEntries.Contains(legacyDefinition); orphanedEntries.Remove(legacyDefinition); return flag && !orphanedEntries.Contains(legacyDefinition); } catch (Exception ex) { exception = ex; return false; } } private static bool ClearLegacyVersionInFile(ConfigFile config, out Exception? exception) { exception = null; string text = "initialize"; string configFilePath = config.ConfigFilePath; try { if (!File.Exists(configFilePath)) { return false; } text = "read"; string[] array = ReadAllLinesWithRetry(configFilePath); List list = new List(array.Length); bool flag = false; bool flag2 = false; text = "normalize"; for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal)) { string text3 = text2; string text4 = text3.Substring(1, text3.Length - 1 - 1).Trim(); flag2 = text4.Equals("Version", StringComparison.OrdinalIgnoreCase); list.Add(array[i]); continue; } if (!flag2) { list.Add(array[i]); continue; } if (string.IsNullOrWhiteSpace(text2) || IsConfigComment(text2)) { list.Add(array[i]); continue; } int num = array[i].IndexOf('='); if (num <= 0) { list.Add(array[i]); continue; } string text5 = array[i].Substring(0, num).Trim(); if (!text5.Equals("Current Version", StringComparison.OrdinalIgnoreCase)) { list.Add(array[i]); continue; } DropTrailingLegacyMetadata(list); flag = true; } if (!flag) { return false; } text = "write-temp"; string directoryName = Path.GetDirectoryName(configFilePath); if (string.IsNullOrWhiteSpace(directoryName)) { throw new InvalidOperationException("Config path does not contain a directory."); } string text6 = Path.Combine(directoryName, $"{Path.GetFileName(configFilePath)}.{Guid.NewGuid():N}.tmp"); try { File.WriteAllLines(text6, list); text = "replace"; if (File.Exists(configFilePath)) { try { File.Replace(text6, configFilePath, null); } catch (Exception ex) when (ex is PlatformNotSupportedException || ex is IOException || ex is UnauthorizedAccessException) { File.Copy(text6, configFilePath, overwrite: true); } } else { File.Move(text6, configFilePath); } } finally { if (File.Exists(text6)) { File.Delete(text6); } } return true; } catch (Exception innerException) { exception = new InvalidOperationException("Failed to clear legacy version in config file at '" + configFilePath + "' during stage '" + text + "'.", innerException); return false; } } private static void DropTrailingLegacyMetadata(List keptLines) { int num = keptLines.Count - 1; while (num >= 0 && string.IsNullOrWhiteSpace(keptLines[num])) { num--; } int num2 = num; while (num >= 0 && IsConfigComment(keptLines[num].Trim())) { num--; } if (num2 != num) { keptLines.RemoveRange(num + 1, keptLines.Count - num - 1); } } private static string BuildCleanupFailureContext(Exception? removeException, Exception? orphanedEntriesException, Exception? fileCleanupException) { string text = ((removeException == null) ? string.Empty : $" Remove API error: {removeException}."); string text2 = ((orphanedEntriesException == null) ? string.Empty : $" OrphanedEntries error: {orphanedEntriesException}."); string text3 = ((fileCleanupException == null) ? string.Empty : $" File cleanup error: {fileCleanupException}."); return text + text2 + text3; } internal static MethodInfo? GetRemoveMethod(ConfigFile config) { Type type2 = ((object)config).GetType(); Lazy orAdd = RemoveMethodCache.GetOrAdd(type2, delegate(Type type) { Type type3 = type; return new Lazy(() => type3.GetMethod("Remove", new Type[1] { typeof(ConfigDefinition) })); }); return orAdd.Value; } internal static PropertyInfo? GetOrphanedEntriesProperty(ConfigFile config) { Type type2 = ((object)config).GetType(); Lazy orAdd = OrphanedEntriesPropertyCache.GetOrAdd(type2, delegate(Type type) { Type type3 = type; return new Lazy(() => type3.GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)); }); return orAdd.Value; } internal static IDictionary? GetOrphanedEntries(ConfigFile config, PropertyInfo? orphanedEntriesProperty = null) { if ((object)orphanedEntriesProperty == null) { orphanedEntriesProperty = GetOrphanedEntriesProperty(config); } return orphanedEntriesProperty?.GetValue(config) as IDictionary; } private static ConfigEntry BindPluginVersion(ConfigFile config, string value) { return config.Bind("Version", "PluginVersion", value, "Tracks plugin release version."); } } } namespace System.Diagnostics.CodeAnalysis { [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ConstantExpectedAttribute : Attribute { public object? Min { get; set; } public object? Max { get; set; } } [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ExperimentalAttribute : Attribute { public string DiagnosticId { get; } public string? UrlFormat { get; set; } public ExperimentalAttribute(string diagnosticId) { DiagnosticId = diagnosticId; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullAttribute : Attribute { public string[] Members { get; } public MemberNotNullAttribute(string member) { Members = new string[1] { member }; } public MemberNotNullAttribute(params string[] members) { Members = members; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] [ExcludeFromCodeCoverage] internal sealed class MemberNotNullWhenAttribute : Attribute { public bool ReturnValue { get; } public string[] Members { get; } public MemberNotNullWhenAttribute(bool returnValue, string member) { ReturnValue = returnValue; Members = new string[1] { member }; } public MemberNotNullWhenAttribute(bool returnValue, params string[] members) { ReturnValue = returnValue; Members = members; } } [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SetsRequiredMembersAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class StringSyntaxAttribute : Attribute { public const string CompositeFormat = "CompositeFormat"; public const string DateOnlyFormat = "DateOnlyFormat"; public const string DateTimeFormat = "DateTimeFormat"; public const string EnumFormat = "EnumFormat"; public const string GuidFormat = "GuidFormat"; public const string Json = "Json"; public const string NumericFormat = "NumericFormat"; public const string Regex = "Regex"; public const string TimeOnlyFormat = "TimeOnlyFormat"; public const string TimeSpanFormat = "TimeSpanFormat"; public const string Uri = "Uri"; public const string Xml = "Xml"; public string Syntax { get; } public object?[] Arguments { get; } public StringSyntaxAttribute(string syntax) { Syntax = syntax; Arguments = new object[0]; } public StringSyntaxAttribute(string syntax, params object?[] arguments) { Syntax = syntax; Arguments = arguments; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class UnscopedRefAttribute : Attribute { } } namespace System.Runtime.Versioning { [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiresPreviewFeaturesAttribute : Attribute { public string? Message { get; } public string? Url { get; set; } public RequiresPreviewFeaturesAttribute() { } public RequiresPreviewFeaturesAttribute(string? message) { Message = message; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CallerArgumentExpressionAttribute : Attribute { public string ParameterName { get; } public CallerArgumentExpressionAttribute(string parameterName) { ParameterName = parameterName; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CollectionBuilderAttribute : Attribute { public Type BuilderType { get; } public string MethodName { get; } public CollectionBuilderAttribute(Type builderType, string methodName) { BuilderType = builderType; MethodName = methodName; } } [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class CompilerFeatureRequiredAttribute : Attribute { public const string RefStructs = "RefStructs"; public const string RequiredMembers = "RequiredMembers"; public string FeatureName { get; } public bool IsOptional { get; set; } public CompilerFeatureRequiredAttribute(string featureName) { FeatureName = featureName; } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public string[] Arguments { get; } public InterpolatedStringHandlerArgumentAttribute(string argument) { Arguments = new string[1] { argument }; } public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { Arguments = arguments; } } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class InterpolatedStringHandlerAttribute : Attribute { } [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal static class IsExternalInit { } [AttributeUsage(AttributeTargets.Method, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class ModuleInitializerAttribute : Attribute { } [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class OverloadResolutionPriorityAttribute : Attribute { public int Priority { get; } public OverloadResolutionPriorityAttribute(int priority) { Priority = priority; } } [AttributeUsage(AttributeTargets.Parameter, Inherited = true, AllowMultiple = false)] [ExcludeFromCodeCoverage] internal sealed class ParamCollectionAttribute : Attribute { } [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class RequiredMemberAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] [EditorBrowsable(EditorBrowsableState.Never)] [ExcludeFromCodeCoverage] internal sealed class RequiresLocationAttribute : Attribute { } [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] [ExcludeFromCodeCoverage] internal sealed class SkipLocalsInitAttribute : Attribute { } }