Decompiled source of repo webcam v1.2.1

RepoWebcamMod.dll

Decompiled 13 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using RepoWebcamMod.Logging;
using RepoWebcamMod.Networking;
using RepoWebcamMod.Networking.Steam;
using RepoWebcamMod.Video;
using Steamworks;
using Steamworks.Data;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("RepoWebcamMod")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.2.1.0")]
[assembly: AssemblyInformationalVersion("1.2.1+2349fc2bd2b4c47bd15a67007d712f0d2e03af2a")]
[assembly: AssemblyProduct("RepoWebcamMod")]
[assembly: AssemblyTitle("RepoWebcamMod")]
[assembly: AssemblyVersion("1.2.1.0")]
[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 RepoWebcamMod
{
	public static class PluginConstants
	{
		public const string PluginGuid = "com.skycheg.webcam";

		public const string PluginName = "REPO Webcam Mod";

		public const string PluginVersion = "1.2.1";

		public const int SteamTransportVirtualPort = 31573;
	}
	public static class RepoWebcamBootstrap
	{
		private static readonly object Sync = new object();

		private static ManualLogSource? _logger;

		private static ConfigFile? _config;

		private static bool _initialized;

		public static void Initialize(ManualLogSource logger, ConfigFile config)
		{
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			lock (Sync)
			{
				if (_initialized)
				{
					return;
				}
				_logger = logger;
				_config = config;
				SceneManager.activeSceneChanged += OnActiveSceneChanged;
				SceneManager.sceneLoaded += OnSceneLoaded;
				_initialized = true;
			}
			Scene activeScene = SceneManager.GetActiveScene();
			TryEnsureRuntime("initialize", ((Scene)(ref activeScene)).name);
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			TryEnsureRuntime("scene-loaded", ((Scene)(ref scene)).name);
		}

		private static void OnActiveSceneChanged(Scene previousScene, Scene nextScene)
		{
			TryEnsureRuntime("scene-changed", ((Scene)(ref nextScene)).name);
		}

		private static void TryEnsureRuntime(string reason, string? sceneName)
		{
			if (!string.IsNullOrWhiteSpace(sceneName) && !((Object)(object)Object.FindObjectOfType<RepoWebcamRuntime>() != (Object)null) && _config != null)
			{
				RepoWebcamRuntime.EnsureInstance(_config);
				ManualLogSource? logger = _logger;
				if (logger != null)
				{
					logger.LogInfo((object)("Runtime created via bootstrap (" + reason + ") in scene '" + sceneName + "'."));
				}
			}
		}
	}
	[BepInPlugin("com.skycheg.webcam", "REPO Webcam Mod", "1.2.1")]
	public sealed class RepoWebcamPlugin : BaseUnityPlugin
	{
		private void Awake()
		{
			RepoWebcamBootstrap.Initialize(((BaseUnityPlugin)this).Logger, ((BaseUnityPlugin)this).Config);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"REPO Webcam Mod bootstrap initialized.");
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? Browsable { get; set; }

		public string? Category { get; set; }

		public object? DefaultValue { get; set; }

		public bool? DispName { get; set; }

		public int? Order { get; set; }

		public bool? ReadOnly { get; set; }

		public bool? IsAdvanced { get; set; }

		public Action? CustomDrawer { get; set; }

		public bool? HideDefaultButton { get; set; }

		public bool? HideSettingName { get; set; }
	}
	public sealed class RepoWebcamRuntime : MonoBehaviour
	{
		private sealed class RemotePeerState
		{
			public string PlayerId { get; }

			public GameObject GameObject { get; }

			public MeshRenderer Renderer { get; }

			public Material Material { get; }

			public Texture2D? Texture { get; set; }

			public Transform? Anchor { get; set; }

			public Transform? AliveAnchor { get; set; }

			public Transform? DeathAnchor { get; set; }

			public RemoteAnchorSource AnchorSource { get; set; }

			public RemotePlayerLifecycleState LifecycleState { get; set; }

			public int LastSequence { get; set; }

			public float LastFrameAt { get; set; }

			public float NextResolveAt { get; set; }

			public bool Mirror { get; set; }

			public float Zoom { get; set; } = 1f;


			public float OffsetX { get; set; }

			public float OffsetY { get; set; }

			public float NextAnchorWarningAt { get; set; }

			public float NextDiagnosticLogAt { get; set; }

			public float NextFallbackLogAt { get; set; }

			public float LastAnchorSeenAt { get; set; }

			public bool UsingAnchorFallback { get; set; }

			public Vector3? LastKnownPosition { get; set; }

			public float NextPositionLogAt { get; set; }

			public RemotePeerState(string playerId, GameObject gameObject, MeshRenderer renderer, Material material)
			{
				PlayerId = playerId;
				GameObject = gameObject;
				Renderer = renderer;
				Material = material;
				LastFrameAt = Time.unscaledTime;
				NextResolveAt = 0f;
				LastAnchorSeenAt = float.NegativeInfinity;
				AnchorSource = RemoteAnchorSource.None;
				LifecycleState = RemotePlayerLifecycleState.Unknown;
			}
		}

		private enum RemoteAnchorSource
		{
			None,
			Direct,
			NameFallbackTrusted,
			ComponentFallback,
			NameFallback
		}

		private enum RemotePlayerLifecycleState
		{
			Unknown,
			Alive,
			Dead,
			AwaitingAliveAnchor
		}

		private const string RuntimeObjectName = "RepoWebcamRuntime";

		private const string ProbeLobbyId = "repowebcam-probe-v1";

		private const string VideoLobbyId = "repowebcam-video-v1";

		private const string CameraSectionName = "Camera";

		private const string RemoteCameraSectionName = "Remote Webcam";

		private const string DefaultCameraOption = "Default";

		private const string PreviewModeOff = "Off";

		private const string PreviewModeAboveHead = "AboveHead";

		private const string PreviewModeScreenDebug = "ScreenDebug";

		private const string PreviewModeScreenOverlay = "ScreenOverlay";

		private const int CameraRequestedWidth = 640;

		private const int CameraRequestedHeight = 360;

		private const int CameraRequestedFps = 30;

		private const string PreviewObjectName = "RepoWebcamPreview";

		private const float PreviewHeightOffset = 0.35f;

		private const float PreviewPlaneHeight = 0.22f;

		private const float AnchorResolveIntervalSeconds = 1.25f;

		private const float CameraFallbackDistance = 0.9f;

		private const float CameraFallbackVerticalOffset = -0.08f;

		private const float ScreenDebugDistance = 0.7f;

		private const float ScreenDebugHorizontalOffset = 0.28f;

		private const float ScreenDebugVerticalOffset = -0.16f;

		private const float ScreenDebugPlaneHeight = 0.18f;

		private const float OverlayMarginPixels = 20f;

		private const float OverlayWidthPixels = 200f;

		private const float OverlayMinHeightPixels = 140f;

		private const float LocalFrameSendIntervalSeconds = 1f / 24f;

		private const int LocalFrameTargetWidth = 360;

		private const int LocalFrameTargetHeight = 360;

		private const int LocalFrameJpegQuality = 50;

		private const int LocalFrameMaxPayloadLength = 200000;

		private const float RemoteFrameTimeoutSeconds = 12f;

		private const float RemotePeerDisposeSeconds = 35f;

		private const float PlayerDisconnectDetectionSeconds = 18f;

		private const float RemoteBillboardHeightOffset = 1.75f;

		private const float RemoteBillboardCameraOffset = 0.24f;

		private const float RemoteBillboardPlaneHeight = 0.56f;

		private const float RemoteResolveIntervalSeconds = 1.2f;

		private const float RemoteAnchorFallbackMaxSeconds = 4f;

		private const float RemoteAnchorParkingCoordinateThreshold = 1000f;

		private const float RecoveryCheckIntervalSeconds = 3f;

		public const string PreviewPosTopLeft = "TopLeft";

		public const string PreviewPosTopCenter = "TopCenter";

		public const string PreviewPosTopRight = "TopRight";

		public const string PreviewPosMiddleLeft = "MiddleLeft";

		public const string PreviewPosMiddleRight = "MiddleRight";

		public const string PreviewPosBottomLeft = "BottomLeft";

		public const string PreviewPosBottomRight = "BottomRight";

		private const bool RemoteDebugForceVisible = false;

		private const float RemoteDebugPositionLogIntervalSeconds = 5f;

		private const string MoreHeadHeadNodePath = "[RIG]/code_lean/code_tilt/ANIM BOT/_____________________________________/ANIM BODY BOT/_____________________________________/ANIM BODY TOP/code_body_top_up/code_body_top_side/_____________________________________/ANIM HEAD BOT/code_head_bot_up/code_head_bot_side/_____________________________________/ANIM HEAD TOP/code_head_top";

		private const float SceneRecoveryGraceSeconds = 2.5f;

		private const float SteamTransportWarmupSeconds = 3f;

		private const float NetworkRecoveryCooldownSeconds = 12f;

		private const float LobbyMembershipReprobeDelaySeconds = 1.5f;

		private const int SoftRecoveryMaxAttempts = 3;

		private const int LocalCaptureFailureLimit = 3;

		private const float LocalCaptureSuppressionSeconds = 120f;

		private const float RemoteAnchorWarningIntervalSeconds = 8f;

		private const float RemoteFallbackLogIntervalSeconds = 5f;

		private const int OverlaySortingOrder = 32000;

		private const string OverlayCanvasObjectName = "RepoWebcamOverlayCanvas";

		private const string OverlayPanelObjectName = "RepoWebcamOverlayPanel";

		private const string OverlayRawImageObjectName = "RepoWebcamOverlayImage";

		private static readonly Color OverlayPanelColor = new Color(0f, 0f, 0f, 0.35f);

		private const KeyCode LocalPreviewToggleKey = 289;

		private static RepoWebcamRuntime? _instance;

		private static ConfigFile? _bootstrapConfig;

		private static bool _applicationQuitting;

		private ConfigFile? _config;

		private FileLogger? _fileLogger;

		private ISteamP2PTransport? _steamTransport;

		private ISignalingChannel? _signalingChannel;

		private IDisposable? _signalingDisposable;

		private IVideoChannel? _videoChannel;

		private IDisposable? _videoDisposable;

		private ConfigEntry<bool>? _cameraEnabled;

		private ConfigEntry<bool>? _cameraMirror;

		private ConfigEntry<string>? _cameraDevice;

		private ConfigEntry<bool>? _showLocalPreview;

		private ConfigEntry<string>? _localPreviewPosition;

		private ConfigEntry<float>? _cameraZoom;

		private ConfigEntry<float>? _cameraOffsetX;

		private ConfigEntry<float>? _cameraOffsetY;

		private ConfigEntry<float>? _overlaySize;

		private ConfigEntry<float>? _remoteHeightOffset;

		private ConfigEntry<float>? _remoteDepthOffset;

		private ConfigEntry<float>? _remoteScale;

		private WebCamTexture? _localCameraTexture;

		private string? _activeCameraDevice;

		private Coroutine? _cameraStartupCoroutine;

		private bool _suppressCameraConfigEvents;

		private GameObject? _previewObject;

		private MeshRenderer? _previewRenderer;

		private Material? _previewMaterial;

		private GameObject? _overlayCanvasObject;

		private RectTransform? _overlayRectTransform;

		private RawImage? _overlayRawImage;

		private int _overlayScreenWidth;

		private int _overlayScreenHeight;

		private Transform? _localPlayerRoot;

		private Transform? _headAnchor;

		private float _nextAnchorResolveAt;

		private bool _anchorLogged;

		private bool _fallbackLogged;

		private int _previewRenderLayer = -1;

		private int _softRecoveryAttempts;

		private float _lastSceneChangeAt;

		private float _nextCameraRecoveryAt;

		private float _nextPreviewDiagnosticsAt;

		private float _nextTransportDiagnosticsAt;

		private int _localCaptureFailureCount;

		private bool _localCaptureSuppressed;

		private float _localCaptureSuppressedUntil;

		private bool _requiredRuntimeModuleReady;

		private bool _requiredRuntimeModuleBlockedLogged;

		private string? _localCaptureRetryBlockedDevice;

		private bool _localCaptureRetryBlockLogged;

		private Coroutine? _localFrameBroadcastCoroutine;

		private OutboundVideoFramePublisher? _outboundVideoPublisher;

		private InboundVideoFrameReceiver? _inboundVideoReceiver;

		private int _inboundVideoFrameCount;

		private long _inboundVideoBytes;

		private int _inboundProbeCount;

		private int _inboundUnknownPayloadCount;

		private readonly HashSet<string> _inboundVideoSources = new HashSet<string>(StringComparer.Ordinal);

		private readonly Dictionary<string, RemotePeerState> _remotePeers = new Dictionary<string, RemotePeerState>(StringComparer.Ordinal);

		private readonly HashSet<string> _peersMarkedDisconnected = new HashSet<string>(StringComparer.Ordinal);

		private int _probeSequence;

		private Coroutine? _probeCoroutine;

		private bool _probeSent;

		private string _lastLobbyMemberSignature = string.Empty;

		private float _scheduledLobbyMembershipReprobeAt = -1f;

		private bool _outboundNetworkWarmupPending = true;

		private float _outboundNetworkWarmupUntil;

		private string _outboundNetworkWarmupReason = "startup";

		private float _nextNetworkRecoveryAt;

		private Shader? _cachedPreviewShader;

		private bool _sceneShadersLogged;

		private Sprite? _circleSprite;

		public static void EnsureInstance(ConfigFile config)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			_bootstrapConfig = config;
			if (!((Object)(object)_instance != (Object)null))
			{
				RepoWebcamRuntime repoWebcamRuntime = Object.FindObjectOfType<RepoWebcamRuntime>();
				if ((Object)(object)repoWebcamRuntime != (Object)null)
				{
					_instance = repoWebcamRuntime;
					return;
				}
				GameObject val = new GameObject("RepoWebcamRuntime");
				Object.DontDestroyOnLoad((Object)val);
				_instance = val.AddComponent<RepoWebcamRuntime>();
			}
		}

		private void Awake()
		{
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			_instance = this;
			_applicationQuitting = false;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
			_config = _bootstrapConfig;
			_fileLogger = new FileLogger("REPO Webcam Mod");
			InitializeCameraConfig();
			RecreateNetworkingComponents(out var usingSteamTransport);
			SceneManager.activeSceneChanged += OnActiveSceneChanged;
			ArmOutboundNetworkWarmup("startup");
			AttachRequiredRuntimeModules();
			_fileLogger.WriteInfo("Plugin initialized.");
			_fileLogger.WriteInfo(usingSteamTransport ? "Signaling channel: Steam relay transport." : "Signaling channel: unavailable.");
			FileLogger? fileLogger = _fileLogger;
			object arg = ((Object)this).GetInstanceID();
			string name = ((Object)((Component)this).gameObject).name;
			Scene scene = ((Component)this).gameObject.scene;
			fileLogger.WriteInfo($"Lifecycle: instanceId={arg}, gameObject='{name}', scene='{((Scene)(ref scene)).name}'.");
		}

		private void Start()
		{
			_fileLogger?.WriteInfo("Start invoked. Preparing deferred signaling probe.");
			if (CanUseCameraRuntime("start", logIfBlocked: true))
			{
				TryApplyCameraState("start");
				EnsureLocalFrameBroadcastRoutine();
				EnsureProbeRoutineStarted("startup");
			}
		}

		private void Update()
		{
			if (CanUseCameraRuntime("update"))
			{
				_steamTransport?.Tick();
				TrackLobbyMembershipChanges();
				EnsureCameraPlaybackHealthy();
				HandleHotkeys();
				ResolveLocalAnchorIfNeeded();
				UpdatePreviewTransform();
				UpdateRemotePeers();
				UpdateOverlayLayoutIfNeeded();
				LogPreviewDiagnosticsTick();
				LogTransportDiagnosticsTick();
			}
		}

		private void TrackLobbyMembershipChanges()
		{
			IReadOnlyList<ulong> lobbyMemberIds = SteamLobbyAccessor.GetLobbyMemberIds();
			string text = ((lobbyMemberIds.Count == 0) ? string.Empty : string.Join(",", lobbyMemberIds.OrderBy((ulong id) => id)));
			if (string.Equals(text, _lastLobbyMemberSignature, StringComparison.Ordinal))
			{
				if (_scheduledLobbyMembershipReprobeAt > 0f && Time.unscaledTime >= _scheduledLobbyMembershipReprobeAt)
				{
					_scheduledLobbyMembershipReprobeAt = -1f;
					TriggerLobbyMembershipReprobe();
				}
				return;
			}
			if (!string.IsNullOrEmpty(_lastLobbyMemberSignature))
			{
				_fileLogger?.WriteInfo("Lobby membership changed: '" + _lastLobbyMemberSignature + "' -> '" + text + "'. Scheduling reprobe.");
				_scheduledLobbyMembershipReprobeAt = Time.unscaledTime + 1.5f;
			}
			_lastLobbyMemberSignature = text;
		}

		private void TriggerLobbyMembershipReprobe()
		{
			if (_signalingChannel == null)
			{
				return;
			}
			_probeSent = false;
			_fileLogger?.WriteInfo("Triggering lobby membership reprobe.");
			EnsureProbeRoutineStarted("lobby-membership-change");
			try
			{
				SendTransformUpdate(_cameraZoom?.Value ?? 1f, _cameraOffsetX?.Value ?? 0f, _cameraOffsetY?.Value ?? 0f);
			}
			catch (Exception exception)
			{
				_fileLogger?.WriteError("Failed to send transform update during lobby membership reprobe.", exception);
			}
		}

		private void EnsureProbeRoutineStarted(string reason)
		{
			if (!_probeSent && _probeCoroutine == null)
			{
				_probeCoroutine = ((MonoBehaviour)this).StartCoroutine(ProbeWhenReadyRoutine(reason));
			}
		}

		private void RecreateNetworkingComponents(out bool usingSteamTransport)
		{
			if (_signalingChannel != null)
			{
				_signalingChannel.PayloadReceived -= OnSignalingPayloadReceived;
			}
			if (_videoChannel != null)
			{
				_videoChannel.FrameReceived -= OnVideoFrameReceived;
			}
			_signalingDisposable?.Dispose();
			_signalingDisposable = null;
			_videoDisposable?.Dispose();
			_videoDisposable = null;
			(_steamTransport as IDisposable)?.Dispose();
			_steamTransport = null;
			_steamTransport = new SteamP2PTransport(_fileLogger, 31573);
			usingSteamTransport = true;
			_signalingChannel = SignalingChannelFactory.Create(_steamTransport);
			_signalingDisposable = _signalingChannel as IDisposable;
			_signalingChannel.PayloadReceived += OnSignalingPayloadReceived;
			_videoChannel = new SteamP2PVideoChannel(_steamTransport);
			_videoDisposable = _videoChannel as IDisposable;
			_videoChannel.FrameReceived += OnVideoFrameReceived;
			_outboundVideoPublisher?.Dispose();
			_outboundVideoPublisher = new OutboundVideoFramePublisher(_videoChannel, _fileLogger, 1f / 24f, 360, 360, 50);
			_inboundVideoReceiver = new InboundVideoFrameReceiver();
		}

		private void ResetAllRemoteWebcamsAndReprobe(string reason)
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime < _nextNetworkRecoveryAt)
			{
				_fileLogger?.WriteInfo($"Network recovery skipped due to cooldown. reason={reason}, retryIn={_nextNetworkRecoveryAt - unscaledTime:0.0}s.");
				return;
			}
			_nextNetworkRecoveryAt = unscaledTime + 12f;
			_fileLogger?.WriteWarning("Starting webcam network recovery. reason=" + reason + ".");
			if (_probeCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_probeCoroutine);
				_probeCoroutine = null;
			}
			_probeSent = false;
			_probeSequence = 0;
			_inboundProbeCount = 0;
			_inboundUnknownPayloadCount = 0;
			_inboundVideoSources.Clear();
			_peersMarkedDisconnected.Clear();
			DisposeRemotePeers("network-recovery:" + reason);
			RecreateNetworkingComponents(out var usingSteamTransport);
			ArmOutboundNetworkWarmup("network-recovery:" + reason);
			if ((Object)(object)_localCameraTexture != (Object)null && _localCameraTexture.isPlaying)
			{
				EnsureLocalFrameBroadcastRoutine();
			}
			EnsureProbeRoutineStarted("network-recovery:" + reason);
			_fileLogger?.WriteInfo("Webcam network recovery armed. signalingProvider=" + (usingSteamTransport ? "SteamRelay" : "disabled") + ".");
		}

		private IEnumerator ProbeWhenReadyRoutine(string reason)
		{
			int attempt = 0;
			while (_signalingChannel != null && !_signalingChannel.IsReady && attempt < 40)
			{
				attempt++;
				if (attempt == 1 || attempt % 10 == 0)
				{
					_fileLogger?.WriteInfo($"Signaling is not ready yet. Waiting before probe send. attempt={attempt}.");
				}
				yield return (object)new WaitForSecondsRealtime(0.5f);
			}
			_probeCoroutine = null;
			if (_signalingChannel == null || _probeSent)
			{
				yield break;
			}
			if (!_signalingChannel.IsReady)
			{
				_fileLogger?.WriteWarning("Signaling did not become ready before timeout. Probe send skipped.");
				yield break;
			}
			int warmupAttempts = 0;
			while (_signalingChannel != null && !CanSendTransportTraffic() && warmupAttempts < 80)
			{
				warmupAttempts++;
				yield return (object)new WaitForSecondsRealtime(0.25f);
			}
			if (_signalingChannel != null)
			{
				if (!CanSendTransportTraffic())
				{
					_fileLogger?.WriteWarning("Steam transport warmup did not finish in time. Probe send skipped.");
					yield break;
				}
				_probeSent = true;
				SendProbeAsync(reason);
			}
		}

		private void ArmOutboundNetworkWarmup(string reason)
		{
			_outboundNetworkWarmupPending = true;
			_outboundNetworkWarmupUntil = 0f;
			_outboundNetworkWarmupReason = reason;
		}

		private bool CanSendTransportTraffic()
		{
			if (_signalingChannel == null || !_signalingChannel.IsReady)
			{
				_outboundNetworkWarmupUntil = 0f;
				return false;
			}
			if (!_outboundNetworkWarmupPending)
			{
				return true;
			}
			if (_outboundNetworkWarmupUntil <= 0f)
			{
				_outboundNetworkWarmupUntil = Time.unscaledTime + 3f;
				_fileLogger?.WriteInfo($"Steam transport warmup started: {3f:0.0}s before outbound packets. reason={_outboundNetworkWarmupReason}.");
			}
			if (Time.unscaledTime < _outboundNetworkWarmupUntil)
			{
				return false;
			}
			_outboundNetworkWarmupPending = false;
			_outboundNetworkWarmupUntil = 0f;
			_fileLogger?.WriteInfo("Steam transport warmup completed. Outbound packets unlocked.");
			return true;
		}

		private async Task SendProbeAsync(string reason)
		{
			if (_signalingChannel == null)
			{
				return;
			}
			try
			{
				int probeId = Interlocked.Increment(ref _probeSequence);
				string payload = $"{{\"type\":\"probe\",\"reason\":\"{reason}\",\"id\":{probeId},\"utc\":\"{DateTime.UtcNow:O}\"}}";
				await _signalingChannel.SendToLobbyAsync("repowebcam-probe-v1", payload, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false);
				_fileLogger?.WriteInfo(string.Format("Probe sent: id={0}, lobby={1}.", probeId, "repowebcam-probe-v1"));
			}
			catch (Exception exception)
			{
				_fileLogger?.WriteError("Failed to send signaling probe.", exception);
			}
		}

		private void OnVideoFrameReceived(object? sender, VideoFrameReceivedEventArgs eventArgs)
		{
			if (_inboundVideoReceiver != null && _inboundVideoReceiver.TryHandleVideo(eventArgs, out InboundVideoFrame frame) && frame != null)
			{
				_inboundVideoFrameCount++;
				_inboundVideoBytes += frame.JpegBytes.LongLength;
				if (_inboundVideoSources.Add(eventArgs.SourcePlayerId))
				{
					_fileLogger?.WriteInfo($"Incoming video stream detected: from={eventArgs.SourcePlayerId}, seq={frame.Sequence}, size={frame.Width}x{frame.Height}, bytes={frame.JpegBytes.Length}.");
				}
				ApplyIncomingVideoFrame(eventArgs.SourcePlayerId, frame);
			}
		}

		private void OnSignalingPayloadReceived(object? sender, SignalingPayloadReceivedEventArgs eventArgs)
		{
			if (eventArgs.Payload is WebcamTransformState packet)
			{
				ApplyIncomingTransform(eventArgs.SourcePlayerId, packet);
			}
			else if (eventArgs.Payload is string text && text.IndexOf("\"type\":\"probe\"", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				_inboundProbeCount++;
				_fileLogger?.WriteInfo("Probe received: lobby=" + eventArgs.LobbyId + ", from=" + eventArgs.SourcePlayerId + ".");
				if (_cameraEnabled != null && _cameraEnabled.Value)
				{
					float zoom = _cameraZoom?.Value ?? 1f;
					float offsetX = _cameraOffsetX?.Value ?? 0f;
					float offsetY = _cameraOffsetY?.Value ?? 0f;
					SendTransformUpdate(zoom, offsetX, offsetY);
				}
			}
			else if (eventArgs.Payload is string text2)
			{
				_inboundUnknownPayloadCount++;
				_fileLogger?.WriteInfo($"Signaling payload received: lobby={eventArgs.LobbyId}, from={eventArgs.SourcePlayerId}, len={text2.Length}.");
			}
		}

		private void OnActiveSceneChanged(Scene previousScene, Scene nextScene)
		{
			_fileLogger?.WriteInfo("Scene changed: '" + ((Scene)(ref previousScene)).name + "' -> '" + ((Scene)(ref nextScene)).name + "'.");
			_localPlayerRoot = null;
			_headAnchor = null;
			_anchorLogged = false;
			_lastSceneChangeAt = Time.unscaledTime;
			_nextCameraRecoveryAt = Time.unscaledTime + 2.5f;
			_softRecoveryAttempts = 0;
			ArmOutboundNetworkWarmup("scene-change");
			_fileLogger?.WriteInfo($"Camera recovery grace period enabled: {2.5f:0.0}s.");
			EnsureProbeRoutineStarted("scene-change");
		}

		private void OnApplicationQuit()
		{
			_applicationQuitting = true;
			_fileLogger?.WriteInfo("OnApplicationQuit invoked.");
		}

		private void OnDestroy()
		{
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_instance == (Object)(object)this)
			{
				_instance = null;
			}
			SceneManager.activeSceneChanged -= OnActiveSceneChanged;
			if (_signalingChannel != null)
			{
				_signalingChannel.PayloadReceived -= OnSignalingPayloadReceived;
			}
			if (_probeCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_probeCoroutine);
				_probeCoroutine = null;
			}
			if (_localFrameBroadcastCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_localFrameBroadcastCoroutine);
				_localFrameBroadcastCoroutine = null;
			}
			UnsubscribeCameraConfigEvents();
			StopLocalCamera("destroy");
			DestroyPreviewObject();
			DestroyOverlayUi();
			DisposeRemotePeers("destroy");
			_outboundVideoPublisher?.Dispose();
			_outboundVideoPublisher = null;
			_inboundVideoReceiver = null;
			_inboundVideoSources.Clear();
			_signalingDisposable?.Dispose();
			_signalingDisposable = null;
			_videoDisposable?.Dispose();
			_videoDisposable = null;
			(_steamTransport as IDisposable)?.Dispose();
			_steamTransport = null;
			_signalingChannel = null;
			_videoChannel = null;
			_fileLogger?.WriteInfo("Plugin disposed.");
			FileLogger? fileLogger = _fileLogger;
			if (fileLogger != null)
			{
				object arg = ((Object)this).GetInstanceID();
				object arg2 = _applicationQuitting;
				Scene scene = ((Component)this).gameObject.scene;
				fileLogger.WriteInfo($"Lifecycle: OnDestroy instanceId={arg}, quitting={arg2}, scene='{((Scene)(ref scene)).name}'.");
			}
			_fileLogger?.Dispose();
		}

		private void InitializeCameraConfig()
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Expected O, but got Unknown
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Expected O, but got Unknown
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_014a: Expected O, but got Unknown
			//IL_017e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Expected O, but got Unknown
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Expected O, but got Unknown
			//IL_01fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0204: Expected O, but got Unknown
			//IL_0238: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Expected O, but got Unknown
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_0280: Expected O, but got Unknown
			//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02be: Expected O, but got Unknown
			//IL_02f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fc: Expected O, but got Unknown
			if (_config == null)
			{
				_fileLogger?.WriteWarning("ConfigFile was not provided to RepoWebcamRuntime. Camera settings were not created.");
				return;
			}
			string[] array = BuildCameraOptions();
			string text = array[0];
			_cameraEnabled = _config.Bind<bool>("Camera", "Enabled", true, new ConfigDescription("Enable the webcam module.", (AcceptableValueBase)null, Array.Empty<object>()));
			_cameraMirror = _config.Bind<bool>("Camera", "Mirror", false, new ConfigDescription("Mirror the local video feed.", (AcceptableValueBase)null, Array.Empty<object>()));
			_cameraDevice = _config.Bind<string>("Camera", "Device", text, new ConfigDescription("Webcam device used by the mod.", (AcceptableValueBase)(object)new AcceptableValueList<string>(array), Array.Empty<object>()));
			_showLocalPreview = _config.Bind<bool>("Camera", "ShowLocalPreview", true, new ConfigDescription("Show the local camera preview on screen as an overlay.", (AcceptableValueBase)null, Array.Empty<object>()));
			_localPreviewPosition = _config.Bind<string>("Camera", "LocalPreviewPosition", "TopCenter", new ConfigDescription("Position of the local preview on screen.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[7] { "TopLeft", "TopCenter", "TopRight", "MiddleLeft", "MiddleRight", "BottomLeft", "BottomRight" }), Array.Empty<object>()));
			_cameraZoom = _config.Bind<float>("Camera", "Zoom", 1f, new ConfigDescription("Video zoom", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 5f), Array.Empty<object>()));
			_cameraOffsetX = _config.Bind<float>("Camera", "OffsetX", 0f, new ConfigDescription("Video offset on X", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>()));
			_cameraOffsetY = _config.Bind<float>("Camera", "OffsetY", 0f, new ConfigDescription("Video offset on Y", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-1f, 1f), Array.Empty<object>()));
			_overlaySize = _config.Bind<float>("Camera", "PreviewSize", 200f, new ConfigDescription("Local preview size in pixels", (AcceptableValueBase)(object)new AcceptableValueRange<float>(100f, 800f), Array.Empty<object>()));
			_remoteHeightOffset = _config.Bind<float>("Remote Webcam", "Height (Y)", 0.85f, new ConfigDescription("Vertical webcam offset above remote players (Y)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			_remoteDepthOffset = _config.Bind<float>("Remote Webcam", "Depth (Z)", 0.3f, new ConfigDescription("Remote webcam depth offset (Z)", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-2f, 2f), Array.Empty<object>()));
			_remoteScale = _config.Bind<float>("Remote Webcam", "Size (Scale)", 0.56f, new ConfigDescription("Remote webcam size", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 2f), Array.Empty<object>()));
			if (!array.Contains<string>(_cameraDevice.Value, StringComparer.Ordinal))
			{
				_fileLogger?.WriteInfo("Camera " + _cameraDevice.Value + " is missing from options");
			}
			NormalizePreviewPositionValue();
			SubscribeCameraConfigEvents();
			_fileLogger?.WriteInfo("Detected cameras: " + string.Join(", ", array) + ".");
			_fileLogger?.WriteInfo($"Current camera settings: Enabled={_cameraEnabled.Value}, Mirror={_cameraMirror.Value}, Device='{_cameraDevice.Value}', ShowLocalPreview={_showLocalPreview.Value}, LocalPreviewPosition='{_localPreviewPosition.Value}'.");
		}

		private static string[] BuildCameraOptions()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			WebCamDevice[] devices = WebCamTexture.devices;
			if (devices == null || devices.Length == 0)
			{
				return new string[1] { "Default" };
			}
			List<string> list = new List<string>(devices.Length + 1) { "Default" };
			WebCamDevice[] array = devices;
			for (int i = 0; i < array.Length; i++)
			{
				WebCamDevice val = array[i];
				string name = ((WebCamDevice)(ref val)).name;
				if (!string.IsNullOrWhiteSpace(name) && !list.Contains<string>(name, StringComparer.Ordinal))
				{
					list.Add(name);
				}
			}
			return list.ToArray();
		}

		private void SubscribeCameraConfigEvents()
		{
			if (_cameraEnabled != null)
			{
				_cameraEnabled.SettingChanged += OnCameraEnabledSettingChanged;
			}
			if (_cameraMirror != null)
			{
				_cameraMirror.SettingChanged += OnCameraMirrorSettingChanged;
			}
			if (_cameraDevice != null)
			{
				_cameraDevice.SettingChanged += OnCameraDeviceSettingChanged;
			}
			if (_showLocalPreview != null)
			{
				_showLocalPreview.SettingChanged += OnLocalPreviewSettingChanged;
			}
			if (_localPreviewPosition != null)
			{
				_localPreviewPosition.SettingChanged += OnLocalPreviewSettingChanged;
			}
			if (_cameraZoom != null)
			{
				_cameraZoom.SettingChanged += OnCameraTransformSettingChanged;
			}
			if (_cameraOffsetX != null)
			{
				_cameraOffsetX.SettingChanged += OnCameraTransformSettingChanged;
			}
			if (_cameraOffsetY != null)
			{
				_cameraOffsetY.SettingChanged += OnCameraTransformSettingChanged;
			}
			if (_overlaySize != null)
			{
				_overlaySize.SettingChanged += OnLocalPreviewSettingChanged;
			}
		}

		private void UnsubscribeCameraConfigEvents()
		{
			if (_cameraEnabled != null)
			{
				_cameraEnabled.SettingChanged -= OnCameraEnabledSettingChanged;
			}
			if (_cameraMirror != null)
			{
				_cameraMirror.SettingChanged -= OnCameraMirrorSettingChanged;
			}
			if (_cameraDevice != null)
			{
				_cameraDevice.SettingChanged -= OnCameraDeviceSettingChanged;
			}
			if (_showLocalPreview != null)
			{
				_showLocalPreview.SettingChanged -= OnLocalPreviewSettingChanged;
			}
			if (_localPreviewPosition != null)
			{
				_localPreviewPosition.SettingChanged -= OnLocalPreviewSettingChanged;
			}
			if (_cameraZoom != null)
			{
				_cameraZoom.SettingChanged -= OnCameraTransformSettingChanged;
			}
			if (_cameraOffsetX != null)
			{
				_cameraOffsetX.SettingChanged -= OnCameraTransformSettingChanged;
			}
			if (_cameraOffsetY != null)
			{
				_cameraOffsetY.SettingChanged -= OnCameraTransformSettingChanged;
			}
			if (_overlaySize != null)
			{
				_overlaySize.SettingChanged -= OnLocalPreviewSettingChanged;
			}
		}

		private void OnCameraEnabledSettingChanged(object? sender, EventArgs eventArgs)
		{
			if (_cameraEnabled != null && !_suppressCameraConfigEvents)
			{
				_fileLogger?.WriteInfo($"Camera setting changed: Enabled={_cameraEnabled.Value}.");
				if (_cameraEnabled.Value)
				{
					ResetLocalCaptureSuppression("config-enabled");
				}
				TryApplyCameraState("config-enabled");
			}
		}

		private void OnCameraMirrorSettingChanged(object? sender, EventArgs eventArgs)
		{
			if (_cameraMirror != null && !_suppressCameraConfigEvents)
			{
				_fileLogger?.WriteInfo($"Camera setting changed: Mirror={_cameraMirror.Value}.");
				ApplyMirrorToPreviewMaterial();
			}
		}

		private void OnCameraDeviceSettingChanged(object? sender, EventArgs eventArgs)
		{
			if (_cameraDevice != null && !_suppressCameraConfigEvents)
			{
				_fileLogger?.WriteInfo("Camera setting changed: Device='" + _cameraDevice.Value + "'.");
				ResetLocalCaptureSuppression("config-device");
				TryApplyCameraState("config-device");
			}
		}

		private void OnLocalPreviewSettingChanged(object? sender, EventArgs eventArgs)
		{
			if (!_suppressCameraConfigEvents)
			{
				NormalizePreviewPositionValue();
				UpdatePreviewVisibilityState();
				UpdateOverlayLayoutIfNeeded(force: true);
			}
		}

		private void OnCameraTransformSettingChanged(object? sender, EventArgs eventArgs)
		{
			if (!_suppressCameraConfigEvents)
			{
				ApplyMirrorToPreviewMaterial();
				float zoom = _cameraZoom?.Value ?? 1f;
				float offsetX = _cameraOffsetX?.Value ?? 0f;
				float offsetY = _cameraOffsetY?.Value ?? 0f;
				SendTransformUpdate(zoom, offsetX, offsetY);
			}
		}

		private void SendTransformUpdate(float zoom, float offsetX, float offsetY)
		{
			ISignalingChannel signalingChannel = _signalingChannel;
			if (signalingChannel == null || !CanSendTransportTraffic() || string.IsNullOrEmpty("repowebcam-video-v1"))
			{
				return;
			}
			try
			{
				WebcamTransformState transform = new WebcamTransformState(zoom, offsetX, offsetY);
				signalingChannel.SendTransform("repowebcam-video-v1", transform);
			}
			catch (Exception exception)
			{
				_fileLogger?.WriteError("Failed to send webcam transform update.", exception);
			}
		}

		private void TryApplyCameraState(string reason)
		{
			if (_cameraEnabled != null && _cameraDevice != null)
			{
				if (!CanUseCameraRuntime(reason, logIfBlocked: true))
				{
					StopLocalCamera("module-blocked:" + reason);
				}
				else if (!_cameraEnabled.Value)
				{
					StopLocalCamera("disabled:" + reason);
				}
				else
				{
					StartOrRestartLocalCamera(reason);
				}
			}
		}

		internal void NotifyRequiredRuntimeModuleReady()
		{
			_requiredRuntimeModuleReady = true;
			_requiredRuntimeModuleBlockedLogged = false;
		}

		private void AttachRequiredRuntimeModules()
		{
			try
			{
				((Component)this).gameObject.AddComponent<RuntimeOverlayCoordinator>().Attach(this);
			}
			catch (Exception exception)
			{
				_requiredRuntimeModuleReady = false;
				_requiredRuntimeModuleBlockedLogged = false;
				_fileLogger?.WriteError("Failed to initialize required runtime module. Cameras are blocked in Release build.", exception);
			}
		}

		private bool CanUseCameraRuntime(string reason, bool logIfBlocked = false)
		{
			if (_requiredRuntimeModuleReady)
			{
				return true;
			}
			if (logIfBlocked && !_requiredRuntimeModuleBlockedLogged)
			{
				_requiredRuntimeModuleBlockedLogged = true;
				_fileLogger?.WriteWarning("Required runtime module is unavailable. Cameras are blocked in Release build. reason=" + reason + ".");
			}
			return false;
		}

		private void EnsureCameraPlaybackHealthy()
		{
			if (_cameraEnabled == null || !_cameraEnabled.Value || IsLocalCaptureSuppressedNow() || Time.unscaledTime - _lastSceneChangeAt < 2.5f || Time.unscaledTime < _nextCameraRecoveryAt)
			{
				return;
			}
			if ((Object)(object)_localCameraTexture == (Object)null)
			{
				RegisterLocalCaptureFailure("recovery-null");
				if (!_localCaptureSuppressed)
				{
					_nextCameraRecoveryAt = Time.unscaledTime + 3f;
					_fileLogger?.WriteWarning("Local camera texture is missing. Trying to restore it.");
					_softRecoveryAttempts = 0;
					StartOrRestartLocalCamera("recovery-null");
				}
				return;
			}
			if (_localCameraTexture.isPlaying)
			{
				_softRecoveryAttempts = 0;
				return;
			}
			if (_softRecoveryAttempts < 3)
			{
				_softRecoveryAttempts++;
				_nextCameraRecoveryAt = Time.unscaledTime + 3f;
				_fileLogger?.WriteWarning($"Local camera stopped (isPlaying=False). Soft recovery Play() attempt={_softRecoveryAttempts}/{3}.");
				try
				{
					_localCameraTexture.Play();
					UpdatePreviewVisibilityState();
					return;
				}
				catch (Exception exception)
				{
					_fileLogger?.WriteError("Soft local camera recovery failed.", exception);
					return;
				}
			}
			_softRecoveryAttempts = 0;
			_nextCameraRecoveryAt = Time.unscaledTime + 3f;
			_fileLogger?.WriteWarning("Local camera stopped (isPlaying=False). Hard recovery will recreate it.");
			StartOrRestartLocalCamera("recovery-hard");
		}

		private void StartOrRestartLocalCamera(string reason)
		{
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Expected O, but got Unknown
			if (_cameraDevice == null)
			{
				return;
			}
			string[] options = BuildCameraOptions();
			string text = ResolveRequestedCamera(_cameraDevice.Value, options);
			if ((Object)(object)_localCameraTexture != (Object)null && _localCameraTexture.isPlaying && string.Equals(_activeCameraDevice, text, StringComparison.Ordinal))
			{
				return;
			}
			StopLocalCamera("restart:" + reason);
			try
			{
				bool flag = string.Equals(text, "Default", StringComparison.Ordinal);
				_localCameraTexture = (flag ? new WebCamTexture(640, 360, 30) : new WebCamTexture(text, 640, 360, 30));
				_activeCameraDevice = text;
				_localCameraTexture.Play();
				BindPreviewToCurrentTexture();
				EnsureLocalFrameBroadcastRoutine();
				_fileLogger?.WriteInfo($"Starting local camera: Device='{text}', requested={640}x{360}@{30}, mirror={_cameraMirror?.Value ?? false}, reason={reason}.");
				if (_cameraStartupCoroutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_cameraStartupCoroutine);
				}
				_cameraStartupCoroutine = ((MonoBehaviour)this).StartCoroutine(ValidateCameraStartedRoutine(_localCameraTexture, reason));
			}
			catch (Exception exception)
			{
				_fileLogger?.WriteError("Failed to start local camera. Device='" + text + "'.", exception);
				StopLocalCamera("start-failed");
			}
		}

		private IEnumerator ValidateCameraStartedRoutine(WebCamTexture cameraTexture, string reason)
		{
			yield return (object)new WaitForSecondsRealtime(1f);
			if (cameraTexture != _localCameraTexture)
			{
				yield break;
			}
			if (!cameraTexture.isPlaying)
			{
				_fileLogger?.WriteWarning("Local camera did not enter playing state. Device='" + _activeCameraDevice + "', reason=" + reason + ".");
				RegisterLocalCaptureFailure(reason);
				if (!_localCaptureSuppressed)
				{
					_nextCameraRecoveryAt = Time.unscaledTime + 3f;
				}
				_cameraStartupCoroutine = null;
			}
			else
			{
				ResetLocalCaptureSuppression("camera-started");
				UpdatePreviewScaleFromTexture(cameraTexture);
				UpdatePreviewVisibilityState();
				_fileLogger?.WriteInfo($"Local camera active: Device='{cameraTexture.deviceName}', actual={((Texture)cameraTexture).width}x{((Texture)cameraTexture).height}, didUpdate={cameraTexture.didUpdateThisFrame}.");
				_cameraStartupCoroutine = null;
			}
		}

		private bool IsLocalCaptureSuppressedNow()
		{
			if (!_localCaptureSuppressed)
			{
				return false;
			}
			if (Time.unscaledTime < _localCaptureSuppressedUntil)
			{
				return true;
			}
			if (IsLocalCaptureRetryBlockedForCurrentDevice())
			{
				_localCaptureSuppressedUntil = Time.unscaledTime + 3f;
				if (!_localCaptureRetryBlockLogged)
				{
					_localCaptureRetryBlockLogged = true;
					_fileLogger?.WriteInfo("Local capture suppression remains active for device '" + _localCaptureRetryBlockedDevice + "'. Automatic retries stay blocked until camera settings change.");
				}
				return true;
			}
			_localCaptureSuppressed = false;
			_localCaptureSuppressedUntil = 0f;
			_localCaptureFailureCount = 0;
			_softRecoveryAttempts = 0;
			_fileLogger?.WriteInfo("Local capture suppression finished. Auto-recovery resumed.");
			return false;
		}

		private void RegisterLocalCaptureFailure(string reason)
		{
			_localCaptureFailureCount++;
			_softRecoveryAttempts = 0;
			if (_localCaptureFailureCount >= 3)
			{
				_localCaptureSuppressed = true;
				_localCaptureSuppressedUntil = Time.unscaledTime + 120f;
				_localCaptureRetryBlockedDevice = ResolveLocalCaptureRetryBlockedDevice();
				_localCaptureRetryBlockLogged = false;
				_nextCameraRecoveryAt = _localCaptureSuppressedUntil;
				StopLocalCamera("receive-only-suppressed");
				_fileLogger?.WriteWarning($"Local camera unavailable ({_localCaptureFailureCount} consecutive failures). " + $"Receive-only mode enabled for {120f:0}s. reason={reason}.");
				if (!string.IsNullOrWhiteSpace(_localCaptureRetryBlockedDevice))
				{
					_fileLogger?.WriteInfo("Automatic retries blocked for device '" + _localCaptureRetryBlockedDevice + "' until camera settings change.");
				}
			}
		}

		private void ResetLocalCaptureSuppression(string reason)
		{
			bool localCaptureSuppressed = _localCaptureSuppressed;
			bool flag = _localCaptureFailureCount > 0 || _softRecoveryAttempts > 0 || !string.IsNullOrWhiteSpace(_localCaptureRetryBlockedDevice);
			_localCaptureSuppressed = false;
			_localCaptureSuppressedUntil = 0f;
			_localCaptureFailureCount = 0;
			_softRecoveryAttempts = 0;
			_localCaptureRetryBlockedDevice = null;
			_localCaptureRetryBlockLogged = false;
			if (localCaptureSuppressed || flag)
			{
				_fileLogger?.WriteInfo("Local capture recovery state reset. reason=" + reason + ".");
			}
		}

		private bool IsLocalCaptureRetryBlockedForCurrentDevice()
		{
			if (string.IsNullOrWhiteSpace(_localCaptureRetryBlockedDevice))
			{
				return false;
			}
			string text = ResolveLocalCaptureRetryBlockedDevice();
			if (string.IsNullOrWhiteSpace(text))
			{
				return false;
			}
			return string.Equals(text, _localCaptureRetryBlockedDevice, StringComparison.Ordinal);
		}

		private string? ResolveLocalCaptureRetryBlockedDevice()
		{
			if (_cameraDevice != null)
			{
				string text = ResolveRequestedCamera(_cameraDevice.Value, BuildCameraOptions());
				if (!string.IsNullOrWhiteSpace(text))
				{
					return text;
				}
			}
			return _activeCameraDevice;
		}

		private void EnsureLocalFrameBroadcastRoutine()
		{
			if (_localFrameBroadcastCoroutine == null)
			{
				_localFrameBroadcastCoroutine = ((MonoBehaviour)this).StartCoroutine(LocalFrameBroadcastRoutine());
			}
		}

		private IEnumerator LocalFrameBroadcastRoutine()
		{
			while (true)
			{
				yield return null;
				if (_cameraEnabled != null && _cameraEnabled.Value && _outboundVideoPublisher != null && !((Object)(object)_headAnchor == (Object)null) && !((Object)(object)_localCameraTexture == (Object)null) && CanSendTransportTraffic())
				{
					float zoom = _cameraZoom?.Value ?? 1f;
					float offsetX = _cameraOffsetX?.Value ?? 0f;
					float offsetY = _cameraOffsetY?.Value ?? 0f;
					int recipientCount = Math.Max(0, SteamLobbyAccessor.GetLobbyMemberIds().Count - 1);
					_outboundVideoPublisher.TryPublish(_localCameraTexture, _cameraMirror?.Value ?? false, zoom, offsetX, offsetY, Time.unscaledTime, recipientCount);
				}
			}
		}

		private void ApplyIncomingVideoFrame(string sourcePlayerId, InboundVideoFrame frame)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			RemotePeerState orCreateRemotePeer = GetOrCreateRemotePeer(sourcePlayerId);
			if (orCreateRemotePeer.LastSequence >= frame.Sequence)
			{
				return;
			}
			if ((Object)(object)orCreateRemotePeer.Texture == (Object)null)
			{
				orCreateRemotePeer.Texture = new Texture2D(2, 2, (TextureFormat)3, false);
			}
			if (!ImageConversion.LoadImage(orCreateRemotePeer.Texture, frame.JpegBytes, false))
			{
				return;
			}
			orCreateRemotePeer.LastSequence = frame.Sequence;
			orCreateRemotePeer.LastFrameAt = Time.unscaledTime;
			_peersMarkedDisconnected.Remove(sourcePlayerId);
			orCreateRemotePeer.Mirror = frame.Mirror;
			orCreateRemotePeer.Zoom = frame.Zoom;
			orCreateRemotePeer.OffsetX = frame.OffsetX;
			orCreateRemotePeer.OffsetY = frame.OffsetY;
			orCreateRemotePeer.Material.mainTexture = (Texture)(object)orCreateRemotePeer.Texture;
			string[] array = new string[3] { "_BaseMap", "_BaseColorMap", "_MainTex" };
			foreach (string text in array)
			{
				if (orCreateRemotePeer.Material.HasProperty(text))
				{
					orCreateRemotePeer.Material.SetTexture(text, (Texture)(object)orCreateRemotePeer.Texture);
				}
			}
			orCreateRemotePeer.Material.mainTextureScale = new Vector2(1f, 1f);
			orCreateRemotePeer.Material.mainTextureOffset = new Vector2(0f, 0f);
			if (orCreateRemotePeer.Material.HasProperty("_EmissionMap"))
			{
				orCreateRemotePeer.Material.SetTexture("_EmissionMap", (Texture)(object)orCreateRemotePeer.Texture);
			}
		}

		private RemotePeerState GetOrCreateRemotePeer(string playerId)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			if (_remotePeers.TryGetValue(playerId, out RemotePeerState value))
			{
				return value;
			}
			GameObject val = new GameObject("RepoWebcamRemote_" + ShortId(playerId));
			val.AddComponent<MeshFilter>().mesh = CreateCircularMesh(32);
			Object.DontDestroyOnLoad((Object)(object)val);
			Component component = val.GetComponent("Collider");
			if ((Object)(object)component != (Object)null)
			{
				Object.Destroy((Object)(object)component);
			}
			MeshRenderer val2 = val.AddComponent<MeshRenderer>();
			Shader val3 = ResolvePreviewShader();
			Material val4 = new Material(val3)
			{
				color = new Color(1f, 1f, 1f, 1f),
				name = "RepoWebcamRemoteMaterial_" + ShortId(playerId)
			};
			val4.renderQueue = 4000;
			val4.SetInt("_ZTest", 8);
			if (val4.HasProperty("_RendererColor"))
			{
				val4.SetColor("_RendererColor", Color.white);
			}
			((Renderer)val2).sharedMaterial = val4;
			((Renderer)val2).shadowCastingMode = (ShadowCastingMode)0;
			((Renderer)val2).receiveShadows = false;
			((Renderer)val2).allowOcclusionWhenDynamic = false;
			val.SetActive(false);
			RemotePeerState remotePeerState = new RemotePeerState(playerId, val, val2, val4);
			_remotePeers[playerId] = remotePeerState;
			_fileLogger?.WriteInfo("Remote webcam peer created: playerId=" + playerId + ", shader='" + ((Object)val3).name + "'.");
			return remotePeerState;
		}

		private void ApplyIncomingTransform(string sourcePlayerId, WebcamTransformState packet)
		{
			//IL_0079: 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)
			RemotePeerState orCreateRemotePeer = GetOrCreateRemotePeer(sourcePlayerId);
			orCreateRemotePeer.Zoom = packet.Zoom;
			orCreateRemotePeer.OffsetX = packet.OffsetX;
			orCreateRemotePeer.OffsetY = packet.OffsetY;
			if ((Object)(object)orCreateRemotePeer.Texture != (Object)null && (Object)(object)orCreateRemotePeer.Material != (Object)null)
			{
				CalculateUV(orCreateRemotePeer.Mirror, orCreateRemotePeer.Zoom, orCreateRemotePeer.OffsetX, orCreateRemotePeer.OffsetY, (Texture?)(object)orCreateRemotePeer.Texture, out var scale, out var offset);
				orCreateRemotePeer.Material.mainTextureScale = scale;
				orCreateRemotePeer.Material.mainTextureOffset = offset;
			}
		}

		private void CalculateUV(bool mirror, float zoom, float offsetX, float offsetY, Texture? texture, out Vector2 scale, out Vector2 offset)
		{
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			float num = 1f;
			if ((Object)(object)texture != (Object)null && texture.height > 0)
			{
				num = (float)texture.width / (float)texture.height;
			}
			float num2 = 1f;
			float num3 = 1f;
			if (num > 1f)
			{
				num2 = 1f / num;
			}
			else if (num < 1f)
			{
				num3 = num;
			}
			if (zoom <= 0.01f)
			{
				zoom = 1f;
			}
			float num4 = num2 / zoom;
			float num5 = num3 / zoom;
			if (mirror)
			{
				num4 = 0f - num4;
			}
			float num6 = 0.5f - num4 / 2f;
			float num7 = 0.5f - num5 / 2f;
			scale = new Vector2(num4, num5);
			offset = new Vector2(num6 + offsetX, num7 + offsetY);
		}

		private Mesh CreateCircularMesh(int segments)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_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_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			Mesh val = new Mesh
			{
				name = "WebcamCircleMesh"
			};
			Vector3[] array = (Vector3[])(object)new Vector3[segments + 1];
			Vector2[] array2 = (Vector2[])(object)new Vector2[segments + 1];
			int[] array3 = new int[segments * 3];
			array[0] = Vector3.zero;
			array2[0] = new Vector2(0.5f, 0.5f);
			float num = MathF.PI * 2f / (float)segments;
			for (int i = 0; i < segments; i++)
			{
				float num2 = (float)i * num;
				float num3 = Mathf.Cos(num2) * 0.5f;
				float num4 = Mathf.Sin(num2) * 0.5f;
				array[i + 1] = new Vector3(num3, num4, 0f);
				array2[i + 1] = new Vector2(num3 + 0.5f, num4 + 0.5f);
				int num5 = (i + 1) % segments + 1;
				int num6 = i * 3;
				array3[num6] = 0;
				array3[num6 + 1] = num5;
				array3[num6 + 2] = i + 1;
			}
			val.vertices = array;
			val.uv = array2;
			val.triangles = array3;
			val.RecalculateBounds();
			val.RecalculateNormals();
			return val;
		}

		private void UpdateRemotePeers()
		{
			if (_remotePeers.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			Camera val = ResolveTargetCamera();
			List<string> list = new List<string>();
			string text = null;
			foreach (KeyValuePair<string, RemotePeerState> remotePeer in _remotePeers)
			{
				RemotePeerState value = remotePeer.Value;
				float num = unscaledTime - value.LastFrameAt;
				if (num > 35f)
				{
					list.Add(remotePeer.Key);
					continue;
				}
				if (num > 12f)
				{
					value.GameObject.SetActive(false);
					if (num > 18f && ((Object)(object)value.Anchor == (Object)null || !((Component)value.Anchor).gameObject.activeInHierarchy) && text == null)
					{
						text = value.PlayerId;
					}
					continue;
				}
				if (unscaledTime >= value.NextResolveAt)
				{
					RefreshRemotePeerAnchors(value, unscaledTime);
					value.NextResolveAt = unscaledTime + 1.2f;
					if ((Object)(object)value.Anchor != (Object)null || (Object)(object)value.AliveAnchor != (Object)null || (Object)(object)value.DeathAnchor != (Object)null)
					{
						value.NextAnchorWarningAt = 0f;
					}
				}
				if ((Object)(object)value.Anchor != (Object)null && IsModInternalHierarchy(value.Anchor))
				{
					_fileLogger?.WriteWarning("Remote anchor rejected as internal mod object: playerId=" + value.PlayerId + ", anchor='" + BuildTransformPath(value.Anchor) + "'.");
					value.Anchor = null;
					value.AnchorSource = RemoteAnchorSource.None;
					value.LifecycleState = RemotePlayerLifecycleState.Unknown;
					value.NextResolveAt = unscaledTime + 1.2f;
				}
				if ((Object)(object)value.Anchor != (Object)null && (Object)(object)val != (Object)null)
				{
					value.UsingAnchorFallback = false;
					value.LastAnchorSeenAt = unscaledTime;
					PositionRemotePeerAtAnchor(value, val);
					continue;
				}
				if ((Object)(object)value.Anchor == (Object)null && (Object)(object)val != (Object)null && CanUseAnchorFallback(value, unscaledTime))
				{
					PositionRemotePeerAtFallback(value, val, unscaledTime);
					continue;
				}
				if ((Object)(object)value.Anchor == (Object)null && unscaledTime >= value.NextAnchorWarningAt)
				{
					value.NextAnchorWarningAt = unscaledTime + 8f;
					_fileLogger?.WriteWarning("Remote anchor not found: playerId=" + value.PlayerId + ".");
				}
				value.UsingAnchorFallback = false;
				value.GameObject.SetActive(false);
			}
			if (!string.IsNullOrWhiteSpace(text))
			{
				HandlePeerApparentDisconnect(text);
			}
			else
			{
				if (list.Count == 0)
				{
					return;
				}
				foreach (string item in list)
				{
					if (_remotePeers.TryGetValue(item, out RemotePeerState value2))
					{
						DisposeRemotePeer(value2);
						_remotePeers.Remove(item);
					}
				}
			}
		}

		private void RefreshRemotePeerAnchors(RemotePeerState peer, float now)
		{
			RemoteAnchorSource source;
			Transform val = ResolveAliveRemoteAnchor(peer.PlayerId, out source);
			RemoteAnchorSource source2;
			Transform val2 = ResolveDeadRemoteAnchor(peer.PlayerId, out source2);
			Transform val3 = TryReuseTrustedRemoteAnchor(peer.AliveAnchor, allowDeathAnchor: false);
			Transform val4 = TryReuseTrustedRemoteAnchor(peer.DeathAnchor, allowDeathAnchor: true);
			if ((Object)(object)val == (Object)null && (Object)(object)val3 != (Object)null && peer.LifecycleState == RemotePlayerLifecycleState.Alive && (Object)(object)val2 == (Object)null)
			{
				val = val3;
				source = RemoteAnchorSource.NameFallbackTrusted;
			}
			if ((Object)(object)val2 == (Object)null && (Object)(object)val4 != (Object)null)
			{
				val2 = val4;
				source2 = RemoteAnchorSource.NameFallbackTrusted;
			}
			if ((Object)(object)val2 == (Object)null && (Object)(object)val != (Object)null)
			{
				val2 = TryResolveDeathHeadFromAliveAnchor(val);
				if ((Object)(object)val2 != (Object)null)
				{
					source2 = RemoteAnchorSource.Direct;
					_fileLogger?.WriteInfo("Death anchor resolved via alive anchor fallback: playerId=" + peer.PlayerId + ", deathAnchor='" + ((Object)val2).name + "'.");
				}
			}
			peer.AliveAnchor = val;
			peer.DeathAnchor = val2;
			RemotePlayerLifecycleState remotePlayerLifecycleState = DetermineRemotePeerLifecycleState(peer, val, val2, now);
			if (remotePlayerLifecycleState != peer.LifecycleState)
			{
				_fileLogger?.WriteInfo(string.Format("Remote lifecycle changed: playerId={0}, {1} -> {2}, aliveAnchor={3}, deathAnchor={4}.", peer.PlayerId, peer.LifecycleState, remotePlayerLifecycleState, ((Object)(object)val != (Object)null) ? ((Object)val).name : "<null>", ((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "<null>"));
				peer.LifecycleState = remotePlayerLifecycleState;
			}
			switch (peer.LifecycleState)
			{
			case RemotePlayerLifecycleState.Alive:
				peer.Anchor = val;
				peer.AnchorSource = source;
				break;
			case RemotePlayerLifecycleState.Dead:
				peer.Anchor = val2;
				peer.AnchorSource = source2;
				break;
			case RemotePlayerLifecycleState.AwaitingAliveAnchor:
				peer.Anchor = val2;
				peer.AnchorSource = (((Object)(object)val2 != (Object)null) ? source2 : RemoteAnchorSource.None);
				break;
			default:
				peer.Anchor = null;
				peer.AnchorSource = RemoteAnchorSource.None;
				break;
			}
			if ((Object)(object)peer.Anchor != (Object)null)
			{
				peer.LastAnchorSeenAt = now;
			}
			if ((Object)(object)peer.Anchor == (Object)null && now >= peer.NextDiagnosticLogAt)
			{
				peer.NextDiagnosticLogAt = now + 30f;
				LogAnchorResolutionFailureDiagnostics(peer.PlayerId);
			}
		}

		private static RemotePlayerLifecycleState DetermineRemotePeerLifecycleState(RemotePeerState peer, Transform? aliveAnchor, Transform? deathAnchor, float now)
		{
			if ((Object)(object)aliveAnchor != (Object)null)
			{
				return RemotePlayerLifecycleState.Alive;
			}
			if ((Object)(object)deathAnchor != (Object)null)
			{
				return RemotePlayerLifecycleState.Dead;
			}
			if (peer.LifecycleState == RemotePlayerLifecycleState.Dead || peer.LifecycleState == RemotePlayerLifecycleState.AwaitingAliveAnchor)
			{
				return RemotePlayerLifecycleState.AwaitingAliveAnchor;
			}
			if (peer.LifecycleState == RemotePlayerLifecycleState.Alive && now - peer.LastAnchorSeenAt < 5f)
			{
				return RemotePlayerLifecycleState.Alive;
			}
			return RemotePlayerLifecycleState.Unknown;
		}

		private Transform? ResolveAliveRemoteAnchor(string remotePlayerId, out RemoteAnchorSource source)
		{
			return ResolveRemotePlayerAnchor(remotePlayerId, preferDeathAnchor: false, out source);
		}

		private Transform? ResolveDeadRemoteAnchor(string remotePlayerId, out RemoteAnchorSource source)
		{
			return ResolveRemotePlayerAnchor(remotePlayerId, preferDeathAnchor: true, out source);
		}

		private Transform? ResolveRemotePlayerAnchor(string remotePlayerId, bool preferDeathAnchor, out RemoteAnchorSource source)
		{
			source = RemoteAnchorSource.None;
			if (string.IsNullOrWhiteSpace(remotePlayerId))
			{
				return null;
			}
			ulong? numericPlayerId = TryParseNumericPlayerId(remotePlayerId);
			Transform val = null;
			MonoBehaviour val2 = null;
			int num = int.MinValue;
			bool flag = false;
			MonoBehaviour[] array = Object.FindObjectsOfType<MonoBehaviour>();
			foreach (MonoBehaviour val3 in array)
			{
				if ((Object)(object)val3 == (Object)null || !((Component)val3).gameObject.activeInHierarchy || !IsLikelyRemoteAnchorCarrier(val3) || (TryReadLocalFlag(val3, out var isLocal) && isLocal) || !TryMatchPlayerId(val3, remotePlayerId, numericPlayerId))
				{
					continue;
				}
				Transform root = ((Component)val3).transform.root ?? ((Component)val3).transform;
				Transform val4 = (Transform)(preferDeathAnchor ? ((object)ResolveDeathHeadAnchor(root)) : ((object)ResolveAliveHeadAnchor(root)));
				if ((Object)(object)val4 == (Object)null || IsRejectedRemoteAnchorCandidate(val4, preferDeathAnchor) || IsLocalHierarchy(val4))
				{
					continue;
				}
				bool flag2 = (Object)(object)val4 != (Object)(object)((Component)val3).transform;
				bool flag3 = IsDeathAnchorCandidate(val4, val3);
				if (preferDeathAnchor == flag3)
				{
					int num2 = ScoreRemoteAnchorCandidate(val4, val3, flag2);
					if (num2 > num)
					{
						num = num2;
						val = val4;
						val2 = val3;
						flag = flag2;
					}
				}
			}
			if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null)
			{
				source = RemoteAnchorSource.Direct;
				_fileLogger?.WriteInfo($"Remote anchor resolved: playerId={remotePlayerId}, anchor='{((Object)val).name}', isHead={flag}, root='{((Object)((Component)val2).gameObject).name}', score={num}.");
				return val;
			}
			return null;
		}

		private static bool IsDeathAnchorCandidate(Transform? anchor, MonoBehaviour behaviour)
		{
			string name = ((object)behaviour).GetType().Name;
			if ((Object)(object)anchor == (Object)null)
			{
				return name.IndexOf("PlayerDeathHead", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			if (((Object)anchor).name.Equals("code_head_top", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			string text = BuildTransformPath(anchor);
			if (text.IndexOf("Player Death Head", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("PlayerDeathHead", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("corpse", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("ragdoll", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("dead", StringComparison.OrdinalIgnoreCase) < 0)
			{
				return name.IndexOf("PlayerDeathHead", StringComparison.OrdinalIgnoreCase) >= 0;
			}
			return true;
		}

		private static Transform? TryResolveDeathHeadFromAliveAnchor(Transform aliveAnchor)
		{
			Transform[] componentsInChildren = ((Component)(aliveAnchor.root ?? aliveAnchor)).GetComponentsInChildren<Transform>(true);
			foreach (Transform val in componentsInChildren)
			{
				string name = ((Object)val).name;
				if (name.IndexOf("Player Death Head", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("PlayerDeathHead", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return val;
				}
			}
			return null;
		}

		private void LogAnchorResolutionFailureDiagnostics(string remotePlayerId)
		{
			ulong? numericPlayerId = TryParseNumericPlayerId(remotePlayerId);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			List<string> list = new List<string>();
			MonoBehaviour[] array = Object.FindObjectsOfType<MonoBehaviour>();
			foreach (MonoBehaviour val in array)
			{
				if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy || !IsLikelyRemoteAnchorCarrier(val))
				{
					continue;
				}
				num++;
				string name = ((object)val).GetType().Name;
				string name2 = ((Object)((Component)val).gameObject).name;
				string text = (((Object)(object)((Component)val).transform.root != (Object)null) ? ((Object)((Component)val).transform.root).name : name2);
				if (TryReadLocalFlag(val, out var isLocal) && isLocal)
				{
					num4++;
					continue;
				}
				bool flag = TryMatchPlayerId(val, remotePlayerId, numericPlayerId);
				if (flag)
				{
					num2++;
					Transform root = ((Component)val).transform.root ?? ((Component)val).transform;
					Transform val2 = ResolveAliveHeadAnchor(root);
					Transform val3 = ResolveDeathHeadAnchor(root);
					bool flag2 = (Object)(object)val2 == (Object)null || IsRejectedRemoteAnchorCandidate(val2);
					bool flag3 = (Object)(object)val3 == (Object)null || IsRejectedRemoteAnchorCandidate(val3, skipParkingCheck: true);
					bool flag4 = ((Object)(object)val2 != (Object)null && IsLocalHierarchy(val2)) || ((Object)(object)val3 != (Object)null && IsLocalHierarchy(val3));
					list.Insert(0, string.Format("MATCH: [{0}@'{1}' root='{2}' alive={3} rejectedAlive={4} death={5} rejectedDeath={6} local={7}]", name, name2, text, ((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "null", flag2, ((Object)(object)val3 != (Object)null) ? ((Object)val3).name : "null", flag3, flag4));
					if (flag2 && flag3)
					{
						num3++;
					}
				}
				else if (text.IndexOf("PlayerAvatar", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
					object obj = ((object)val).GetType().GetProperty("photonView", bindingAttr)?.GetValue(val, null);
					if (obj != null)
					{
						object obj2 = obj.GetType().GetProperty("Owner", bindingAttr)?.GetValue(obj, null);
						if (obj2 != null)
						{
							object obj3 = obj2.GetType().GetProperty("UserId", bindingAttr)?.GetValue(obj2, null);
							object obj4 = obj2.GetType().GetProperty("ActorNumber", bindingAttr)?.GetValue(obj2, null);
							object obj5 = obj2.GetType().GetProperty("NickName", bindingAttr)?.GetValue(obj2, null);
							list.Add($"[FAILED: {name}@'{name2}' root='{text}' pv.Owner(User={obj3}, Actor={obj4}, Nick={obj5})]");
						}
					}
				}
				else if (list.Count < 5)
				{
					list.Add($"[{name}@'{name2}' root='{text}' idMatch={flag}]");
				}
			}
			IReadOnlyList<ulong> lobbyMemberIds = SteamLobbyAccessor.GetLobbyMemberIds();
			_fileLogger?.WriteWarning($"Anchor resolution diagnostics: playerId={remotePlayerId}, carriers={num}, local={num4}, " + $"idMatched={num2}, rejected={num3}. " + "LobbyMembers: " + string.Join(", ", lobbyMemberIds) + ". " + ((list.Count > 0) ? ("Candidates: " + string.Join(", ", list)) : "No candidates found."));
		}

		private static ulong? TryParseNumericPlayerId(string playerId)
		{
			if (!ulong.TryParse(playerId, out var result))
			{
				return null;
			}
			return result;
		}

		private static bool TryMatchPlayerId(MonoBehaviour behaviour, string playerId, ulong? numericPlayerId)
		{
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			Type type = ((object)behaviour).GetType();
			PropertyInfo[] properties = type.GetProperties(bindingAttr);
			foreach (PropertyInfo propertyInfo in properties)
			{
				if (!propertyInfo.CanRead)
				{
					continue;
				}
				try
				{
					if (propertyInfo.GetIndexParameters().Length != 0 || !IsMemberMatchingPlayerId(propertyInfo.GetValue(behaviour, null), playerId, numericPlayerId))
					{
						continue;
					}
					return true;
				}
				catch
				{
				}
			}
			FieldInfo[] fields = type.GetFields(bindingAttr);
			foreach (FieldInfo fieldInfo in fields)
			{
				try
				{
					if (IsMemberMatchingPlayerId(fieldInfo.GetValue(behaviour), playerId, numericPlayerId))
					{
						return true;
					}
				}
				catch
				{
				}
			}
			object obj3 = type.GetProperty("photonView", bindingAttr)?.GetValue(behaviour, null);
			if (obj3 != null && TryMatchPhotonOwnerId(obj3, playerId, numericPlayerId))
			{
				return true;
			}
			try
			{
				MonoBehaviour[] componentsInParent = ((Component)behaviour).GetComponentsInParent<MonoBehaviour>(true);
				foreach (MonoBehaviour val in componentsInParent)
				{
					if ((Object)(object)val == (Object)null || val == behaviour)
					{
						continue;
					}
					Type type2 = ((object)val).GetType();
					if (type2.Name != "PhotonView")
					{
						object obj4 = type2.GetProperty("photonView", bindingAttr)?.GetValue(val, null);
						if (obj4 != null && TryMatchPhotonOwnerId(obj4, playerId, numericPlayerId))
						{
							return true;
						}
					}
					else if (TryMatchPhotonOwnerId(val, playerId, numericPlayerId))
					{
						return true;
					}
				}
			}
			catch
			{
			}
			return false;
		}

		private Transform? TryReuseTrustedRemoteAnchor(Transform? anchor, bool allowDeathAnchor)
		{
			if ((Object)(object)anchor == (Object)null)
			{
				return null;
			}
			if (IsRejectedRemoteAnchorCandidate(anchor, allowDeathAnchor))
			{
				return null;
			}
			if (IsLocalHierarchy(anchor))
			{
				return null;
			}
			if (!allowDeathAnchor && IsTransientRemoteAnchor(anchor))
			{
				return null;
			}
			return anchor;
		}

		private static bool TryMatchPhotonOwnerId(object photonView, string playerId, ulong? numericPlayerId)
		{
			BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			object obj = photonView.GetType().GetProperty("Owner", bindingAttr)?.GetValue(photonView, null);
			if (obj == null)
			{
				return false;
			}
			Type type = obj.GetType();
			PropertyInfo property = type.GetProperty("UserId", bindingAttr);
			if (property != null && IsMemberMatchingPlayerId(property.GetValue(obj, null), playerId, numericPlayerId))
			{
				return true;
			}
			PropertyInfo property2 = type.GetProperty("ActorNumber", bindingAttr);
			if (property2 != null && IsMemberMatchingPlayerId(property2.GetValue(obj, null), playerId, numericPlayerId))
			{
				return true;
			}
			PropertyInfo property3 = type.GetProperty("NickName", bindingAttr);
			if (property3 != null && IsMemberMatchingPlayerId(property3.GetValue(obj, null), playerId, numericPlayerId))
			{
				return true;
			}
			PropertyInfo property4 = type.GetProperty("CustomProperties", bindingAttr);
			if (property4 != null && property4.GetValue(obj, null) is IDictionary dictionary)
			{
				foreach (object value in dictionary.Values)
				{
					if (IsMemberMatchingPlayerId(value, playerId, numericPlayerId))
					{
						return true;
					}
				}
			}
			return false;
		}

		private static bool IsMemberMatchingPlayerId(object? value, string playerId, ulong? numericPlayerId)
		{
			if (value == null)
			{
				return false;
			}
			if (!(value is string a))
			{
				if (!(value is ulong num))
				{
					if (!(value is long num2))
					{
						if (!(value is uint num3))
						{
							if (value is int num4)
							{
								if (numericPlayerId.HasValue)
								{
									return num4 == (int)numericPlayerId.Value;
								}
								return false;
							}
							return false;
						}
						if (numericPlayerId.HasValue)
						{
							return num3 == (uint)numericPlayerId.Value;
						}
						return false;
					}
					if (numericPlayerId.HasValue)
					{
						return num2 == (long)numericPlayerId.Value;
					}
					return false;
				}
				if (numericPlayerId.HasValue)
				{
					return num == numericPlayerId.Value;
				}
				return false;
			}
			return string.Equals(a, playerId, StringComparison.Ordinal);
		}

		private void DisposeRemotePeers(string reason)
		{
			foreach (RemotePeerState value in _remotePeers.Values)
			{
				DisposeRemotePeer(value);
			}
			_remotePeers.Clear();
			if (!string.IsNullOrWhiteSpace(reason))
			{
				_fileLogger?.WriteInfo("Remote peers cleared. reason=" + reason + ".");
			}
		}

		private static void DisposeRemotePeer(RemotePeerState peer)
		{
			if ((Object)(object)peer.GameObject != (Object)null)
			{
				Object.Destroy((Object)(object)peer.GameObject);
			}
			if ((Object)(object)peer.Material != (Object)null)
			{
				Object.Destroy((Object)(object)peer.Material);
			}
			if ((Object)(object)peer.Texture != (Object)null)
			{
				Object.Destroy((Object)(object)peer.Texture);
				peer.Texture = null;
			}
		}

		private void HandlePeerApparentDisconnect(string playerId)
		{
			if (_peersMarkedDisconnected.Add(playerId))
			{
				_inboundVideoSources.Remove(playerId);
				_fileLogger?.WriteInfo("Peer " + playerId + " is not sending frames and is missing from the scene. Assuming disconnect. Resetting inbound video state and sending a probe for faster reconnection.");
				ResetAllRemoteWebcamsAndReprobe("peer-disconnect:" + playerId);
			}
		}

		private static string ShortId(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "none";
			}
			if (value.Length > 8)
			{
				return value.Substring(Math.Max(0, value.Length - 8), 8);
			}
			return value;
		}

		private string ResolveRequestedCamera(string requestedDevice, IReadOnlyList<string> options)
		{
			if (options.Count == 0)
			{
				return "Default";
			}
			if (string.IsNullOrWhiteSpace(requestedDevice) || string.Equals(requestedDevice, "Default", StringComparison.Ordinal))
			{
				return "Default";
			}
			if (options.Contains<string>(requestedDevice, StringComparer.Ordinal))
			{
				return requestedDevice;
			}
			_fileLogger?.WriteWarning("Requested camera '" + requestedDevice + "' is unavailable. 'Default' will be used instead.");
			SetCameraDeviceValueWithoutEvents("Default");
			return "Default";
		}

		private void SetCameraDeviceValueWithoutEvents(string value)
		{
			if (_cameraDevice == null)
			{
				return;
			}
			_suppressCameraConfigEvents = true;
			try
			{
				_cameraDevice.Value = value;
			}
			finally
			{
				_suppressCameraConfigEvents = false;
			}
		}

		private void StopLocalCamera(string reason)
		{
			if (_cameraStartupCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_cameraStartupCoroutine);
				_cameraStartupCoroutine = null;
			}
			if ((Object)(object)_localCameraTexture == (Object)null)
			{
				return;
			}
			try
			{
				if (_localCameraTexture.isPlaying)
				{
					_localCameraTexture.Stop();
				}
			}
			catch (Exception exception)
			{
				_fileLogger?.WriteError("Failed to stop local camera.", exception);
			}
			finally
			{
				Object.Destroy((Object)(object)_localCameraTexture);
				_localCameraTexture = null;
				_activeCameraDevice = null;
				_outboundVideoPublisher?.ResetSchedule();
				if ((Object)(object)_overlayRawImage != (Object)null)
				{
					_overlayRawImage.texture = null;
				}
				SetPreviewVisible(visible: false);
				SetOverlayVisible(visible: false);
				_fileLogger?.WriteInfo("Local camera stopped. reason=" + reason + ".");
			}
		}

		private void BindPreviewToCurrentTexture()
		{
			if (!((Object)(object)_localCameraTexture == (Object)null))
			{
				EnsurePreviewObject();
				EnsureOverlayUi();
				if ((Object)(object)_previewMaterial != (Object)null)
				{
					_previewMaterial.mainTexture = (Texture)(object)_localCameraTexture;
				}
				if ((Object)(object)_overlayRawImage != (Object)null)
				{
					_overlayRawImage.texture = (Texture)(object)_localCameraTexture;
				}
				ApplyMirrorToPreviewMaterial();
				UpdatePreviewVisibilityState();
			}
		}

		private void EnsurePreviewObject()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Expected O, but got Unknown
			if (!((Object)(object)_previewObject != (Object)null))
			{
				_previewObject = new GameObject("RepoWebcamPreview");
				_previewObject.AddComponent<MeshFilter>().mesh = CreateCircularMesh(32);
				Object.DontDestroyOnLoad((Object)(object)_previewObject);
				Component component = _previewObject.GetComponent("Collider");
				if ((Object)(object)component != (Object)null)
				{
					Object.Destroy((Object)(object)component);
				}
				_previewRenderer = _previewObject.AddComponent<MeshRenderer>();
				Shader val = ResolvePreviewShader();
				_previewMaterial = new Material(val)
				{
					name = "RepoWebcamPreviewMaterial",
					color = Color.white
				};
				((Renderer)_previewRenderer).sharedMaterial = _previewMaterial;
				((Renderer)_previewRenderer).shadowCastingMode = (ShadowCastingMode)0;
				((Renderer)_previewRenderer).receiveShadows = false;
				((Renderer)_previewRenderer).allowOcclusionWhenDynamic = false;
				ApplyRenderLayer(_previewObject, ResolvePreferredRenderLayer(_headAnchor ?? _localPlayerRoot), isRemote: false, null);
				SetPreviewVisible(visible: false);
				_fileLogger?.WriteInfo("Preview object 'RepoWebcamPreview' created for webcam rendering.");
			}
		}

		private Shader ResolvePreviewShader()
		{
			if ((Object)(object)_cachedPreviewShader != (Object)null)
			{
				return _cachedPreviewShader;
			}
			LogSceneShaders();
			string[] array = new string[4] { "Sprites/Default", "Unlit/Texture", "Universal Render Pipeline/Unlit", "Particles/Standard Unlit" };
			foreach (string text in array)
			{
				Shader val = Shader.Find(text);
				if ((Object)(object)val != (Object)null)
				{
					_fileLogger?.WriteInfo("ResolvePreviewShader: found '" + text + "'.");
					_cachedPreviewShader = val;
					return val;
				}
			}
			Shader val2 = TryBorrowShaderFromScene(requireUnlit: true);
			if ((Object)(object)val2 != (Object)null)
			{
				_fileLogger?.WriteInfo("ResolvePreviewShader: using scene unlit shader '" + ((Object)val2).name + "'.");
				_cachedPreviewShader = val2;
				return val2;
			}
			Shader val3 = TryBorrowShaderFromScene();
			if ((Object)(object)val3 != (Object)null)
			{
				_fileLogger?.WriteInfo("ResolvePreviewShader: using scene shader '" + ((Object)val3).name + "' (lit).");
				_cachedPreviewShader = val3;
				return val3;
			}
			Shader val4 = Shader.Find("Standard");
			if ((Object)(object)val4 != (Object)null)
			{
				_fileLogger?.WriteInfo("ResolvePreviewShader: using Standard (lit, last resort).");
				_cachedPreviewShader = val4;
				return val4;
			}
			_fileLogger?.WriteWarning("ResolvePreviewShader: shader not found, fallback=Sprites/Default.");
			_cachedPreviewShader = Shader.Find("Sprites/Default");
			return _cachedPreviewShader;
		}

		private void LogSceneShaders()
		{
			if (_sceneShadersLogged)
			{
				return;
			}
			_sceneShadersLogged = true;
			HashSet<string> hashSet = new HashSet<string>();
			MeshRenderer[] array = Object.FindObjectsOfType<MeshRenderer>();
			foreach (MeshRenderer val in array)
			{
				if (!((Object)(object)val == (Object)null) && !((Object)(object)((Renderer)val).sharedMaterial == (Object)null) && !((Object)(object)((Renderer)val).sharedMaterial.shader == (Object)null))
				{
					hashSet.Add(((Object)((Renderer)val).sharedMaterial.shader).name);
				}
			}
			_fileLogger?.WriteInfo(string.Format("Scene shaders ({0}): {1}.", hashSet.Count, string.Join(" | ", hashSet)));
		}

		private Shader? TryBorrowShaderFromScene(bool requireUnlit = false)
		{
			MeshRenderer[] array = Object.FindObjectsOfType<MeshRenderer>();
			foreach (MeshRenderer val in array)
			{
				if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy || IsModInternalHierarchy(((Component)val).transform) || IsUiOrMenuHierarchy(((Component)val).transform))
				{
					continue;
				}
				Material sharedMaterial = ((Renderer)val).sharedMaterial;
				if (!((Object)(object)sharedMaterial == (Object)null) && !((Object)(object)sharedMaterial.shader == (Object)null))
				{
					string name = ((Object)sharedMaterial.shader).name;
					if (name.IndexOf("Sprite", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Particle", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Hidden", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("Error", StringComparison.OrdinalIgnoreCase) < 0 && name.IndexOf("GUI", StringComparison.OrdinalIgnoreCase) < 0 && (!requireUnlit || name.IndexOf("Unlit", StringComparison.OrdinalIgnoreCase) >= 0))
					{
						return sharedMaterial.shader;
					}
				}
			}
			return null;
		}

		private void SetPreviewVisible(bool visible)
		{
			if (!((Object)(object)_previewObject == (Object)null))
			{
				_previewObject.SetActive(visible);
			}
		}

		private void UpdatePreviewVisibilityState()
		{
			bool overlayVisible = (Object)(object)_localCameraTexture != (Object)null && _localCameraTexture.isPlaying && (_showLocalPreview?.Value ?? true);
			SetPreviewVisible(visible: false);
			SetOverlayVisible(overlayVisible);
		}

		private void DestroyPreviewObject()
		{
			if ((Object)(object)_previewObject != (Object)null)
			{
				Object.Destroy((Object)(object)_previewObject);
				_previewObject = null;
			}
			if ((Object)(object)_previewMaterial != (Object)null)
			{
				Object.Destroy((Object)(object)_previewMaterial);
				_previewMaterial = null;
			}
			_previewRenderer = null;
			_localPlayerRoot = null;
			_headAnchor = null;
			_anchorLogged = false;
			_previewRenderLayer = -1;
		}

		private void EnsureOverlayUi()
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_015d: Unknown result type (might be due to invalid IL or missing references)
			//IL_016e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Expected O, but got Unknown
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0237: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_overlayCanvasObject != (Object)null))
			{
				_overlayCanvasObject = new GameObject("RepoWebcamOverlayCanvas", new Type[3]
				{
					typeof(Canvas),
					typeof(CanvasScaler),
					typeof(GraphicRaycaster)
				});
				Object.DontDestroyOnLoad((Object)(object)_overlayCanvasObject);
				Canvas component = _overlayCanvasObject.GetComponent<Canvas>();
				component.renderMode = (RenderMode)0;
				component.sortingOrder = 32000;
				CanvasScaler component2 = _overlayCanvasObject.GetComponent<CanvasScaler>();
				component2.uiScaleMode = (ScaleMode)1;
				component2.referenceResolution = new Vector2(1920f, 1080f);
				component2.matchWidthOrHeight = 1f;
				GameObject val = new GameObject("RepoWebcamOverlayPanel", new Type[3]
				{
					typeof(RectTransform),
					typeof(CanvasRenderer),
					typeof(Image)
				});
				val.transform.SetParent(_overlayCanvasObject.transform, false);
				_overlayRectTransform = val.GetComponent<RectTransform>();
				_overlayRectTransform.anchorMin = new Vector2(1f, 0f);
				_overlayRectTransform.anchorMax = new Vector2(1f, 0f);
				_overlayRectTransform.pivot = new Vector2(1f, 0f);
				_overlayRectTransform.anchoredPosition = new Vector2(-20f, 20f);
				Image component3 = val.GetComponent<Image>();
				((Graphic)component3).color = OverlayPanelColor;
				((Graphic)component3).raycastTarget = false;
				component3.sprite = GetOrCreateCircleSprite();
				val.AddComponent<Mask>().showMaskGraphic = true;
				GameObject val2 = new GameObject("RepoWebcamOverlayImage", new Type[3]
				{
					typeof(RectTransform),
					typeof(CanvasRenderer),
					typeof(RawImage)
				});
				val2.transform.SetParent(val.transform, false);
				RectTransform component4 = val2.GetComponent<RectTransform>();
				component4.anchorMin = Vector2.zero;
				component4.anchorMax = Vector2.one;
				component4.offsetMin = new Vector2(6f, 6f);
				component4.offsetMax = new Vector2(-6f, -6f);
				_overlayRawImage = val2.GetComponent<RawImage>();
				((Graphic)_overlayRawImage).color = Color.white;
				((Graphic)_overlayRawImage).raycastTarget = false;
				WebCamTexture? localCameraTexture = _localCameraTexture;
				int textureWidth = Mathf.Max(1, (localCameraTexture != null) ? ((Texture)localCameraTexture).width : 16);
				WebCamTexture? localCameraTexture2 = _localCameraTexture;
				UpdateOverlayRectTransform(textureWidth, Mathf.Max(1, (localCameraTexture2 != null) ? ((Texture)localCameraTexture2).height : 9));
				SetOverlayVisible(visible: false);
				_fileLogger?.WriteInfo("Screen overlay UI created for local preview.");
			}
		}

		private void SetOverlayVisible(bool visible)
		{
			if (!((Object)(object)_overlayCanvasObject == (Object)null))
			{
				_overlayCanvasObject.SetActive(visible);
			}
		}

		private Sprite GetOrCreateCircleSprite()
		{
			//IL_001f: 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_0030: Expected O, but got Unknown
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_circleSprite != (Object)null)
			{
				return _circleSprite;
			}
			int num = 128;
			Texture2D val = new Texture2D(num, num, (TextureFormat)4, false)
			{
				name = "CircleMaskTempTex"
			};
			Color32[] array = (Color32[])(object)new Color32[num * num];
			float num2 = (float)num / 2f;
			float num3 = (float)num / 2f - 1f;
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					float num4 = (float)j + 0.5f - num2;
					float num5 = (float)i + 0.5f - num2;
					float num6 = Mathf.Sqrt(num4 * num4 + num5 * num5);
					float num7 = Mathf.Clamp01(num3 - num6 + 1f);
					array[i * num + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num7 * 255f));
				}
			}
			val.SetPixels32(array);
			val.Apply(false, true);
			_circleSprite = Sprite.Create(val, new Rect(0f, 0f, (float)num, (float)num), new Vector2(0.5f, 0.5f));
			((Object)_circleSprite).name = "RepoWebcamCircleMask";
			return _circleSprite;
		}

		private void DestroyOverlayUi()
		{
			if ((Object)(object)_overlayCanvasObject != (Object)null)
			{
				Object.Destroy((Object)(object)_overlayCanvasObject);
				_overlayCanvasObject = null;
			}
			if ((Object)(object)_circleSprite != (Object)null)
			{
				if ((Object)(object)_circleSprite.texture != (Object)null)
				{
					Object.Destroy((Object)(object)_circleSprite.texture);
				}
				Object.Destroy((Object)(object)_circleSprite);
				_circleSprite = null;
			}
			_overlayRectTransform = null;
			_overlayRawImage = null;
			_overlayScreenWidth = 0;
			_overlayScreenHeight = 0;
		}

		private void UpdateOverlayRectTransform(int textureWidth, int textureHeight)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0467: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_049b: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0249: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_027d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_022a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0390: Unknown result type (might be due to invalid IL or missing references)
			//IL_03aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03de: Unknown result type (might be due to invalid IL or missing references)
			//IL_03fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0417: Unknown result type (might be due to invalid IL or missing references)
			//IL_0431: Unknown result type (might be due to invalid IL or missing references)
			//IL_044b: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0304: Unknown result type (might be due to invalid IL or missing references)
			//IL_0323: Unknown result type (might be due to invalid IL or missing references)
			//IL_033d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_overlayRectTransform == (Object)null))
			{
				float num = Mathf.Min(_overlaySize?.Value ?? 200f, (float)Screen.width * 0.42f);
				float num2 = num;
				_overlayRectTransform.sizeDelta = new Vector2(num, num2);
				switch (_localPreviewPosition?.Value ?? "TopCenter")
				{
				case "TopLeft":
					_overlayRectTransform.anchorMin = new Vector2(0f, 1f);
					_overlayRectTransform.anchorMax = new Vector2(0f, 1f);
					_overlayRectTransform.pivot = new Vector2(0f, 1f);
					_overlayRectTransform.anchoredPosition = new Vector2(20f, -20f);
					break;
				case "TopCenter":
					_overlayRectTransform.anchorMin = new Vector2(0.5f, 1f);
					_overlayRectTransform.anchorMax = new Vector2(0.5f, 1f);
					_overlayRectTransform.pivot = new Vector2(0.5f, 1f);
					_overlayRectTransform.anchoredPosition = new Vector2(0f, -20f);
					break;
				case "TopRight":
					_overlayRectTransform.anchorMin = new Vector2(1f, 1f);
					_overlayRectTransform.anchorMax = new Vector2(1f, 1f);
					_overlayRectTransform.pivot = new Vector2(1f, 1f);
					_overlayRectTransform.anchoredPosition = new Vector2(-20f, -20f);
					break;
				case "MiddleLeft":
					_overlayRectTransform.anchorMin = new Vector2(0f, 0.5f);
					_overlayRectTransform.anchorMax = new Vector2(0f, 0.5f);
					_overlayRectTransform.pivot = new Vector2(0f, 0.5f);
					_overlayRectTransform.anchoredPosition = new Vector2(20f, 0f);
					break;
				case "MiddleRight":
					_overlayRectTransform.anchorMin = new Vector2(1f, 0.5f);
					_overlayRectTransform.anchorMax = new Vector2(1f, 0.5f);
					_overlayRectTransform.pivot = new Vector2(1f, 0.5f);
					_overlayRectTransform.anchoredPosition = new Vector2(-20f, 0f);
					break;
				case "BottomLeft":
					_overlayRectTransform.anchorMin = new Vector2(0f, 0f);
					_overlayRectTransform.anchorMax = new Vector2(0f, 0f);
					_overlayRectTransform.pivot = new Vector2(0f, 0f);
					_overlayRectTransform.anchoredPosition = new Vector2(20f, 20f);
					break;
				case "BottomRight":
					_overlayRectTransform.anchorMin = new Vector2(1f, 0f);
					_overlayRectTransform.anchorMax = new Vector2(1f, 0f);
					_overlayRectTransform.pivot = new Vector2(1f, 0f);
					_overlayRectTransform.anchoredPosition = new Vector2(-20f, 20f);
					break;
				default:
					_overlayRectTransform.anchorMin = new Vector2(0f, 1f);
					_overlayRectTransform.anchorMax = new Vector2(0f, 1f);
					_overlayRectTransform.pivot = new Vector2(0f, 1f);
					_overlayRectTransform.anchoredPosition = new Vector2(20f, -20f);
					break;
				}
				_overlayScreenWidth = Screen.width;
				_overlayScreenHeight = Screen.height;
			}
		}

		private void UpdateOverlayLayoutIfNeeded(bool force = false)
		{
			ConfigEntry<bool>? showLocalPreview = _showLocalPreview;
			if ((showLocalPreview == null || showLocalPreview.Value) && !((Object)(object)_overlayRectTransform == (Object)null) && !((Object)(object)_localCameraTexture == (Object)null) && (force || _overlayScreenWidth != Screen.width || _overlayScreenHeight != Screen.height))
			{
				UpdateOverlayRectTransform(Mathf.Max(1, ((Texture)_localCameraTexture).width), Mathf.Max(1, ((Texture)_localCameraTexture).height));
			}
		}

		private void ApplyMirrorToPreviewMaterial()
		{
			//IL_0080: 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_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			bool mirror = _cameraMirror?.Value ?? false;
			float zoom = _cameraZoom?.Value ?? 1f;
			float offsetX = _cameraOffsetX?.Value ?? 0f;
			float offsetY = _cameraOffsetY?.Value ?? 0f;
			CalculateUV(mirror, zoom, offsetX, offsetY, (Texture?)(object)_localCameraTexture, out var scale, out var offset);
			if ((Object)(object)_previewMaterial != (Object)null)
			{
				_previewMaterial.mainTextureScale = scale;
				_previewMaterial.mainTextureOffset = offset;
			}
			if ((Object)(object)_overlayRawImage != (Object)null)
			{
				_overlayRawImage.uvRect = new Rect(offset.x, offset.y, scale.x, scale.y);
			}
		}

		private void UpdatePreviewScaleFromTexture(WebCamTexture texture)
		{
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			int textureWidth = Mathf.Max(1, ((Texture)texture).width);
			int textureHeight = Mathf.Max(1, ((Texture)texture).height);
			if ((Object)(object)_previewObject != (Object)null)
			{
				_previewObject.transform.localScale = new Vector3(0.22f, 0.22f, 1f);
			}
			UpdateOverlayRectTransform(textureWidth, textureHeight);
		}

		private void UpdatePreviewTransform()
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: 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_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0128: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_previewObject == (Object)null || !_previewObject.activeSelf)
			{
				return;
			}
			ResolveLocalAnchorIfNeeded();
			Transform val = _headAnchor ?? _localPlayerRoot;
			Camera val2 = ResolveTargetCamera();
			if ((Object)(object)val2 == (Object)null)
			{
				return;
			}
			ApplyRenderLayer(_previewObject, ResolvePreferredRenderLayer(val), isRemote: false, null);
			Transform transform = _previewObject.transform;
			if ((Object)(object)val != (Object)null)
			{
				transform.position = val.position + Vector3.up * 0.35f;
			}
			else
			{
				transform.position = ((Component)val2).transform.position + ((Component)val2).transform.forward * 0.9f + Vector3.up * -0.08f;
				if (!_fallbackLogged)
				{
					_fallbackLogged = true;
					_fileLogger?.WriteWarning("Player anchor not found. Preview is temporarily attached in front of the camera (fallback).");
					LogAnchorCandidates();
				}
			}
			Vector3 val3 = ((Component)val2).transform.position - transform.position;
			if (!(((Vector3)(ref val3)).sqrMagnitude < 0.001f))
			{
				transform.rotation = Quaternion.LookRotation(((Vector3)(ref val3)).normalized, Vector3.up);
			}
		}

		private Camera? ResolveTargetCamera()
		{
			//IL_0117: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			Camera val = null;
			float num = float.MinValue;
			Transform val2 = _headAnchor ?? _localPlayerRoot;
			Camera[] array = Object.FindObjectsOfType<Camera>();
			foreach (Camera val3 in array)
			{
				if (!((Object)(object)val3 == (Object)null) && ((Behaviour)val3).isActiveAndEnabled && !((Object)(object)val3.targetTexture != (Object)null))
				{
					float num2 = 0f;
					if ((Object)(object)val3 == (Object)(object)Camera.main)
					{
						num2 += 1000f;
					}
					if (string.Equals(((Component)val3).tag, "MainCamera", StringComparison.Ordinal))
					{
						num2 += 300f;
					}
					num2 += Mathf.Clamp(val3.depth, -100f, 100f);
					if (((uint)val3.cullingMask & (true ? 1u : 0u)) != 0)
					{
						num2 += 80f;
					}
					if (((uint)val3.cullingMask & 0x20u) != 0 && (val3.cullingMask & 1) == 0)
					{
						num2 -= 300f;
					}
					if (IsUiOrMenuHierarchy(((Component)val3).transfor