Decompiled source of PEAKNetworkingLibrary v1.0.9
AeralisFoundation.NetworkingLibrary.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
#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<INetworkingService> CreateOfflineNetworkingService = () => new OfflineNetworkingService(); internal static Func<bool> IsApplicationPlaying = () => Application.isPlaying; internal static Action<Object> DestroyObject = Object.Destroy; internal static Action<Object> DestroyObjectImmediate = Object.DestroyImmediate; internal static Func<float> RealtimeSinceStartupProvider = () => Time.realtimeSinceStartup; internal static Func<float, object?> 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<NetworkingPoller> list = (from poller in Object.FindObjectsByType<NetworkingPoller>((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<NetworkingPoller>(); ((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<NetworkingPoller> duplicatePollers, Action<Object> 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<Component>(); 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<Message, ulong, bool>? IncomingValidator { get; set; } event Action? LobbyCreated; event Action? LobbyEntered; event Action? LobbyLeft; event Action<ulong>? PlayerEntered; event Action<ulong>? PlayerLeft; event Action<string[]>? LobbyDataChanged; event Action<ulong, string[]>? 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<T>(string key); void RegisterPlayerDataKey(string key); void SetPlayerData(string key, object value); T GetPlayerData<T>(ulong steamId64, string key); void PollReceive(); void RegisterModSigner(uint modId, Func<byte[], byte[]> 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<float> TimeProvider = () => Time.unscaledTime; internal static Func<double> 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<bool> IsSteamClientRunning = () => SteamAPI.IsSteamRunning(); internal static Func<bool> IsSteamApiInitialized = ProbeSteamApiInitialized; internal static Func<INetworkingService> CreateSteamService = () => new SteamNetworkingService(); internal static Func<INetworkingService> CreateOfflineService = () => new OfflineNetworkingService(); internal static Func<string, Type?> ResolveType = Type.GetType; internal static Func<float> 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>(); } 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<DateTime> q = new Queue<DateTime>(); 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<uint, Dictionary<string, List<MessageHandler>>> rpcs = new Dictionary<uint, Dictionary<string, List<MessageHandler>>>(); private readonly Dictionary<string, string> lobbyData = new Dictionary<string, string>(); private readonly Dictionary<ulong, Dictionary<string, string>> perPlayerData = new Dictionary<ulong, Dictionary<string, string>>(); private readonly HashSet<string> lobbyKeys = new HashSet<string>(); private readonly HashSet<string> playerKeys = new HashSet<string>(); private readonly SlidingWindowRateLimiter rateLimiter = new SlidingWindowRateLimiter(100, TimeSpan.FromSeconds(1.0)); private readonly MessageSizePolicy messageSizePolicy; private readonly Dictionary<ulong, byte[]> perPeerSymmetricKey = new Dictionary<ulong, byte[]>(); private byte[]? globalSharedSecret; private HMACSHA256? globalHmac; private readonly Dictionary<uint, Func<byte[], byte[]>> modSigners = new Dictionary<uint, Func<byte[], byte[]>>(); private readonly Dictionary<uint, RSAParameters> modPublicKeys = new Dictionary<uint, RSAParameters>(); 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<string, DateTime> lastExceptionLogByKey = new Dictionary<string, DateTime>(); private static readonly Dictionary<string, int> suppressedExceptionLogByKey = new Dictionary<string, int>(); internal static Func<DateTime> UtcNow = () => DateTime.UtcNow; private long _nextMessageId; private bool offlineIsHost; private readonly List<RuntimeRegistration> runtimeRegistrations = new List<RuntimeRegistration>(); 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<Message, ulong, bool>? 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<ulong>? PlayerEntered; public event Action<ulong>? PlayerLeft; public event Action<string[]>? LobbyDataChanged; public event Action<ulong, string[]>? 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>(); } 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<byte[], byte[]> 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<uint, Func<byte[], byte[]>>[] array4; KeyValuePair<uint, RSAParameters>[] array5; lock (cryptoStateLock) { array4 = modSigners.ToArray(); array5 = modPublicKeys.Select((KeyValuePair<uint, RSAParameters> entry) => new KeyValuePair<uint, RSAParameters>(entry.Key, CloneRsaParameters(entry.Value))).ToArray(); } List<IDisposable> list = new List<IDisposable>(); Func<Message, ulong, bool> 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<uint, Func<byte[], byte[]>>[] array9 = array4; for (int l = 0; l < array9.Length; l++) { KeyValuePair<uint, Func<byte[], byte[]>> keyValuePair = array9[l]; target.RegisterModSigner(keyValuePair.Key, keyValuePair.Value); } KeyValuePair<uint, RSAParameters>[] array10 = array5; for (int m = 0; m < array10.Length; m++) { KeyValuePair<uint, RSAParameters> 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<string, string>(); 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<string, string>(); 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<HandlerRegistration> registeredHandlers = new List<HandlerRegistration>(); 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<string, List<MessageHandler>> value)) { value = new Dictionary<string, List<MessageHandler>>(); rpcs[modId] = value; } if (!value.TryGetValue(method.Name, out var value2)) { value2 = new List<MessageHandler>(); 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<HandlerRegistration> registeredHandlers = new List<HandlerRegistration>(); 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<string, List<MessageHandler>> value)) { value = new Dictionary<string, List<MessageHandler>>(); rpcs[modId] = value; } if (!value.TryGetValue(method2.Name, out var value2)) { value2 = new List<MessageHandler>(); 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<HandlerRegistration> handlersToRemove) { lock (rpcLock) { if (!rpcs.TryGetValue(modId, out Dictionary<string, List<MessageHandler>> 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<string, List<MessageHandler>> 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<string, List<MessageHandler>> 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<T>(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<T>(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<string, string> value2)) { value2 = new Dictionary<string, string>(); 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<T>(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<string, string> 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<T>(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<MessageHandler>(); lock (rpcLock) { if (rpcs.TryGetValue(modId, out Dictionary<string, List<MessageHandler>> 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<string, List<MessageHandler>> 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<object> assign, bool isLocalLoopback) { if (memberType == typeof(bool)) { assign(isLocalLoopback); } else if (memberType == typeof(bool?)) { assign(isLocalLoopback); } } private static void TryAssignMemberValue(Type memberType, Action<object> 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<int, byte[]> Fragments = new Dictionary<int, byte[]>(); } 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<DateTime> q = new Queue<DateTime>(); 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<CSteamID>(); private readonly List<string> lobbyDataKeys = new List<string>(); private readonly List<string> playerDataKeys = new List<string>(); private readonly Dictionary<CSteamID, Dictionary<string, string>> lastPlayerData = new Dictionary<CSteamID, Dictionary<string, string>>(); private readonly Dictionary<string, string> lastLobbyData = new Dictionary<string, string>(); private readonly HashSet<string> localEmptyLobbyDataKeys = new HashSet<string>(); private readonly HashSet<string> localEmptyPlayerDataKeys = new HashSet<string>(); private Func<CSteamID, int> getNumLobbyMembers = SteamMatchmaking.GetNumLobbyMembers; private Func<CSteamID, CSteamID> getLobbyOwner = SteamMatchmaking.GetLobbyOwner; private Func<CSteamID> getLocalSteamId = SteamUser.GetSteamID; private Func<CSteamID, int, CSteamID> getLobbyMemberByIndex = SteamMatchmaking.GetLobbyMemberByIndex; private Action<CSteamID, string, string> 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<CSteamID, string, string> getLobbyData = SteamMatchmaking.GetLobbyData; private Action<CSteamID, string, string> setLobbyMemberData = SteamMatchmaking.SetLobbyMemberData; private Func<CSteamID, CSteamID, string, string> getLobbyMemberData = SteamMatchmaking.GetLobbyMemberData; private Func<int, IntPtr[], int, int> receiveMessagesOnChannel = SteamNetworkingMessages.ReceiveMessagesOnChannel; private Callback<LobbyEnter_t>? cbLobbyEnter; private Callback<LobbyCreated_t>? cbLobbyCreated; private Callback<LobbyChatUpdate_t>? cbLobbyChatUpdate; private Callback<LobbyDataUpdate_t>? cbLobbyDataUpdate; private readonly Dictionary<uint, Dictionary<string, List<MessageHandler>>> rpcs = new Dictionary<uint, Dictionary<string, List<MessageHandler>>>(); private readonly List<RuntimeRegistration> runtimeRegistrations = new List<RuntimeRegistration>(); private readonly Queue<QueuedSend> normalQueue = new Queue<QueuedSend>(); private readonly Queue<QueuedSend> lowQueue = new Queue<QueuedSend>(); 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<uint, ulong> outgoingSequencePerMod = new Dictionary<uint, ulong>(); private readonly Dictionary<ulong, Dictionary<uint, ulong>> lastSeenSequence = new Dictionary<ulong, Dictionary<uint, ulong>>(); private readonly Dictionary<ulong, SlidingWindowRateLimiter> rateLimiters = new Dictionary<ulong, SlidingWindowRateLimiter>(); private readonly Dictionary<ulong, byte[]> perPeerSymmetricKey = new Dictionary<ulong, byte[]>(); private byte[]? globalSharedSecret; private HMACSHA256? globalHmac; private readonly Dictionary<uint, Func<byte[], byte[]>> modSigners = new Dictionary<uint, Func<byte[], byte[]>>(); private readonly Dictionary<uint, RSAParameters> modPublicKeys = new Dictionary<uint, RSAParameters>(); private RSAParameters[] modPublicKeysSnapshot = Array.Empty<RSAParameters>(); private readonly Dictionary<ulong, HandshakeState> handshakeStates = new Dictionary<ulong, HandshakeState>(); 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<RSA> 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<Message, ulong, bool>? IncomingValidator { get; set; } public event Action? LobbyCreated; public event Action? LobbyEntered; public event Action? LobbyLeft; public event Action<ulong>? PlayerEntered; public event Action<ulong>? PlayerLeft; public event Action<string[]>? LobbyDataChanged; public event Action<ulong, string[]>? 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<ulong>(); } try { int num = getNumLobbyMembers(Lobby); if (num <= 0) { return Array.Empty<ulong>(); } 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<ulong>(); } 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<ulong>(); } } 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<SteamCallbackPump> source = (from p in Object.FindObjectsByType<SteamCallbackPump>((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<SteamCallbackPump>(); if ((Object)(object)steamCallbackPump == (Object)null) { steamCallbackPump = val.AddComponent<SteamCallbackPump>(); 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<SteamCallbackPump> 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<Component>(); 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 enabledPumpingInThisInit