Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of EnhancedSpectator v0.2.0
EnhancedSpectator.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Dissonance; using Dissonance.Integrations.Unity_NFGO; using EnhancedSpectator.Config; using EnhancedSpectator.Features; using EnhancedSpectator.Features.FloatingHead; using EnhancedSpectator.Features.ModelInspection; using EnhancedSpectator.Features.PlayerStateSync; using EnhancedSpectator.Features.Spectator; using EnhancedSpectator.Features.SpectatorPresence; using EnhancedSpectator.Features.VoiceActivity; using EnhancedSpectator.Features.VoiceDiagnostics; using EnhancedSpectator.Features.VoiceRouting; using EnhancedSpectator.GameInterop; using EnhancedSpectator.Logging; using EnhancedSpectator.Networking; using EnhancedSpectator.Patching; using EnhancedSpectator.Runtime; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Steamworks; using Steamworks.Data; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Audio; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("EnhancedSpectator")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+12c7eaf60f9d1e976f15c11fdf911b1e41c5140e")] [assembly: AssemblyProduct("EnhancedSpectator")] [assembly: AssemblyTitle("EnhancedSpectator")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.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 EnhancedSpectator { [BepInPlugin("Auuueser.EnhancedSpectator", "Enhanced Spectator", "0.2.0")] public sealed class Plugin : BaseUnityPlugin { private FeatureBootstrapper? _featureBootstrapper; private PatchBootstrapper? _patchBootstrapper; private bool _applicationQuitting; private bool _shutdownComplete; private void Awake() { RuntimeConnectionState.Reset(); ModLog.Initialize(((BaseUnityPlugin)this).Logger); ModLog.Info("Enhanced Spectator 0.2.0 starting."); Application.quitting += OnApplicationQuitting; EnhancedSpectatorConfig enhancedSpectatorConfig = EnhancedSpectatorConfig.Bind(((BaseUnityPlugin)this).Config); ModLog.SetDebugEnabled(enhancedSpectatorConfig.EnableDebugLogging.Value); if (enhancedSpectatorConfig.EnableDebugLogging.Value) { PluginDiagnostics.LogPluginBinaryHash(((BaseUnityPlugin)this).Info.Location); } _featureBootstrapper = new FeatureBootstrapper(enhancedSpectatorConfig); _featureBootstrapper.Initialize(); _patchBootstrapper = new PatchBootstrapper(); _patchBootstrapper.Register(); EnhancedSpectatorRuntimeDriver.Install(_featureBootstrapper, Shutdown); ModLog.Info("Local spectator freecam initialized."); } private void OnDestroy() { if (!_applicationQuitting) { EnhancedSpectatorRuntimeDriver.EnsureInstalled(); ModLog.Warning("Plugin component destroyed before application quit; preserving runtime driver and patches."); } else { Shutdown(); } } private void OnApplicationQuitting() { _applicationQuitting = true; RuntimeConnectionState.MarkApplicationQuitting(); Shutdown(); } private void Shutdown() { if (!_shutdownComplete) { _shutdownComplete = true; RuntimeConnectionState.MarkPluginShuttingDown(); Application.quitting -= OnApplicationQuitting; EnhancedSpectatorRuntimeDriver.BeginShutdown(); _patchBootstrapper?.Dispose(); _featureBootstrapper?.Dispose(); ModLog.Info("Local spectator freecam shut down."); } } } public static class PluginMetadata { public const string Guid = "Auuueser.EnhancedSpectator"; public const string Name = "Enhanced Spectator"; public const string Version = "0.2.0"; } } namespace EnhancedSpectator.Runtime { public sealed class EnhancedSpectatorRuntimeDriver : MonoBehaviour { [CompilerGenerated] private static class <>O { public static UnityAction<Scene, LoadSceneMode> <0>__OnSceneLoaded; public static UnityAction<Scene> <1>__OnSceneUnloaded; public static CameraCallback <2>__OnCameraPreCull; public static Action<ScriptableRenderContext, Camera> <3>__OnBeginCameraRendering; } private static FeatureBootstrapper? _featureBootstrapper; private static Action? _shutdown; private static EnhancedSpectatorRuntimeDriver? _instance; private static bool _applicationQuitting; private static bool _sceneHookRegistered; private static int _lastCameraTickFrame = -1; private static int _lastCameraTickInstanceId = int.MinValue; private bool _intentionalDestroy; public static void Install(FeatureBootstrapper featureBootstrapper, Action shutdown) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown _featureBootstrapper = featureBootstrapper ?? throw new ArgumentNullException("featureBootstrapper"); _shutdown = shutdown ?? throw new ArgumentNullException("shutdown"); _applicationQuitting = false; if (!_sceneHookRegistered) { SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += OnSceneUnloaded; CameraCallback onPreCull = Camera.onPreCull; object obj = <>O.<2>__OnCameraPreCull; if (obj == null) { CameraCallback val = OnCameraPreCull; <>O.<2>__OnCameraPreCull = val; obj = (object)val; } Camera.onPreCull = (CameraCallback)Delegate.Combine((Delegate?)(object)onPreCull, (Delegate?)obj); RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering; _sceneHookRegistered = true; } EnsureInstalled(); } public static void EnsureInstalled() { //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_002c: Expected O, but got Unknown if (!_applicationQuitting && _featureBootstrapper != null && !((Object)(object)_instance != (Object)null)) { GameObject val = new GameObject("Enhanced Spectator Runtime"); Object.DontDestroyOnLoad((Object)val); _instance = val.AddComponent<EnhancedSpectatorRuntimeDriver>(); } } public static void BeginShutdown() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown //IL_006a: 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_0075: Expected O, but got Unknown _applicationQuitting = true; RuntimeConnectionState.MarkPluginShuttingDown(); if (_sceneHookRegistered) { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= OnSceneUnloaded; CameraCallback onPreCull = Camera.onPreCull; object obj = <>O.<2>__OnCameraPreCull; if (obj == null) { CameraCallback val = OnCameraPreCull; <>O.<2>__OnCameraPreCull = val; obj = (object)val; } Camera.onPreCull = (CameraCallback)Delegate.Remove((Delegate?)(object)onPreCull, (Delegate?)obj); RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering; _sceneHookRegistered = false; } if ((Object)(object)_instance != (Object)null) { _instance._intentionalDestroy = true; Object.Destroy((Object)(object)((Component)_instance).gameObject); _instance = null; } _featureBootstrapper = null; _shutdown = null; _lastCameraTickFrame = -1; _lastCameraTickInstanceId = int.MinValue; } private void Update() { _featureBootstrapper?.Tick(); } private void LateUpdate() { _featureBootstrapper?.LateTick(); } private void OnGUI() { _featureBootstrapper?.GuiTick(); } private void OnApplicationQuit() { _applicationQuitting = true; RuntimeConnectionState.MarkApplicationQuitting(); _shutdown?.Invoke(); _shutdown = null; } private void OnDestroy() { if ((Object)(object)_instance == (Object)(object)this) { _instance = null; } if (!_applicationQuitting && !_intentionalDestroy) { ModLog.Warning("Runtime driver destroyed before application quit; it will be recreated on the next plugin or scene lifecycle event."); } } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { RuntimeConnectionState.MarkSceneTransition(); EnsureInstalled(); } private static void OnSceneUnloaded(Scene scene) { RuntimeConnectionState.MarkSceneTransition(); } private static void OnCameraPreCull(Camera camera) { TryCameraPreCullTick(camera); } private static void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera) { TryCameraPreCullTick(camera); } private static void TryCameraPreCullTick(Camera camera) { if (_featureBootstrapper != null && !((Object)(object)camera == (Object)null)) { int frameCount = Time.frameCount; int instanceID = ((Object)camera).GetInstanceID(); if (_lastCameraTickFrame != frameCount || _lastCameraTickInstanceId != instanceID) { _lastCameraTickFrame = frameCount; _lastCameraTickInstanceId = instanceID; _featureBootstrapper.CameraPreCullTick(camera); } } } } public interface IRuntimeCameraPreCullTickable { void CameraPreCullTick(Camera camera); } public interface IRuntimeGuiTickable { void GuiTick(); } public interface IRuntimeLateTickable { void LateTick(); } public interface IRuntimeTickable { void Tick(); } public static class RuntimeConnectionState { private const float SceneTransitionUnsafeSeconds = 0.5f; private static bool _applicationQuitting; private static bool _pluginShuttingDown; private static float _unsafeUntilRealtime; public static void Reset() { _applicationQuitting = false; _pluginShuttingDown = false; _unsafeUntilRealtime = 0f; } public static void MarkApplicationQuitting() { _applicationQuitting = true; MarkUnsafeWindow(); } public static void MarkPluginShuttingDown() { _pluginShuttingDown = true; MarkUnsafeWindow(); } public static void MarkSceneTransition() { MarkUnsafeWindow(); } public static bool CanUseModNetworking(out string reason) { if (IsLifecycleUnsafe(out reason)) { return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { reason = "NetworkManager unavailable"; return false; } if (singleton.ShutdownInProgress) { reason = "NetworkManager shutdown in progress"; return false; } if (!singleton.IsListening) { reason = "NetworkManager is not listening"; return false; } if (!singleton.IsClient && !singleton.IsHost) { reason = "not connected as client or host"; return false; } if (!singleton.IsConnectedClient && !singleton.IsHost) { reason = "local client is not connected"; return false; } if (singleton.CustomMessagingManager == null) { reason = "CustomMessagingManager unavailable"; return false; } reason = string.Empty; return true; } public static bool CanRunLocalDiagnostics(out string reason) { if (IsLifecycleUnsafe(out reason)) { return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && singleton.ShutdownInProgress) { reason = "NetworkManager shutdown in progress"; return false; } reason = string.Empty; return true; } public static bool CanRepairVanillaPlayerState(out string reason) { if (IsLifecycleUnsafe(out reason)) { return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton != (Object)null && singleton.ShutdownInProgress) { reason = "NetworkManager shutdown in progress"; return false; } reason = string.Empty; return true; } public static bool ShouldSkipVanillaSpectatorTargetSwitch(out string reason) { if (IsLifecycleUnsafe(out reason)) { return true; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { reason = "NetworkManager unavailable"; return true; } if (singleton.ShutdownInProgress) { reason = "NetworkManager shutdown in progress"; return true; } if (!singleton.IsListening || (!singleton.IsClient && !singleton.IsHost)) { reason = "network is disconnected"; return true; } reason = string.Empty; return false; } private static bool IsLifecycleUnsafe(out string reason) { if (_applicationQuitting) { reason = "application is quitting"; return true; } if (_pluginShuttingDown) { reason = "plugin is shutting down"; return true; } if (Time.realtimeSinceStartup < _unsafeUntilRealtime) { reason = "scene transition unsafe window"; return true; } reason = string.Empty; return false; } private static void MarkUnsafeWindow() { float num = Time.realtimeSinceStartup + 0.5f; if (num > _unsafeUntilRealtime) { _unsafeUntilRealtime = num; } } } } namespace EnhancedSpectator.Patching { public interface IPatchModule { void Register(Harmony harmony); void Unregister(Harmony harmony); } public sealed class PatchBootstrapper : IDisposable { private readonly Harmony _harmony = new Harmony("Auuueser.EnhancedSpectator"); private readonly List<IPatchModule> _modules = new List<IPatchModule> { new SpectatorLifecyclePatchModule() }; private bool _registered; public void Register() { if (_registered) { return; } foreach (IPatchModule module in _modules) { module.Register(_harmony); } _registered = true; ModLog.Debug("Patch modules registered."); } public void Dispose() { if (_registered) { for (int num = _modules.Count - 1; num >= 0; num--) { _modules[num].Unregister(_harmony); } _harmony.UnpatchSelf(); _registered = false; ModLog.Debug("Patch modules unregistered."); } } } public sealed class SpectatorLifecyclePatchModule : IPatchModule { [HarmonyPatch(typeof(PlayerControllerB), "KillPlayer", new Type[] { typeof(Vector3), typeof(bool), typeof(CauseOfDeath), typeof(int), typeof(Vector3), typeof(bool) })] private static class PlayerKillPatch { private static void Postfix(PlayerControllerB __instance) { if (IsLocalPlayerOrOwner(__instance)) { SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.PlayerDied); } } } [HarmonyPatch(typeof(PlayerControllerB), "SetSpectatedPlayerEffects", new Type[] { typeof(bool) })] private static class SpectatedPlayerEffectsPatch { private static void Postfix(PlayerControllerB __instance) { if (IsLocalSpectatorContext(__instance)) { SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.SpectatedPlayerEffectsApplied); } } } [HarmonyPatch(typeof(StartOfRound), "SwitchCamera", new Type[] { typeof(Camera) })] private static class SwitchCameraPatch { private static void Postfix() { SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.CameraSwitched); } } [HarmonyPatch(typeof(StartOfRound), "SetSpectateCameraToGameOverMode", new Type[] { typeof(bool), typeof(PlayerControllerB) })] private static class GameOverSpectateModePatch { private static void Postfix() { SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.GameOverOverrideChanged); } } [HarmonyPatch(typeof(StartOfRound), "ReviveDeadPlayers")] private static class ReviveDeadPlayersPatch { private static void Postfix() { SpectatorLifecycleEvents.Raise(SpectatorLifecycleEventKind.Revived); } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc", new Type[] { typeof(ulong), typeof(int), typeof(ulong[]), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(bool) })] private static class PlayerConnectedPatch { private static int _nextDebugFrame; private static void Postfix() { RestoreLocalDeadSpectatorStateAfterPlayerJoin(); } private static void RestoreLocalDeadSpectatorStateAfterPlayerJoin() { if (!RuntimeConnectionState.CanRepairVanillaPlayerState(out string reason)) { LogDebug("Skipped local spectator state restore after player connect: " + reason + "."); return; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return; } PlayerControllerB localPlayerController = instance.localPlayerController; if (!((Object)(object)localPlayerController == (Object)null) && IsLocalPlayerOrOwner(localPlayerController) && localPlayerController.isPlayerDead && localPlayerController.isPlayerControlled) { localPlayerController.isPlayerControlled = false; Camera spectateCamera = instance.spectateCamera; if ((Object)(object)spectateCamera != (Object)null && (Object)(object)instance.activeCamera != (Object)(object)spectateCamera) { instance.SwitchCamera(spectateCamera); } LogDebug("Restored local dead spectator state after vanilla player-connect sync."); } } private static void LogDebug(string message) { if (Time.frameCount >= _nextDebugFrame) { _nextDebugFrame = Time.frameCount + 120; ModLog.Debug(message); } } } [HarmonyPatch(typeof(PlayerControllerB), "SpectateNextPlayer", new Type[] { typeof(bool) })] private static class SpectateNextPlayerPatch { private static int _nextSuppressDebugFrame; private static int _nextUnsafeDebugFrame; private static bool Prefix(PlayerControllerB __instance) { if (!TryGetLocalDeadSpectatorContext(__instance, out string reason)) { if (reason.Length > 0 && Time.frameCount >= _nextUnsafeDebugFrame) { _nextUnsafeDebugFrame = Time.frameCount + 120; ModLog.Debug("Vanilla spectator target switch suppression skipped: " + reason + "."); } return true; } if (RuntimeConnectionState.ShouldSkipVanillaSpectatorTargetSwitch(out string reason2)) { if (Time.frameCount >= _nextUnsafeDebugFrame) { _nextUnsafeDebugFrame = Time.frameCount + 120; ModLog.Debug("Skipped vanilla spectator target switch during unsafe lifecycle: " + reason2 + "."); } return false; } if (!TryGetLocalSpectatorContext(__instance, out string reason3)) { if (reason3.Length > 0 && Time.frameCount >= _nextUnsafeDebugFrame) { _nextUnsafeDebugFrame = Time.frameCount + 120; ModLog.Debug("Vanilla spectator target switch suppression skipped: " + reason3 + "."); } return true; } if (SpectatorVanillaInputGuard.ShouldSuppressTargetSwitchInput(out string reason4)) { if (Time.frameCount >= _nextSuppressDebugFrame) { _nextSuppressDebugFrame = Time.frameCount + 120; ModLog.Debug("Suppressed vanilla spectator target switch while " + ((reason4.Length > 0) ? reason4 : "quick menu is open") + "."); } return false; } return true; } private static bool TryGetLocalDeadSpectatorContext(PlayerControllerB player, out string reason) { reason = string.Empty; if ((Object)(object)player == (Object)null) { reason = "patch instance unavailable"; return false; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { reason = "round unavailable"; return false; } PlayerControllerB localPlayerController = instance.localPlayerController; if ((Object)(object)localPlayerController == (Object)null) { reason = "local player unavailable"; return false; } if (!IsLocalPlayerOrOwner(player, localPlayerController)) { return false; } if (!localPlayerController.isPlayerDead) { return false; } return true; } private static bool TryGetLocalSpectatorContext(PlayerControllerB player, out string reason) { reason = string.Empty; if ((Object)(object)player == (Object)null) { reason = "patch instance unavailable"; return false; } StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { reason = "round unavailable"; return false; } PlayerControllerB localPlayerController = instance.localPlayerController; if ((Object)(object)localPlayerController == (Object)null) { reason = "local player unavailable"; return false; } if (!IsLocalPlayerOrOwner(player, localPlayerController)) { return false; } if (!localPlayerController.isPlayerDead) { return false; } if ((Object)(object)instance.spectateCamera == (Object)null || (Object)(object)localPlayerController.spectatedPlayerScript == (Object)null) { reason = "spectator camera or target unavailable"; return false; } return true; } } [HarmonyPatch(typeof(PlayerControllerB), "Interact_performed", new Type[] { typeof(CallbackContext) })] private static class InteractPerformedPatch { private static int _nextSuppressDebugFrame; private static bool Prefix(PlayerControllerB __instance) { if (!ShouldSuppressQuickMenuGameplayInput(__instance, out string reason)) { return true; } LogQuickMenuGameplaySuppression("interact", reason, ref _nextSuppressDebugFrame); return false; } } [HarmonyPatch(typeof(PlayerControllerB), "ActivateItem_performed", new Type[] { typeof(CallbackContext) })] private static class ActivateItemPerformedPatch { private static int _nextSuppressDebugFrame; private static bool Prefix(PlayerControllerB __instance) { if (!ShouldSuppressQuickMenuGameplayInput(__instance, out string reason)) { return true; } LogQuickMenuGameplaySuppression("activate item", reason, ref _nextSuppressDebugFrame); return false; } } public void Register(Harmony harmony) { harmony.CreateClassProcessor(typeof(PlayerKillPatch)).Patch(); harmony.CreateClassProcessor(typeof(SpectatedPlayerEffectsPatch)).Patch(); harmony.CreateClassProcessor(typeof(SwitchCameraPatch)).Patch(); harmony.CreateClassProcessor(typeof(GameOverSpectateModePatch)).Patch(); harmony.CreateClassProcessor(typeof(ReviveDeadPlayersPatch)).Patch(); harmony.CreateClassProcessor(typeof(PlayerConnectedPatch)).Patch(); harmony.CreateClassProcessor(typeof(SpectateNextPlayerPatch)).Patch(); harmony.CreateClassProcessor(typeof(InteractPerformedPatch)).Patch(); harmony.CreateClassProcessor(typeof(ActivateItemPerformedPatch)).Patch(); } public void Unregister(Harmony harmony) { } private static bool IsLocalSpectatorContext(PlayerControllerB player) { if (IsLocalPlayerOrOwner(player)) { return true; } PlayerControllerB localPlayer = GetLocalPlayer(); if ((Object)(object)localPlayer != (Object)null && localPlayer.isPlayerDead) { return (Object)(object)localPlayer.spectatedPlayerScript == (Object)(object)player; } return false; } private static bool IsLocalPlayerOrOwner(PlayerControllerB player) { if ((Object)(object)player == (Object)null) { return false; } if ((Object)(object)GetLocalPlayer() == (Object)(object)player) { return true; } if (!((NetworkBehaviour)player).IsOwner) { return ((NetworkBehaviour)player).IsLocalPlayer; } return true; } private static bool IsLocalPlayerOrOwner(PlayerControllerB player, PlayerControllerB localPlayer) { if ((Object)(object)player == (Object)null) { return false; } if ((Object)(object)localPlayer == (Object)(object)player) { return true; } if (!((NetworkBehaviour)player).IsOwner) { return ((NetworkBehaviour)player).IsLocalPlayer; } return true; } private static PlayerControllerB? GetLocalPlayer() { StartOfRound instance = StartOfRound.Instance; if (!((Object)(object)instance != (Object)null)) { return null; } return instance.localPlayerController; } private static bool ShouldSuppressQuickMenuGameplayInput(PlayerControllerB player, out string reason) { reason = string.Empty; if (!SpectatorVanillaInputGuard.ShouldSuppressGameplayInteractInput() && !IsLocalQuickMenuOpen()) { return false; } PlayerControllerB localPlayer = GetLocalPlayer(); if ((Object)(object)localPlayer == (Object)null) { reason = "local player unavailable"; return false; } if (!IsLocalPlayerOrOwner(player, localPlayer)) { return false; } reason = "quick menu is open"; return true; } private static bool IsLocalQuickMenuOpen() { PlayerControllerB localPlayer = GetLocalPlayer(); if ((Object)(object)localPlayer != (Object)null && (Object)(object)localPlayer.quickMenuManager != (Object)null) { return localPlayer.quickMenuManager.isMenuOpen; } return false; } private static void LogQuickMenuGameplaySuppression(string inputName, string reason, ref int nextSuppressDebugFrame) { if (Time.frameCount >= nextSuppressDebugFrame) { nextSuppressDebugFrame = Time.frameCount + 120; ModLog.Debug("Suppressed local " + inputName + " input while " + reason + "."); } } } } namespace EnhancedSpectator.Networking { public sealed class EnhancedSpectatorNetworkService : IEnhancedSpectatorNetworkService { private const float CapabilityStableDelaySeconds = 0.35f; private const int CapabilityStableDelayFrames = 3; private const float CompatiblePeerProbeTimeoutSeconds = 2.5f; private const float CapabilityProbeRetryIntervalSeconds = 2.5f; private const float SpectatorPoseHeartbeatIntervalSeconds = 0.5f; private readonly EnhancedSpectatorConfig _config; private readonly ISpectatorTargetStateProvider _spectatorTargetStateProvider; private readonly ISpectatorPoseStateProvider _spectatorPoseStateProvider; private readonly IPeerIdentityStateProvider _peerIdentityStateProvider; private readonly IVoiceActivityProvider _voiceActivityProvider; private readonly IModNetworkTransport _transport; private readonly INetworkRuntimeState _runtimeState; private readonly RemotePeerRegistry _peerRegistry; private readonly RemoteSpectatorTargetRegistry _remoteTargetRegistry; private readonly RemoteSpectatorPoseRegistry _remotePoseRegistry; private readonly RemotePeerIdentityRegistry _remoteIdentityRegistry; private readonly RemoteVoiceActivityRegistry _remoteVoiceActivityRegistry; private readonly VoiceActivityDebugLimiter _voiceDebugLimiter; private SpectatorTargetState? _lastObservedTargetState; private SpectatorTargetState? _lastSentTargetState; private SpectatorTargetState? _pendingTargetState; private SpectatorPoseState? _lastObservedPoseState; private SpectatorPoseState? _lastSentPoseState; private SpectatorPoseState? _pendingPoseState; private bool _pendingPoseRefresh; private VoiceActivityState? _lastObservedVoiceActivityState; private VoiceActivityState? _lastSentVoiceActivityState; private VoiceActivityState? _pendingVoiceActivityState; private PeerIdentityState? _lastSentIdentityState; private bool _pendingVoiceActivityRefresh; private bool _initialized; private bool _networkAvailable; private bool _targetSyncReady; private bool _hasCompatibleModPeer; private bool _noCompatiblePeerLocalOnly; private bool _capabilitySent; private float _capabilityProbeSentRealtime; private ulong? _lastLocalClientId; private float _nextTargetSyncTime; private float _nextPoseSyncTime; private float _nextVoiceActivitySyncTime; private float _nextTargetSampleTime; private float _nextPoseSampleTime; private float _nextPoseRefreshTime; private float _nextVoiceActivitySampleTime; private float _nextVoiceActivityRefreshTime; private float _nextPeerPruneTime; private float _transportRegisteredRealtime; private int _transportRegisteredFrame; private int _nextLifecycleDebugFrame; private int _nextCapabilityDelayDebugFrame; private string? _lastDegradationReason; private string? _lastTargetSyncWaitReason; private NetworkLifecycleState _lifecycleState; public bool IsNetworkAvailable => _networkAvailable; public bool IsTargetSyncEnabled => _targetSyncReady; public bool HasCompatibleModPeer => _hasCompatibleModPeer; public NetworkLifecycleState LifecycleState => _lifecycleState; public int RemotePeerIdentityRevision => _remoteIdentityRegistry.Revision; public int RemoteSpectatorTargetRevision => _remoteTargetRegistry.Revision; public EnhancedSpectatorNetworkService(EnhancedSpectatorConfig config, SpectatorModule spectatorModule) { EnhancedSpectatorConfig config2 = config; this..ctor(config2, spectatorModule, spectatorModule, spectatorModule, new LethalCompanyVoiceActivityProvider(), new UnityNetcodeMessagingTransport(() => config2.DebugNetworkMessages.Value), UnityNetworkRuntimeState.Instance); } public EnhancedSpectatorNetworkService(EnhancedSpectatorConfig config, ISpectatorTargetStateProvider spectatorTargetStateProvider, ISpectatorPoseStateProvider spectatorPoseStateProvider, IPeerIdentityStateProvider peerIdentityStateProvider, IVoiceActivityProvider voiceActivityProvider, IModNetworkTransport transport) : this(config, spectatorTargetStateProvider, spectatorPoseStateProvider, peerIdentityStateProvider, voiceActivityProvider, transport, UnityNetworkRuntimeState.Instance) { } public EnhancedSpectatorNetworkService(EnhancedSpectatorConfig config, ISpectatorTargetStateProvider spectatorTargetStateProvider, ISpectatorPoseStateProvider spectatorPoseStateProvider, IPeerIdentityStateProvider peerIdentityStateProvider, IVoiceActivityProvider voiceActivityProvider, IModNetworkTransport transport, INetworkRuntimeState runtimeState) { _peerRegistry = new RemotePeerRegistry(); _remoteTargetRegistry = new RemoteSpectatorTargetRegistry(); _remotePoseRegistry = new RemoteSpectatorPoseRegistry(); _remoteIdentityRegistry = new RemotePeerIdentityRegistry(); _remoteVoiceActivityRegistry = new RemoteVoiceActivityRegistry(); _voiceDebugLimiter = new VoiceActivityDebugLimiter(); _capabilityProbeSentRealtime = -1f; _transportRegisteredFrame = -1; base..ctor(); _config = config ?? throw new ArgumentNullException("config"); _spectatorTargetStateProvider = spectatorTargetStateProvider ?? throw new ArgumentNullException("spectatorTargetStateProvider"); _spectatorPoseStateProvider = spectatorPoseStateProvider ?? throw new ArgumentNullException("spectatorPoseStateProvider"); _peerIdentityStateProvider = peerIdentityStateProvider ?? throw new ArgumentNullException("peerIdentityStateProvider"); _voiceActivityProvider = voiceActivityProvider ?? throw new ArgumentNullException("voiceActivityProvider"); _transport = transport ?? throw new ArgumentNullException("transport"); _runtimeState = runtimeState ?? throw new ArgumentNullException("runtimeState"); } public void Initialize() { if (!_initialized) { _initialized = true; _lifecycleState = NetworkLifecycleState.LocalOnly; ModLog.Debug("Networking service initialized."); } } public void Tick() { if (!_initialized) { return; } try { string reason; if (!_config.EnableNetworking.Value) { Degrade("networking config disabled"); } else if (!_runtimeState.CanUseModNetworking(out reason)) { StopNetworkingForLifecycle(reason); } else if (EnsureTransportRegistered()) { _networkAvailable = true; _lifecycleState = NetworkLifecycleState.TransportRegistered; RegisterLocalCapability(); PruneDisconnectedPeers(); SendLocalCapabilityIfNeeded(); UpdateTargetSyncReadiness(); UpdateCompatiblePeerProbeState(); if (!NetworkCompatibilityPolicy.ShouldRunBusinessSync(_lifecycleState, _targetSyncReady)) { ClearBusinessSyncState(); return; } UpdateAndSendLocalPeerIdentity(); UpdateLocalSpectatorTargetIfDue(); UpdateLocalSpectatorPoseIfDue(); UpdateLocalVoiceActivityIfDue(); UpdateTargetSyncReadiness(); TrySendPendingTargetState(); TrySendPendingPoseState(); TrySendPendingVoiceActivityState(); } } catch (Exception ex) { Degrade("networking exception: " + ex.GetType().Name); ModLog.Error($"Enhanced Spectator networking failed and degraded to local-only mode: {ex}"); } } public void Dispose() { _transport.Dispose(); ClearNetworkState(); _initialized = false; _lifecycleState = NetworkLifecycleState.Disposed; ModLog.Debug("Networking service disposed."); } public bool TryGetPeerCapability(ulong clientId, out ModPeerCapability capability) { return _peerRegistry.TryGetCapability(clientId, out capability); } public bool TryGetRemoteSpectatorTarget(ulong clientId, out SpectatorTargetState state) { return _remoteTargetRegistry.TryGet(clientId, out state); } public bool TryGetRemoteSpectatorPose(ulong clientId, out SpectatorPoseState state) { return _remotePoseRegistry.TryGet(clientId, out state); } public bool TryGetRemotePeerIdentity(ulong clientId, out PeerIdentityState state) { return _remoteIdentityRegistry.TryGet(clientId, out state); } public bool TryGetRemoteVoiceActivity(ulong clientId, out VoiceActivityState state) { if (!_remoteVoiceActivityRegistry.TryGet(clientId, out state, out var receivedAtTicks)) { return false; } long ticks = TimeSpan.FromSeconds(Mathf.Max(0f, _config.VoiceActivityStaleSeconds.Value)).Ticks; if (VoiceActivitySyncRules.IsFresh(receivedAtTicks, _runtimeState.UtcNowTicks, ticks)) { return true; } DebugVoice($"Remote voice activity expired: peer={clientId}."); _remoteVoiceActivityRegistry.Remove(clientId); state = VoiceActivityState.NoData; return false; } public IReadOnlyList<SpectatorTargetState> GetRemoteSpectatorTargets() { return _remoteTargetRegistry.GetSnapshot().AsReadOnly(); } public void CopyRemoteSpectatorTargetsTo(List<SpectatorTargetState> destination) { if (destination == null) { throw new ArgumentNullException("destination"); } _remoteTargetRegistry.CopySnapshotTo(destination); } public IReadOnlyList<SpectatorPoseState> GetRemoteSpectatorPoses() { return _remotePoseRegistry.GetSnapshot().AsReadOnly(); } public IReadOnlyList<PeerIdentityState> GetRemotePeerIdentities() { return _remoteIdentityRegistry.GetSnapshot().AsReadOnly(); } public void CopyRemotePeerIdentitiesTo(List<PeerIdentityState> destination) { if (destination == null) { throw new ArgumentNullException("destination"); } _remoteIdentityRegistry.CopySnapshotTo(destination); } public IReadOnlyList<VoiceActivityState> GetRemoteVoiceActivities() { return _remoteVoiceActivityRegistry.GetSnapshot().AsReadOnly(); } public IReadOnlyList<ModPeerCapability> GetKnownModdedPeers() { return _peerRegistry.GetCapabilitiesSnapshot().AsReadOnly(); } private bool EnsureTransportRegistered() { if (_transport.IsRegistered) { if (!_transport.IsNetworkAvailable) { Degrade("custom messaging transport no longer available"); return false; } return true; } if (!_transport.TryRegister(OnCapabilityReceived, OnSpectatorTargetReceived, OnSpectatorPoseReceived, OnPeerIdentityReceived, OnVoiceActivityReceived, out string reason)) { Degrade(reason); return false; } _capabilitySent = false; _capabilityProbeSentRealtime = -1f; _noCompatiblePeerLocalOnly = false; _lastLocalClientId = _transport.LocalClientId; _transportRegisteredFrame = _runtimeState.FrameCount; _transportRegisteredRealtime = _runtimeState.RealtimeSinceStartup; _lastDegradationReason = null; Debug("Custom messaging transport registered."); return true; } private void RegisterLocalCapability() { ulong localClientId = _transport.LocalClientId; if (_lastLocalClientId.HasValue && _lastLocalClientId.Value != localClientId) { _peerRegistry.Clear(); _remoteTargetRegistry.Clear(); _remotePoseRegistry.Clear(); _remoteIdentityRegistry.Clear(); _remoteVoiceActivityRegistry.Clear(); _lastObservedTargetState = null; _lastSentTargetState = null; _pendingTargetState = null; _lastObservedPoseState = null; _lastSentPoseState = null; _pendingPoseState = null; _pendingPoseRefresh = false; _lastObservedVoiceActivityState = null; _lastSentVoiceActivityState = null; _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; _lastSentIdentityState = null; _nextTargetSyncTime = 0f; _nextPoseSyncTime = 0f; _nextVoiceActivitySyncTime = 0f; _nextTargetSampleTime = 0f; _nextPoseSampleTime = 0f; _nextPoseRefreshTime = 0f; _nextVoiceActivitySampleTime = 0f; _nextVoiceActivityRefreshTime = 0f; _capabilitySent = false; _capabilityProbeSentRealtime = -1f; _noCompatiblePeerLocalOnly = false; _transportRegisteredFrame = _runtimeState.FrameCount; _transportRegisteredRealtime = _runtimeState.RealtimeSinceStartup; Debug($"Local Netcode client id changed from {_lastLocalClientId.Value} to {localClientId}; reset network state."); } _lastLocalClientId = localClientId; _peerRegistry.RegisterLocal(CreateLocalCapability(localClientId)); } private void SendLocalCapabilityIfNeeded() { if (!_config.EnableCapabilityHandshake.Value) { _capabilitySent = false; } else { if (_capabilitySent && !NetworkCompatibilityPolicy.ShouldRetryCapabilityProbe(_targetSyncReady, _capabilitySent, _capabilityProbeSentRealtime, _runtimeState.RealtimeSinceStartup, 2.5f)) { return; } if (!_runtimeState.CanUseModNetworking(out string reason)) { StopNetworkingForLifecycle(reason); return; } if (!IsTransportStableForCapability(out string reason2)) { DebugCapabilityDelay(reason2); return; } ModPeerCapability modPeerCapability = CreateLocalCapability(_transport.LocalClientId); _peerRegistry.RegisterLocal(modPeerCapability); if (_transport.SendCapability(modPeerCapability, null, out string reason3)) { _capabilitySent = true; _capabilityProbeSentRealtime = _runtimeState.RealtimeSinceStartup; _noCompatiblePeerLocalOnly = false; _lifecycleState = NetworkLifecycleState.TransportRegistered; Debug($"Capability sent: client={modPeerCapability.ClientId}, targetSync={modPeerCapability.SupportsSpectatorTargetSync}, voiceSync={modPeerCapability.SupportsVoiceActivitySync}, voiceRoute={modPeerCapability.SupportsSpectatorVoiceToTarget}."); } else { Degrade("capability send failed: " + reason3); } } } private bool IsTransportStableForCapability(out string reason) { int num = _runtimeState.FrameCount - _transportRegisteredFrame; if (_transportRegisteredFrame < 0 || num < 3) { reason = $"transport registered {Mathf.Max(0, num)} frame(s) ago"; return false; } float num2 = _runtimeState.RealtimeSinceStartup - _transportRegisteredRealtime; if (num2 < 0.35f) { reason = $"transport registered {num2:0.00}s ago"; return false; } reason = string.Empty; return true; } private void UpdateAndSendLocalPeerIdentity() { if (!_capabilitySent || !_targetSyncReady || !_peerIdentityStateProvider.TryGetLocalPeerIdentity(out PeerIdentityState state) || string.IsNullOrWhiteSpace(state.DisplayName) || IdentityEquals(_lastSentIdentityState, state)) { return; } List<ulong> spectatorTargetSyncPeerIds = _peerRegistry.GetSpectatorTargetSyncPeerIds(_transport.LocalClientId); if (spectatorTargetSyncPeerIds.Count != 0) { if (_transport.SendPeerIdentity(state, spectatorTargetSyncPeerIds, out string reason)) { _lastSentIdentityState = state; Debug($"Peer identity sent: client={state.ClientId}, slot={state.PlayerSlotId}, name={state.DisplayName}, voiceName={FormatVoiceName(state.VoicePlayerName)}."); } else { Degrade("peer identity send failed: " + reason); } } } private void PruneDisconnectedPeers(bool force = false) { if (!force && _runtimeState.UnscaledTime < _nextPeerPruneTime) { return; } _nextPeerPruneTime = _runtimeState.UnscaledTime + 1f; if (!_lastLocalClientId.HasValue) { return; } foreach (ulong remotePeerId in _peerRegistry.GetRemotePeerIds(_lastLocalClientId.Value)) { if ((_transport.IsHost || !_peerRegistry.IsRelayedPeer(remotePeerId)) && !_transport.IsPeerConnected(remotePeerId)) { RelayDisconnectedPeerCleanup(remotePeerId); _peerRegistry.Remove(remotePeerId); _remoteTargetRegistry.Remove(remotePeerId); _remotePoseRegistry.Remove(remotePeerId); _remoteIdentityRegistry.Remove(remotePeerId); _remoteVoiceActivityRegistry.Remove(remotePeerId); Debug($"Removed disconnected network peer state: peer={remotePeerId}."); } } } private void UpdateTargetSyncReadiness() { string text = null; if (!_config.EnableCapabilityHandshake.Value) { text = "capability handshake disabled"; } else if (!_config.EnableSpectatorTargetSync.Value) { text = "spectator target sync disabled"; } else if (!_transport.IsRegistered) { text = "custom messaging transport not registered"; } else if (!_peerRegistry.HasSpectatorTargetSyncPeer(_transport.LocalClientId)) { text = "waiting for compatible remote peer capability"; } _targetSyncReady = text == null; _hasCompatibleModPeer = _targetSyncReady; if (_config.DebugNetworkMessages.Value && _lastTargetSyncWaitReason != text) { _lastTargetSyncWaitReason = text; if (text == null) { ModLog.Debug("Spectator target sync is ready."); } else { ModLog.Debug("Spectator target sync is not ready: " + text + "."); } } } private void UpdateCompatiblePeerProbeState() { _lifecycleState = NetworkCompatibilityPolicy.ResolveLifecycleState(_targetSyncReady, _capabilitySent, _capabilityProbeSentRealtime, _runtimeState.RealtimeSinceStartup, 2.5f); _noCompatiblePeerLocalOnly = _lifecycleState == NetworkLifecycleState.NoCompatiblePeerLocalOnly; if (_noCompatiblePeerLocalOnly && _lastDegradationReason != "no compatible Enhanced Spectator peer capability received") { _lastDegradationReason = "no compatible Enhanced Spectator peer capability received"; Debug("Networking remains local-only because no compatible Enhanced Spectator peer answered the capability probe."); } } private void ClearBusinessSyncState() { _lastObservedTargetState = null; _lastSentTargetState = null; _pendingTargetState = null; _lastObservedPoseState = null; _lastSentPoseState = null; _pendingPoseState = null; _pendingPoseRefresh = false; _lastObservedVoiceActivityState = null; _lastSentVoiceActivityState = null; _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; _lastSentIdentityState = null; _nextTargetSampleTime = 0f; _nextPoseSampleTime = 0f; _nextPoseRefreshTime = 0f; _nextVoiceActivitySampleTime = 0f; } private void UpdateLocalSpectatorTargetIfDue() { if (!_config.EnableSpectatorTargetSync.Value) { _lastObservedTargetState = null; _lastSentTargetState = null; _pendingTargetState = null; } else if (NetworkSyncSamplingRules.ShouldSample(_lastObservedTargetState != null, _runtimeState.UnscaledTime, _nextTargetSampleTime)) { UpdateLocalSpectatorTarget(); _nextTargetSampleTime = NetworkSyncSamplingRules.ResolveNextSampleTime(_runtimeState.UnscaledTime, 0.1f); } } private void UpdateLocalSpectatorTarget() { if (!_spectatorTargetStateProvider.TryGetCurrentSpectatorTarget(out SpectatorTargetState state)) { state = new SpectatorTargetState(isSpectating: false, _transport.LocalClientId, 0uL, null, null, _runtimeState.UtcNowTicks); } if (_lastObservedTargetState == null || !_lastObservedTargetState.Equals(state)) { _lastObservedTargetState = state; if (_lastSentTargetState != null && _lastSentTargetState.Equals(state)) { _pendingTargetState = null; } else { _pendingTargetState = state; } Debug($"Observed spectator target change: spectating={state.IsSpectating}, localClient={state.LocalClientId}, localSlot={state.LocalPlayerSlotId}, targetClient={FormatNullable(state.TargetClientId)}, targetSlot={FormatNullable(state.TargetPlayerSlotId)}."); } } private void UpdateLocalSpectatorPoseIfDue() { if (!_config.EnableSpectatorPoseSync.Value) { _lastObservedPoseState = null; _lastSentPoseState = null; _pendingPoseState = null; _pendingPoseRefresh = false; } else if (NetworkSyncSamplingRules.ShouldSample(_lastObservedPoseState != null, _runtimeState.UnscaledTime, _nextPoseSampleTime)) { UpdateLocalSpectatorPose(); _nextPoseSampleTime = NetworkSyncSamplingRules.ResolveNextSampleTime(_runtimeState.UnscaledTime, GetPoseSyncInterval()); } } private void TrySendPendingTargetState() { if (!_targetSyncReady || _pendingTargetState == null) { return; } string reason; if (!_transport.IsRegistered || !_transport.IsNetworkAvailable) { StopNetworkingForLifecycle("custom messaging transport unavailable before target send"); } else if (!_runtimeState.CanUseModNetworking(out reason)) { StopNetworkingForLifecycle(reason); } else if (_lastSentTargetState != null && _lastSentTargetState.Equals(_pendingTargetState)) { _pendingTargetState = null; } else { if (_runtimeState.UnscaledTime < _nextTargetSyncTime) { return; } List<ulong> spectatorTargetSyncPeerIds = _peerRegistry.GetSpectatorTargetSyncPeerIds(_transport.LocalClientId); if (spectatorTargetSyncPeerIds.Count == 0) { UpdateTargetSyncReadiness(); return; } SpectatorTargetSyncMessage spectatorTargetSyncMessage = new SpectatorTargetSyncMessage(1, _pendingTargetState, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorTarget(spectatorTargetSyncMessage, spectatorTargetSyncPeerIds, out string reason2)) { _lastSentTargetState = _pendingTargetState; _pendingTargetState = null; _nextTargetSyncTime = _runtimeState.UnscaledTime + 0.1f; Debug($"Spectator target sent to {spectatorTargetSyncPeerIds.Count} peer(s): spectating={spectatorTargetSyncMessage.State.IsSpectating}, targetClient={FormatNullable(spectatorTargetSyncMessage.State.TargetClientId)}, targetSlot={FormatNullable(spectatorTargetSyncMessage.State.TargetPlayerSlotId)}."); } else { Degrade("spectator target send failed: " + reason2); } } } private void UpdateLocalSpectatorPose() { //IL_005e: 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_014d: Unknown result type (might be due to invalid IL or missing references) if (!_config.EnableSpectatorPoseSync.Value) { _lastObservedPoseState = null; _lastSentPoseState = null; _pendingPoseState = null; _pendingPoseRefresh = false; return; } if (!_spectatorPoseStateProvider.TryGetCurrentSpectatorPose(out SpectatorPoseState state)) { state = new SpectatorPoseState(isSpectating: false, _transport.LocalClientId, 0uL, null, null, Vector3.zero, Quaternion.identity, _runtimeState.UtcNowTicks); } if (_lastObservedPoseState != null && _lastObservedPoseState.ApproximatelyEquals(state)) { if (NetworkSyncSamplingRules.ShouldRefreshUnchangedState(state.IsSpectating, _lastSentPoseState != null && _lastSentPoseState.IsSpectating, _runtimeState.UnscaledTime, _nextPoseRefreshTime)) { _pendingPoseState = state; _pendingPoseRefresh = true; } return; } _lastObservedPoseState = state; if (_lastSentPoseState != null && _lastSentPoseState.ApproximatelyEquals(state)) { _pendingPoseState = null; _pendingPoseRefresh = false; } else { _pendingPoseState = state; _pendingPoseRefresh = false; } if (IsPoseDebugEnabled()) { ModLog.Debug($"Observed spectator pose change: spectating={state.IsSpectating}, localClient={state.LocalClientId}, targetClient={FormatNullable(state.TargetClientId)}, position={FormatVector(state.Position)}."); } } private void UpdateLocalVoiceActivityIfDue() { if (!_config.EnableVoiceActivitySync.Value) { _lastObservedVoiceActivityState = null; _lastSentVoiceActivityState = null; _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; } else if (NetworkSyncSamplingRules.ShouldSample(_lastObservedVoiceActivityState != null, _runtimeState.UnscaledTime, _nextVoiceActivitySampleTime)) { UpdateLocalVoiceActivity(); _nextVoiceActivitySampleTime = NetworkSyncSamplingRules.ResolveNextSampleTime(_runtimeState.UnscaledTime, GetVoiceActivitySyncInterval()); } } private void TrySendPendingPoseState() { //IL_018a: Unknown result type (might be due to invalid IL or missing references) if (!_targetSyncReady || !_config.EnableSpectatorPoseSync.Value || _pendingPoseState == null) { return; } string reason; if (!_transport.IsRegistered || !_transport.IsNetworkAvailable) { StopNetworkingForLifecycle("custom messaging transport unavailable before pose send"); } else if (!_runtimeState.CanUseModNetworking(out reason)) { StopNetworkingForLifecycle(reason); } else if (!_pendingPoseRefresh && _lastSentPoseState != null && _lastSentPoseState.ApproximatelyEquals(_pendingPoseState)) { _pendingPoseState = null; } else { if (_runtimeState.UnscaledTime < _nextPoseSyncTime) { return; } List<ulong> spectatorTargetSyncPeerIds = _peerRegistry.GetSpectatorTargetSyncPeerIds(_transport.LocalClientId); if (spectatorTargetSyncPeerIds.Count == 0) { UpdateTargetSyncReadiness(); return; } SpectatorPoseSyncMessage spectatorPoseSyncMessage = new SpectatorPoseSyncMessage(1, _pendingPoseState, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorPose(spectatorPoseSyncMessage, spectatorTargetSyncPeerIds, out string reason2)) { _lastSentPoseState = _pendingPoseState; _pendingPoseState = null; _pendingPoseRefresh = false; _nextPoseSyncTime = _runtimeState.UnscaledTime + GetPoseSyncInterval(); _nextPoseRefreshTime = _runtimeState.UnscaledTime + GetPoseHeartbeatInterval(); if (IsPoseDebugEnabled()) { ModLog.Debug($"Spectator pose sent to {spectatorTargetSyncPeerIds.Count} peer(s): spectating={spectatorPoseSyncMessage.State.IsSpectating}, targetClient={FormatNullable(spectatorPoseSyncMessage.State.TargetClientId)}, position={FormatVector(spectatorPoseSyncMessage.State.Position)}."); } } else { Degrade("spectator pose send failed: " + reason2); } } } private void UpdateLocalVoiceActivity() { if (!_config.EnableVoiceActivitySync.Value) { _lastObservedVoiceActivityState = null; _lastSentVoiceActivityState = null; _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; } else { if (!TryGetLocalVoiceIdentity(out var clientId, out var slotId)) { return; } if (!_voiceActivityProvider.TryGetVoiceActivity(clientId, slotId, out VoiceActivityState state) || !state.HasData) { state = new VoiceActivityState(hasData: false, isSpeaking: false, 0f, 0f, clientId, slotId, _runtimeState.UtcNowTicks); } if (_lastObservedVoiceActivityState == null && !state.HasData) { _lastObservedVoiceActivityState = state; return; } if (VoiceActivitySyncRules.ApproximatelyEquals(_lastObservedVoiceActivityState, state)) { if (ShouldRefreshVoiceActivity(state)) { _pendingVoiceActivityState = state; _pendingVoiceActivityRefresh = true; } return; } _lastObservedVoiceActivityState = state; if (VoiceActivitySyncRules.ApproximatelyEquals(_lastSentVoiceActivityState, state)) { _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; } else { _pendingVoiceActivityState = state; _pendingVoiceActivityRefresh = false; } DebugObservedVoiceActivity(state); } } private void TrySendPendingVoiceActivityState() { if (!_targetSyncReady || !_config.EnableVoiceActivitySync.Value || _pendingVoiceActivityState == null) { return; } string reason; if (!_transport.IsRegistered || !_transport.IsNetworkAvailable) { StopNetworkingForLifecycle("custom messaging transport unavailable before voice activity send"); } else if (!_runtimeState.CanUseModNetworking(out reason)) { StopNetworkingForLifecycle(reason); } else if (!_pendingVoiceActivityRefresh && VoiceActivitySyncRules.ApproximatelyEquals(_lastSentVoiceActivityState, _pendingVoiceActivityState)) { _pendingVoiceActivityState = null; } else if (!_pendingVoiceActivityState.HasData && (_lastSentVoiceActivityState == null || !_lastSentVoiceActivityState.HasData)) { _pendingVoiceActivityState = null; } else { if (_runtimeState.UnscaledTime < _nextVoiceActivitySyncTime) { return; } List<ulong> voiceActivitySyncPeerIds = _peerRegistry.GetVoiceActivitySyncPeerIds(_transport.LocalClientId); if (voiceActivitySyncPeerIds.Count == 0) { UpdateTargetSyncReadiness(); return; } VoiceActivitySyncMessage voiceActivitySyncMessage = new VoiceActivitySyncMessage(1, _pendingVoiceActivityState, _runtimeState.UtcNowTicks); if (_transport.SendVoiceActivity(voiceActivitySyncMessage, voiceActivitySyncPeerIds, out string reason2)) { _lastSentVoiceActivityState = _pendingVoiceActivityState; _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; _nextVoiceActivitySyncTime = _runtimeState.UnscaledTime + GetVoiceActivitySyncInterval(); _nextVoiceActivityRefreshTime = _runtimeState.UnscaledTime + GetVoiceActivityRefreshInterval(); DebugSentVoiceActivity(voiceActivitySyncMessage.State, voiceActivitySyncPeerIds.Count); } else { Degrade("voice activity send failed: " + reason2); } } } private void OnCapabilityReceived(ulong senderClientId, ModPeerCapability capability) { if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableCapabilityHandshake.Value) { return; } if (!_runtimeState.CanUseModNetworking(out string reason)) { Debug($"Dropped capability from sender={senderClientId}: {reason}."); return; } if (capability.ClientId == _transport.LocalClientId) { _peerRegistry.RegisterLocal(CreateLocalCapability(_transport.LocalClientId)); return; } if (_transport.IsHost && capability.ClientId != senderClientId) { Debug($"Dropped capability with mismatched sender and peer id: sender={senderClientId}, peer={capability.ClientId}."); return; } if (!_transport.IsHost && senderClientId != capability.ClientId && senderClientId != _transport.ServerClientId) { Debug($"Dropped relayed capability from non-server sender={senderClientId}, peer={capability.ClientId}."); return; } bool flag = !_transport.IsHost && senderClientId != capability.ClientId; if (!ModPeerCapabilityRules.SupportsCurrentSpectatorTargetSync(capability)) { RemoveRemotePeerState(capability.ClientId, $"capability no longer compatible: sender={senderClientId}, targetSync={capability.SupportsSpectatorTargetSync}, voiceSync={capability.SupportsVoiceActivitySync}, voiceRoute={capability.SupportsSpectatorVoiceToTarget}, relayed={flag}"); return; } bool flag2 = _peerRegistry.RegisterRemote(capability, flag); if (flag2) { _noCompatiblePeerLocalOnly = false; _lifecycleState = NetworkLifecycleState.TransportRegistered; } Debug($"Capability received from sender={senderClientId}, peer={capability.ClientId}, protocol={capability.ProtocolVersion}, targetSync={capability.SupportsSpectatorTargetSync}, voiceSync={capability.SupportsVoiceActivitySync}, voiceRoute={capability.SupportsSpectatorVoiceToTarget}, compatible={flag2}, relayed={flag}."); if (flag2 && _transport.IsHost) { ModPeerCapability capability2 = CreateLocalCapability(_transport.LocalClientId); _peerRegistry.RegisterLocal(capability2); if (_transport.SendCapability(capability2, new ulong[1] { capability.ClientId }, out string reason2)) { Debug($"Capability reply sent to peer={capability.ClientId}."); } else { Debug($"Capability reply to peer={capability.ClientId} failed: {reason2}."); } SendLocalPeerIdentityToRecipients(new ulong[1] { capability.ClientId }); SendLocalSpectatorStateToPeer(capability.ClientId); PruneDisconnectedPeers(force: true); RelayCapabilityToCompatiblePeers(capability); RelayKnownCapabilitiesToPeer(capability.ClientId); RelayKnownSpectatorStatesToPeer(capability.ClientId); RelayKnownPeerIdentitiesToPeer(capability.ClientId); RelayKnownVoiceActivitiesToPeer(capability.ClientId); } } private void RemoveRemotePeerState(ulong peerId, string reason) { if (!_transport.IsRegistered || peerId != _transport.LocalClientId) { ModPeerCapability capability; SpectatorTargetState state; SpectatorPoseState state2; PeerIdentityState state3; VoiceActivityState state4; bool num = _peerRegistry.TryGetCapability(peerId, out capability) || _remoteTargetRegistry.TryGet(peerId, out state) || _remotePoseRegistry.TryGet(peerId, out state2) || _remoteIdentityRegistry.TryGet(peerId, out state3) || _remoteVoiceActivityRegistry.TryGet(peerId, out state4); _peerRegistry.Remove(peerId); _remoteTargetRegistry.Remove(peerId); _remotePoseRegistry.Remove(peerId); _remoteIdentityRegistry.Remove(peerId); _remoteVoiceActivityRegistry.Remove(peerId); if (num) { Debug($"Removed remote peer state for peer={peerId}: {reason}."); } else { Debug($"Ignored incompatible remote peer capability for peer={peerId}: {reason}."); } } } private void OnSpectatorTargetReceived(ulong senderClientId, SpectatorTargetState state) { if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableSpectatorTargetSync.Value) { return; } if (!_runtimeState.CanUseModNetworking(out string reason)) { Debug($"Dropped spectator target from sender={senderClientId}: {reason}."); } else if (state.LocalClientId != _transport.LocalClientId) { if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.LocalClientId, _peerRegistry, out string reason2)) { Debug($"Dropped spectator target from sender={senderClientId}, peer={state.LocalClientId}: {reason2}."); return; } _remoteTargetRegistry.Update(state); Debug($"Spectator target received from peer={state.LocalClientId}: spectating={state.IsSpectating}, targetClient={FormatNullable(state.TargetClientId)}, targetSlot={FormatNullable(state.TargetPlayerSlotId)}."); RelaySpectatorTargetState(senderClientId, state); } } private void OnSpectatorPoseReceived(ulong senderClientId, SpectatorPoseState state) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableSpectatorPoseSync.Value) { return; } if (!_runtimeState.CanUseModNetworking(out string reason)) { Debug($"Dropped spectator pose from sender={senderClientId}: {reason}."); } else { if (state.LocalClientId == _transport.LocalClientId) { return; } if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.LocalClientId, _peerRegistry, out string reason2)) { Debug($"Dropped spectator pose from sender={senderClientId}, peer={state.LocalClientId}: {reason2}."); return; } _remotePoseRegistry.Update(state); if (IsPoseDebugEnabled()) { ModLog.Debug($"Spectator pose received from peer={state.LocalClientId}: spectating={state.IsSpectating}, targetClient={FormatNullable(state.TargetClientId)}, position={FormatVector(state.Position)}."); } RelaySpectatorPoseState(senderClientId, state); } } private void OnPeerIdentityReceived(ulong senderClientId, PeerIdentityState state) { if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableCapabilityHandshake.Value) { return; } if (!_runtimeState.CanUseModNetworking(out string reason)) { Debug($"Dropped peer identity from sender={senderClientId}: {reason}."); } else if (state.ClientId != _transport.LocalClientId) { if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.ClientId, _peerRegistry, out string reason2)) { Debug($"Dropped peer identity from sender={senderClientId}, peer={state.ClientId}: {reason2}."); return; } _remoteIdentityRegistry.Update(state); Debug($"Peer identity received from peer={state.ClientId}: slot={state.PlayerSlotId}, name={state.DisplayName}, voiceName={FormatVoiceName(state.VoicePlayerName)}."); RelayPeerIdentity(senderClientId, state); } } private void OnVoiceActivityReceived(ulong senderClientId, VoiceActivityState state) { if (!_initialized || !_config.EnableNetworking.Value || !_config.EnableVoiceActivitySync.Value) { return; } if (!_runtimeState.CanUseModNetworking(out string reason)) { DebugVoice($"Dropped voice activity from sender={senderClientId}: {reason}."); } else if (state.ClientId != _transport.LocalClientId) { if (!HostRelayPlanner.CanAcceptSpectatorState(_transport.IsHost, _transport.LocalClientId, _transport.ServerClientId, senderClientId, state.ClientId, _peerRegistry, out string reason2)) { DebugVoice($"Dropped voice activity from sender={senderClientId}, peer={state.ClientId}: {reason2}."); return; } _remoteVoiceActivityRegistry.Update(state, _runtimeState.UtcNowTicks); DebugReceivedVoiceActivity(senderClientId, state); RelayVoiceActivityState(senderClientId, state); } } private void RelayCapabilityToCompatiblePeers(ModPeerCapability capability) { if (!_transport.IsHost || !_config.EnableHostRelay.Value) { return; } IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, capability.ClientId); if (relayRecipients.Count != 0) { if (_transport.SendCapability(capability, relayRecipients, out string reason)) { Debug($"Relayed capability for peer={capability.ClientId} to {relayRecipients.Count} peer(s)."); } else { Debug($"Capability relay for peer={capability.ClientId} failed: {reason}."); } } } private void RelayKnownCapabilitiesToPeer(ulong recipientClientId) { if (!_transport.IsHost || !_config.EnableHostRelay.Value) { return; } foreach (ModPeerCapability item in _peerRegistry.GetCapabilitiesSnapshot()) { if (item.ClientId != _transport.LocalClientId && item.ClientId != recipientClientId && item.HandshakeComplete && item.ProtocolVersion == 1 && item.SupportsCapabilityHandshake && item.SupportsSpectatorTargetSync) { if (_transport.SendCapability(item, new ulong[1] { recipientClientId }, out string reason)) { Debug($"Relayed known capability for peer={item.ClientId} to peer={recipientClientId}."); } else { Debug($"Known capability relay for peer={item.ClientId} to peer={recipientClientId} failed: {reason}."); } } } } private void RelayKnownSpectatorStatesToPeer(ulong recipientClientId) { if (!_transport.IsHost || !_config.EnableHostRelay.Value) { return; } foreach (SpectatorTargetState item in _remoteTargetRegistry.GetSnapshot()) { if (item.LocalClientId == _transport.LocalClientId || item.LocalClientId == recipientClientId) { continue; } if (!IsConnectedForKnownState(item.LocalClientId)) { Debug($"Skipped known spectator target relay for peer={item.LocalClientId} to peer={recipientClientId}: origin is not connected."); continue; } if (item.TargetClientId.HasValue && !IsConnectedForKnownState(item.TargetClientId.Value)) { Debug($"Skipped known spectator target relay for peer={item.LocalClientId} to peer={recipientClientId}: target={item.TargetClientId.Value} is not connected."); continue; } SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, item, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorTarget(message, new ulong[1] { recipientClientId }, out string reason)) { Debug($"Relayed known spectator target for peer={item.LocalClientId} to peer={recipientClientId}."); } else { Debug($"Known spectator target relay for peer={item.LocalClientId} to peer={recipientClientId} failed: {reason}."); } } if (!_config.EnableSpectatorPoseSync.Value) { return; } foreach (SpectatorPoseState item2 in _remotePoseRegistry.GetSnapshot()) { if (item2.LocalClientId == _transport.LocalClientId || item2.LocalClientId == recipientClientId) { continue; } if (!IsConnectedForKnownState(item2.LocalClientId)) { DebugPose($"Skipped known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId}: origin is not connected."); continue; } if (item2.TargetClientId.HasValue && !IsConnectedForKnownState(item2.TargetClientId.Value)) { DebugPose($"Skipped known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId}: target={item2.TargetClientId.Value} is not connected."); continue; } if (!HasMatchingStoredTarget(item2)) { DebugPose($"Skipped known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId}: no matching active target."); continue; } SpectatorPoseSyncMessage message2 = new SpectatorPoseSyncMessage(1, item2, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorPose(message2, new ulong[1] { recipientClientId }, out string reason2)) { DebugPose($"Relayed known spectator pose for peer={item2.LocalClientId} to peer={recipientClientId}."); } else { DebugPose($"Known spectator pose relay for peer={item2.LocalClientId} to peer={recipientClientId} failed: {reason2}."); } } } private bool HasMatchingStoredPose(SpectatorTargetState targetState) { if (_remotePoseRegistry.TryGet(targetState.LocalClientId, out SpectatorPoseState state) && state.IsSpectating && state.TargetClientId == targetState.TargetClientId) { return state.TargetPlayerSlotId == targetState.TargetPlayerSlotId; } return false; } private bool HasMatchingStoredTarget(SpectatorPoseState poseState) { if (_remoteTargetRegistry.TryGet(poseState.LocalClientId, out SpectatorTargetState state) && state.IsSpectating && state.TargetClientId == poseState.TargetClientId) { return state.TargetPlayerSlotId == poseState.TargetPlayerSlotId; } return false; } private bool IsConnectedForKnownState(ulong clientId) { if (clientId != _transport.LocalClientId) { return _transport.IsPeerConnected(clientId); } return true; } private bool IsConnectedForRelayState(ulong originClientId, bool isSpectating, ulong? targetClientId, out string reason) { if (!IsConnectedForKnownState(originClientId)) { reason = "origin is not connected"; return false; } if (isSpectating && targetClientId.HasValue && !IsConnectedForKnownState(targetClientId.Value)) { reason = $"target={targetClientId.Value} is not connected"; return false; } reason = string.Empty; return true; } private void RelaySpectatorTargetState(ulong senderClientId, SpectatorTargetState state) { if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.LocalClientId) { return; } if (!IsConnectedForRelayState(state.LocalClientId, state.IsSpectating, state.TargetClientId, out string reason)) { Debug($"Skipped spectator target relay from peer={state.LocalClientId}: {reason}."); return; } IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, state.LocalClientId); if (relayRecipients.Count != 0) { SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, state, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorTarget(message, relayRecipients, out string reason2)) { Debug($"Relayed spectator target from peer={state.LocalClientId} to {relayRecipients.Count} peer(s)."); } else { Debug($"Spectator target relay from peer={state.LocalClientId} failed: {reason2}."); } } } private void RelaySpectatorPoseState(ulong senderClientId, SpectatorPoseState state) { if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.LocalClientId) { return; } if (!IsConnectedForRelayState(state.LocalClientId, state.IsSpectating, state.TargetClientId, out string reason)) { DebugPose($"Skipped spectator pose relay from peer={state.LocalClientId}: {reason}."); return; } IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, state.LocalClientId); if (relayRecipients.Count != 0) { SpectatorPoseSyncMessage message = new SpectatorPoseSyncMessage(1, state, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorPose(message, relayRecipients, out string reason2)) { DebugPose($"Relayed spectator pose from peer={state.LocalClientId} to {relayRecipients.Count} peer(s)."); } else { DebugPose($"Spectator pose relay from peer={state.LocalClientId} failed: {reason2}."); } } } private void RelayVoiceActivityState(ulong senderClientId, VoiceActivityState state) { if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.ClientId) { return; } if (!IsConnectedForKnownState(state.ClientId)) { DebugVoice($"Skipped voice activity relay from peer={state.ClientId}: origin is not connected."); return; } IReadOnlyList<ulong> voiceActivityRelayRecipients = HostRelayPlanner.GetVoiceActivityRelayRecipients(_peerRegistry, _transport.LocalClientId, state.ClientId); if (voiceActivityRelayRecipients.Count != 0) { VoiceActivitySyncMessage message = new VoiceActivitySyncMessage(1, state, _runtimeState.UtcNowTicks); if (_transport.SendVoiceActivity(message, voiceActivityRelayRecipients, out string reason)) { DebugRelayedVoiceActivity(state, voiceActivityRelayRecipients.Count); } else { DebugVoice($"Voice activity relay from peer={state.ClientId} failed: {reason}."); } } } private void RelayDisconnectedPeerCleanup(ulong peerId) { //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) if (!_transport.IsHost || !_config.EnableHostRelay.Value) { return; } IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, peerId); if (relayRecipients.Count == 0) { return; } ModPeerCapability capability = new ModPeerCapability(peerId, 1, supportsCapabilityHandshake: false, supportsSpectatorTargetSync: false, handshakeComplete: false, _runtimeState.UtcNowTicks); if (_transport.SendCapability(capability, relayRecipients, out string reason)) { Debug($"Relayed disconnected peer capability cleanup for peer={peerId} to {relayRecipients.Count} peer(s)."); } else { Debug($"Disconnected peer capability cleanup relay for peer={peerId} failed: {reason}."); } SpectatorTargetState state = new SpectatorTargetState(isSpectating: false, peerId, 0uL, null, null, _runtimeState.UtcNowTicks); SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, state, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorTarget(message, relayRecipients, out string reason2)) { Debug($"Relayed disconnected peer target cleanup for peer={peerId} to {relayRecipients.Count} peer(s)."); } else { Debug($"Disconnected peer target cleanup relay for peer={peerId} failed: {reason2}."); } if (_config.EnableVoiceActivitySync.Value) { IReadOnlyList<ulong> voiceActivityRelayRecipients = HostRelayPlanner.GetVoiceActivityRelayRecipients(_peerRegistry, _transport.LocalClientId, peerId); VoiceActivityState state2 = new VoiceActivityState(hasData: false, isSpeaking: false, 0f, 0f, peerId, 0uL, _runtimeState.UtcNowTicks); VoiceActivitySyncMessage message2 = new VoiceActivitySyncMessage(1, state2, _runtimeState.UtcNowTicks); if (voiceActivityRelayRecipients.Count > 0) { if (_transport.SendVoiceActivity(message2, voiceActivityRelayRecipients, out string reason3)) { DebugVoice($"Relayed disconnected peer voice activity cleanup for peer={peerId} to {voiceActivityRelayRecipients.Count} peer(s)."); } else { DebugVoice($"Disconnected peer voice activity cleanup relay for peer={peerId} failed: {reason3}."); } } } if (_config.EnableSpectatorPoseSync.Value) { SpectatorPoseState state3 = new SpectatorPoseState(isSpectating: false, peerId, 0uL, null, null, Vector3.zero, Quaternion.identity, _runtimeState.UtcNowTicks); SpectatorPoseSyncMessage message3 = new SpectatorPoseSyncMessage(1, state3, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorPose(message3, relayRecipients, out string reason4)) { DebugPose($"Relayed disconnected peer pose cleanup for peer={peerId} to {relayRecipients.Count} peer(s)."); } else { DebugPose($"Disconnected peer pose cleanup relay for peer={peerId} failed: {reason4}."); } } } private void SendLocalPeerIdentityToRecipients(IEnumerable<ulong> recipients) { if (_peerIdentityStateProvider.TryGetLocalPeerIdentity(out PeerIdentityState state) && !string.IsNullOrWhiteSpace(state.DisplayName)) { if (_transport.SendPeerIdentity(state, recipients, out string reason)) { _lastSentIdentityState = state; Debug($"Peer identity reply sent: client={state.ClientId}, slot={state.PlayerSlotId}, name={state.DisplayName}, voiceName={FormatVoiceName(state.VoicePlayerName)}."); } else { Debug("Peer identity reply failed: " + reason + "."); } } } private void SendLocalSpectatorStateToPeer(ulong recipientClientId) { if (!_config.EnableSpectatorTargetSync.Value || !_spectatorTargetStateProvider.TryGetCurrentSpectatorTarget(out SpectatorTargetState state) || !state.IsSpectating) { return; } if (!IsConnectedForRelayState(state.LocalClientId, state.IsSpectating, state.TargetClientId, out string reason)) { Debug($"Skipped local spectator target replay to peer={recipientClientId}: {reason}."); return; } SpectatorTargetSyncMessage message = new SpectatorTargetSyncMessage(1, state, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorTarget(message, new ulong[1] { recipientClientId }, out string reason2)) { Debug($"Replayed local spectator target to peer={recipientClientId}."); } else { Debug($"Local spectator target replay to peer={recipientClientId} failed: {reason2}."); } if (_config.EnableSpectatorPoseSync.Value && _spectatorPoseStateProvider.TryGetCurrentSpectatorPose(out SpectatorPoseState state2) && state2.IsSpectating && HasMatchingLocalTarget(state, state2)) { SpectatorPoseSyncMessage message2 = new SpectatorPoseSyncMessage(1, state2, _runtimeState.UtcNowTicks); if (_transport.SendSpectatorPose(message2, new ulong[1] { recipientClientId }, out string reason3)) { DebugPose($"Replayed local spectator pose to peer={recipientClientId}."); } else { DebugPose($"Local spectator pose replay to peer={recipientClientId} failed: {reason3}."); } } } private static bool HasMatchingLocalTarget(SpectatorTargetState targetState, SpectatorPoseState poseState) { if (targetState.IsSpectating && poseState.TargetClientId == targetState.TargetClientId) { return poseState.TargetPlayerSlotId == targetState.TargetPlayerSlotId; } return false; } private void RelayPeerIdentity(ulong senderClientId, PeerIdentityState state) { if (!_transport.IsHost || !_config.EnableHostRelay.Value || senderClientId != state.ClientId) { return; } IReadOnlyList<ulong> relayRecipients = HostRelayPlanner.GetRelayRecipients(_peerRegistry, _transport.LocalClientId, state.ClientId); if (relayRecipients.Count != 0) { if (_transport.SendPeerIdentity(state, relayRecipients, out string reason)) { Debug($"Relayed peer identity from peer={state.ClientId} to {relayRecipients.Count} peer(s)."); } else { Debug($"Peer identity relay from peer={state.ClientId} failed: {reason}."); } } } private void RelayKnownPeerIdentitiesToPeer(ulong recipientClientId) { if (!_transport.IsHost || !_config.EnableHostRelay.Value) { return; } foreach (PeerIdentityState item in _remoteIdentityRegistry.GetSnapshot()) { if (item.ClientId != _transport.LocalClientId && item.ClientId != recipientClientId) { if (_transport.SendPeerIdentity(item, new ulong[1] { recipientClientId }, out string reason)) { Debug($"Relayed known peer identity for peer={item.ClientId} to peer={recipientClientId}."); } else { Debug($"Known peer identity relay for peer={item.ClientId} to peer={recipientClientId} failed: {reason}."); } } } } private void RelayKnownVoiceActivitiesToPeer(ulong recipientClientId) { if (!_transport.IsHost || !_config.EnableHostRelay.Value || !_config.EnableVoiceActivitySync.Value || !_peerRegistry.TryGetCapability(recipientClientId, out ModPeerCapability capability) || !ModPeerCapabilityRules.SupportsCurrentVoiceActivitySync(capability)) { return; } foreach (VoiceActivityState item in _remoteVoiceActivityRegistry.GetSnapshot()) { if (item.ClientId == _transport.LocalClientId || item.ClientId == recipientClientId) { continue; } if (!IsConnectedForKnownState(item.ClientId)) { DebugVoice($"Skipped known voice activity relay for peer={item.ClientId} to peer={recipientClientId}: origin is not connected."); continue; } VoiceActivitySyncMessage message = new VoiceActivitySyncMessage(1, item, _runtimeState.UtcNowTicks); if (_transport.SendVoiceActivity(message, new ulong[1] { recipientClientId }, out string reason)) { DebugVoice($"Relayed known voice activity for peer={item.ClientId} to peer={recipientClientId}."); } else { DebugVoice($"Known voice activity relay for peer={item.ClientId} to peer={recipientClientId} failed: {reason}."); } } } private void Degrade(string reason) { _transport.Unregister(); ClearNetworkState(); _lifecycleState = NetworkLifecycleState.LocalOnly; if (!(_lastDegradationReason == reason)) { _lastDegradationReason = reason; Debug("Networking degraded to local-only mode: " + reason + "."); } } private void StopNetworkingForLifecycle(string reason) { if (_transport.IsRegistered) { _transport.Unregister(); } ClearNetworkState(); _lifecycleState = NetworkLifecycleState.LocalOnly; if (!(_lastDegradationReason == reason)) { _lastDegradationReason = reason; if (_runtimeState.FrameCount >= _nextLifecycleDebugFrame) { _nextLifecycleDebugFrame = _runtimeState.FrameCount + 120; Debug("Networking stopped because shutdown/disconnect was detected: " + reason + "."); } } } private void ClearNetworkState() { _peerRegistry.Clear(); _remoteTargetRegistry.Clear(); _remotePoseRegistry.Clear(); _remoteIdentityRegistry.Clear(); _remoteVoiceActivityRegistry.Clear(); _lastObservedTargetState = null; _lastSentTargetState = null; _pendingTargetState = null; _lastObservedPoseState = null; _lastSentPoseState = null; _pendingPoseState = null; _pendingPoseRefresh = false; _lastObservedVoiceActivityState = null; _lastSentVoiceActivityState = null; _pendingVoiceActivityState = null; _pendingVoiceActivityRefresh = false; _lastSentIdentityState = null; _networkAvailable = false; _targetSyncReady = false; _hasCompatibleModPeer = false; _noCompatiblePeerLocalOnly = false; _capabilitySent = false; _capabilityProbeSentRealtime = -1f; _lastLocalClientId = null; _nextPeerPruneTime = 0f; _transportRegisteredRealtime = 0f; _transportRegisteredFrame = -1; _lastTargetSyncWaitReason = null; _nextTargetSyncTime = 0f; _nextPoseSyncTime = 0f; _nextVoiceActivitySyncTime = 0f; _nextTargetSampleTime = 0f; _nextPoseSampleTime = 0f; _nextPoseRefreshTime = 0f; _nextVoiceActivitySampleTime = 0f; _nextVoiceActivityRefreshTime = 0f; _voiceDebugLimiter.Clear(); } private ModPeerCapability CreateLocalCapability(ulong localClientId) { return new ModPeerCapability(localClientId, 1, _config.EnableCapabilityHandshake.Value, _config.EnableSpectatorTargetSync.Value, _config.EnableCapabilityHandshake.Value, _runtimeState.UtcNowTicks, _config.EnableVoiceActivitySync.Value, _config.EnableSpectatorVoiceToTarget.Value); } private void Debug(string message) { if (_config.DebugNetworkMessages.Value) { ModLog.Debug(message); } } private void DebugPose(string message) { if (IsPoseDebugEnabled()) { ModLog.Debug(message); } } private bool IsPoseDebugEnabled() { if (_config.DebugNetworkMessages.Value) { return _config.DebugPoseMessages.Value; } return false; } private float GetPoseSyncInterval() { return Mathf.Max(0.02f, _config.SpectatorPoseSyncInterval.Value); } private float GetPoseHeartbeatInterval() { return Mathf.Max(0.5f, GetPoseSyncInterval() * 5f); } private float GetVoiceActivitySyncInterval() { return NetworkSyncSamplingRules.ResolveVoiceActivitySyncInterval(_config.VoiceActivitySyncInterval.Value); } private float GetVoiceActivityRefreshInterval() { return Mathf.Clamp(Mathf.Max(0.1f, _config.VoiceActivityStaleSeconds.Value) * 0.5f, 0.1f, 0.25f); } private bool ShouldRefreshVoiceActivity(VoiceActivityState state) { if (!state.HasData || (!state.IsSpeaking && state.Amplitude <= 0f)) { return false; } return _runtimeState.UnscaledTime >= _nextVoiceActivityRefreshTime; } private bool TryGetLocalVoiceIdentity(out ulong clientId, out ulong slotId) { if (_peerIdentityStateProvider.TryGetLocalPeerIdentity(out PeerIdentityState state)) { clientId = state.ClientId; slotId = state.PlayerSlotId; return true; } if (_transport.IsRegistered) { clientId = _transport.LocalClientId; slotId = 0uL; return true; } clientId = 0uL; slotId = 0uL; return false; } private void DebugVoice(string message) { if (IsVoiceDebugEnabled()) { ModLog.Debug(message); } } private void DebugObservedVoiceActivity(VoiceActivityState state) { if (ShouldLogVoiceActivity("observed", state.ClientId, state, isRelayed: false)) { ModLog.Debug($"Observed local voice activity change: hasData={state.HasData}, speaking={state.IsSpeaking}, amplitude={state.Amplitude:0.00}, client={state.ClientId}, slot={state.SlotId}."); } } private void DebugSentVoiceActivity(VoiceActivityState state, int recipientCount) { if (ShouldLogVoiceActivity("sent", state.ClientId, state, isRelayed: false)) { ModLog.Debug($"Voice activity sent to {recipientCount} peer(s): hasData={state.HasData}, speaking={state.IsSpeaking}, amplitude={state.Amplitude:0.00}."); } } private void DebugReceivedVoiceActivity(ulong senderClientId, VoiceActivityState state) { bool flag = senderClientId != state.ClientId; if (ShouldLogVoiceActivity("received", state.ClientId, state, flag)) { ModLog.Debug($"Voice activity received from peer={state.ClientId}: hasData={state.HasData}, speaking={state.IsSpeaking}, amplitude={state.Amplitude:0.00}, relayed={flag}."); } } private void DebugRelayedVoiceActivity(VoiceActivityState state, int recipientCount) { if (ShouldLogVoiceActivity("relayed", state.ClientId, state, isRelayed: true)) { ModLog.Debug($"Relayed voice activity from peer={state.ClientId} to {recipientCount} peer(s)."); } } private bool ShouldLogVoiceActivity(string category, ulong peerId, VoiceActivityState state, bool isRelayed) { if (IsVoiceDebugEnabled()) { return _voiceDebugLimiter.ShouldLog(category, peerId, _runtimeState.FrameCount, state, isRelayed); } return false; } private bool IsVoiceDebugEnabled() { if (_config.DebugNetworkMessages.Value) { return _config.DebugVoiceActivitySync.Value; } return false; } private void DebugCapabilityDelay(string reason) { if (_config.DebugNetworkMessages.Value && _runtimeState.FrameCount >= _nextCapabilityDelayDebugFrame) { _nextCapabilityDelayDebugFrame = _runtimeState.FrameCount + 120; ModLog.Debug("Capability delayed because network is not stable yet: " + reason + "."); } } private static string FormatNullable(ulong? value) { if (!value.HasValue) { return "none"; } return value.Value.ToString(); } private static string FormatVector(Vector3 value) { //IL_0005: 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_001b: Unknown result type (might be due to invalid IL or missing references) return $"({value.x:0.00}, {value.y:0.00}, {value.z:0.00})"; } private static string FormatVoiceName(string value) { if (!string.IsNullOrWhiteSpace(value)) { return "present"; } return "none"; } private static bool IdentityEquals(PeerIdentityState? left, PeerIdentityState right) { if (left != null && left.ClientId == right.ClientId && left.PlayerSlotId == right.PlayerSlotId && string.Equals(left.DisplayName, right.DisplayName, StringComparison.Ordinal)) { return string.Equals(left.VoicePlayerName, right.VoicePlayerName, StringComparison.Ordinal); } return false; } } public static class HostRelayPlanner { public static IReadOnlyList<ulong> GetRelayRecipients(RemotePeerRegistry registry, ulong hostClientId, ulong originClientId) { List<ulong> spectatorTargetSyncPeerIds = registry.GetSpectatorTargetSyncPeerIds(hostClientId); for (int num = spectatorTargetSyncPeerIds.Count - 1; num >= 0; num--) { ulong num2 = spectatorTargetSyncPeerIds[num]; if (num2 == hostClientId || num2 == originClientId) { spectatorTargetSyncPeerIds.RemoveAt(num); } } return spectatorTargetSyncPeerIds; } public static IReadOnlyList<ulong> GetVoiceActivityRelayRecipients(RemotePeerRegistry registry, ulong hostClientId, ulong originClientId) { List<ulong> voiceActivitySyncPeerIds = registry.GetVoiceActivitySyncPeerIds(hostClientId); for (int num = voiceActivitySyncPeerIds.Count - 1; num >= 0; num--) { ulong num2 = voiceActivitySyncPeerIds[num]; if (num2 == hostClientId || num2 == originClientId) { voiceActivitySyncPeerIds.RemoveAt(num); } } return voiceActivitySyncPeerIds; } public static bool CanAcceptSpectatorState(bool isHost, ulong localClientId, ulong serverClientId, ulong senderClientId, ulong originClientId, RemotePeerRegistry registry, out string reason) { if (originClientId == localClientId) { reason = "origin is local client"; return false; } if (!registry.IsSpectatorTargetSyncPeer(originClientId)) { reason = "origin is not a compatible spectator sync peer"; return false; } if (isHost) { if (senderClientId == originClientId) { reason = string.Empty; return true; } reason = "host rejected state where sender does not match origin"; return false; } if (senderClientId == originClientId && !registry.IsRelayedPeer(originClientId)) { reason = string.Empty; return true; } if (senderClientId == serverClientId && registry.IsRelayedPeer(originClientId)) { reason = string.Empty; return true; } reason = "client rejected non-host relayed foreign origin"; return false; } } public interface IEnhancedSpectatorNetworkService { bool IsNetworkAvailable { get; } bool IsTargetSyncEnabled { get; } bool HasCompatibleModPeer { get; } NetworkLifecycleState LifecycleState { get; } int RemotePeerIdentityRevision { get; } int RemoteSpectatorTargetRevision { get; } void Initialize(); void Tick(); void Dispose(); bool TryGetPeerCapability(ulong clientId, out ModPeerCapability capability); bool TryGetRemoteSpectatorTarget(ulong clientId, out SpectatorTargetState state); bool TryGetRemoteSpectatorPose(ulong clientId, out SpectatorPoseState state); bool TryGetRemotePeerIdentity(ulong clientId, out PeerIdentityState state); bool TryGetRemoteVoiceActivity(ulong clientId, out VoiceActivityState state); IReadOnlyList<SpectatorTargetState> GetRemoteSpectatorTargets(); void CopyRemoteSpectatorTargetsTo(List<SpectatorTargetState> destination); IReadOnlyList<SpectatorPoseState> GetRemoteSpectatorPoses(); IReadOnlyList<PeerIdentityState> GetRemotePeerIdentities(); void CopyRemotePeerIdentitiesTo(List<PeerIdentityState> destination); IReadOnlyList<VoiceActivityState> GetRemoteVoiceActivities(); IReadOnlyList<ModPeerCapability> GetKnownModdedPeers(); } public interface IModNetworkTransport : IDisposable { bool IsRegistered { get; } bool IsNetworkAvailable { get; } bool IsHost { get; } ulong LocalClientId { get; } ulong ServerClientId { get; } bool IsPeerConnected(ulong clientId); bool TryRegister(Action<ulong, ModPeerCapability> capabilityReceived, Action<ulong, SpectatorTargetState> spectatorTargetReceived, Action<ulong, SpectatorPoseState> spectatorPoseReceived, Action<ulong, PeerIdentityState> peerIdentityReceived, Action<ulong, VoiceActivityState> voiceActivityReceived, out string reason); void Unregister(); bool SendCapability(ModPeerCapability capability, IEnumerable<ulong>? recipients, out string reason); bool SendSpectatorTarget(SpectatorTargetSyncMessage message, IEnumerable<ulong> recipients, out string reason); bool SendSpectatorPose(SpectatorPoseSyncMessage message, IEnumerable<ulong> recipients, out string reason); bool SendPeerIdentity(PeerIdentityState state, IEnumerable<ulong>? recipients, out string reason); bool SendVoiceActivity(VoiceActivitySyncMessage message, IEnumerable<ulong> recipients, out string reason); } public interface INetworkRuntimeState { int FrameCount { get; } float RealtimeSinceStartup { get; } float UnscaledTime { get; } long UtcNowTicks { get; } bool CanUseModNetworking(out string reason); } public sealed class UnityNetworkRuntimeState : INetworkRuntimeState { public static UnityNetworkRuntimeState Instance { get; } = new UnityNetworkRuntimeState(); public int FrameCount => Time.frameCount; public float RealtimeSinceStartup => Time.realtimeSinceStartup; public float UnscaledTime => Time.unscaledTime; public long UtcNowTicks => DateTime.UtcNow.Ticks; private UnityNetworkRuntimeState() { } public bool CanUseModNetworking(out string reason) { return RuntimeConnectionState.CanUseModNetworking(out reason); } } public interface IPeerIdentityStateProvider { bool TryGetLocalPeerIdentity(out PeerIdentityState state); } public static class ModNetworkConstants { public const int ProtocolVersion = 1; public const string CapabilityMessageName = "EnhancedSpectator.Capability.V1"; public const string SpectatorTargetMessageName = "EnhancedSpectator.SpectatorTarget.V1"; public const string SpectatorPoseMessageName = "EnhancedSpectator.SpectatorPose.V1"; public const string PeerIdentityMessageName = "EnhancedSpectator.PeerIdentity.V1"; public const string VoiceActivityMessageName = "EnhancedSpectator.VoiceActivity.V1"; public const double TargetSyncMinIntervalSeconds = 0.1; public const double VoiceActivitySyncMinIntervalSeconds = 0.066; } public static class ModNetworkSerializer { public static int CapabilityMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<long>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<bool>(); public static int SpectatorTargetMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<long>(); public static int SpectatorPoseMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<float>() * 7 + FastBufferWriter.GetWriteSize<long>(); public static int PeerIdentityMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<FixedString64Bytes>() + FastBufferWriter.GetWriteSize<long>() + FastBufferWriter.GetWriteSize<FixedString64Bytes>(); public static int VoiceActivityMessageSize => FastBufferWriter.GetWriteSize<int>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<bool>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<ulong>() + FastBufferWriter.GetWriteSize<float>() + FastBufferWriter.GetWriteSize<float>() + FastBufferWriter.GetWriteSize<long>(); public static void WriteCapability(ref FastBufferWriter writer, ModPeerCapability capability) { //IL_000c: 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_0024: 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_003c: 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_0054: 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_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: 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_00a3: Unknown result type (might be due to invalid IL or missing references) int protocolVersion = capability.ProtocolVersion; ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(ref protocolVersion, default(ForPrimitives)); ulong clientId = capability.ClientId; ((FastBufferWriter)(ref writer)).WriteValueSafe<ulong>(ref clientId, default(ForPrimitives)); bool supportsCapabilityHandshake = capability.SupportsCapabilityHandshake; ((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives)); supportsCapabilityHandshake = capability.SupportsSpectatorTargetSync; ((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives)); long lastSeenTicks = capability.LastSeenTicks; ((FastBufferWriter)(ref writer)).WriteValueSafe<long>(ref lastSeenTicks, default(ForPrimitives)); supportsCapabilityHandshake = capability.SupportsVoiceActivitySync; ((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives)); supportsCapabilityHandshake = capability.SupportsSpectatorVoiceToTarget; ((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref supportsCapabilityHandshake, default(ForPrimitives)); } public static bool TryReadCapability(ref FastBufferReader reader, out ModPeerCapability capability, out string reason) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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_0045: 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_0057: 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_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: 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_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) capability = null; reason = string.Empty; try { int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); if (num != 1) { reason = $"unsupported protocol version {num}"; return false; } ulong clientId = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref clientId, default(ForPrimitives)); bool flag = default(bool); ((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag, default(ForPrimitives)); bool supportsSpectat