Decompiled source of CymruSyncLockTV v1.0.0

plugins/CymruSyncLockTV/CymruTVSync.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Logging;
using CymruTVSync.NetcodePatcher;
using GameNetcodeStuff;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Video;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("CymruTVSync")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Syncs the TV across all players using TVLoader.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CymruTVSync")]
[assembly: AssemblyTitle("CymruTVSync")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
[module: NetcodePatchedAssembly]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 CymruTVSync
{
	internal class HostLockStatusOverlay : MonoBehaviour
	{
		private GUIStyle _labelStyle;

		private GUIStyle _boxStyle;

		private void OnGUI()
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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_008d: Unknown result type (might be due to invalid IL or missing references)
			if (ShouldShow())
			{
				EnsureStyles();
				string text = ((!TVSyncState.LockFeatureEnabled) ? "TV LOCK CTRL: OFF" : (TVSyncState.IsLocked ? "TV LOCK: ON" : "TV LOCK: OFF"));
				float num = (float)Screen.width - 170f - 16f;
				Rect val = default(Rect);
				((Rect)(ref val))..ctor(num, 16f, 170f, 28f);
				Color color = GUI.color;
				GUI.Box(val, string.Empty, _boxStyle);
				GUI.Label(val, text, _labelStyle);
				GUI.color = color;
			}
		}

		private static bool ShouldShow()
		{
			if ((Object)(object)NetworkManager.Singleton == (Object)null)
			{
				return false;
			}
			return NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer;
		}

		private void EnsureStyles()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Expected O, but got Unknown
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			if (_boxStyle == null)
			{
				_boxStyle = new GUIStyle(GUI.skin.box);
				_boxStyle.normal.background = Texture2D.whiteTexture;
			}
			if (_labelStyle == null)
			{
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					alignment = (TextAnchor)4,
					fontStyle = (FontStyle)1,
					fontSize = 12
				};
				val.normal.textColor = Color.white;
				_labelStyle = val;
			}
			GUI.color = (TVSyncState.IsLocked ? new Color(0.72f, 0.14f, 0.14f, 0.78f) : new Color(0.12f, 0.42f, 0.18f, 0.78f));
		}
	}
	public class TVSyncNetworkHandler : MonoBehaviour
	{
		private const string RequestChangeMessageName = "CymruTVSync/RequestChange";

		private const string RequestSyncMessageName = "CymruTVSync/RequestSync";

		private const string ToggleLockMessageName = "CymruTVSync/ToggleLock";

		private const string ToggleLockFeatureMessageName = "CymruTVSync/ToggleLockFeature";

		private const string BroadcastStateMessageName = "CymruTVSync/BroadcastState";

		private const string BroadcastLockStateMessageName = "CymruTVSync/BroadcastLockState";

		private const string BroadcastLockFeatureMessageName = "CymruTVSync/BroadcastLockFeature";

		private static bool _handlersRegistered;

		private static NetworkManager _registeredNetworkManager;

		private static bool _pendingSyncRequest;

		public static TVSyncNetworkHandler Instance { get; private set; }

		public static event Action<int, double, bool, string> OnTVStateReceived;

		public static event Action<bool> OnLockStateReceived;

		public static event Action<bool> OnLockFeatureStateReceived;

		public static void EnsureInstance()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			if (!((Object)(object)Instance != (Object)null))
			{
				GameObject val = new GameObject("TVSyncNetworkHandlerLocal");
				Object.DontDestroyOnLoad((Object)(object)val);
				Instance = val.AddComponent<TVSyncNetworkHandler>();
			}
		}

		public static void EnsureMessagingReady(string source)
		{
			EnsureInstance();
			Instance.TryEnsureMessagingReady(source);
		}

		private void Awake()
		{
			if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this)
			{
				Object.Destroy((Object)(object)((Component)this).gameObject);
				return;
			}
			Instance = this;
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}

		private static string LocalRoleTag()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				return "NO-NET";
			}
			if (singleton.IsHost)
			{
				return "HOST";
			}
			if (singleton.IsServer)
			{
				return "SERVER";
			}
			if (singleton.IsClient)
			{
				return "CLIENT";
			}
			return "OFFLINE";
		}

		private static string LocalEndpointTag()
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				return "role=NO-NET local=n/a";
			}
			return $"role={LocalRoleTag()} local={singleton.LocalClientId}";
		}

		private bool TryEnsureMessagingReady(string source)
		{
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Expected O, but got Unknown
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Expected O, but got Unknown
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0119: Expected O, but got Unknown
			//IL_012c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			//IL_0149: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Expected O, but got Unknown
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Expected O, but got Unknown
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync][NGO] Messaging not ready during " + source + " because NetworkManager.Singleton is null."));
				return false;
			}
			if (_handlersRegistered && (Object)(object)_registeredNetworkManager == (Object)(object)singleton)
			{
				return true;
			}
			if (singleton.CustomMessagingManager == null)
			{
				TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync][NGO] Messaging not ready during " + source + " because CustomMessagingManager is null (" + LocalEndpointTag() + ")."));
				return false;
			}
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/RequestChange", new HandleNamedMessageDelegate(OnRequestChangeMessage));
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/RequestSync", new HandleNamedMessageDelegate(OnRequestSyncMessage));
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/ToggleLock", new HandleNamedMessageDelegate(OnToggleLockMessage));
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/ToggleLockFeature", new HandleNamedMessageDelegate(OnToggleLockFeatureMessage));
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/BroadcastState", new HandleNamedMessageDelegate(OnBroadcastStateMessage));
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/BroadcastLockState", new HandleNamedMessageDelegate(OnBroadcastLockStateMessage));
			singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/BroadcastLockFeature", new HandleNamedMessageDelegate(OnBroadcastLockFeatureMessage));
			_registeredNetworkManager = singleton;
			_handlersRegistered = true;
			if (_pendingSyncRequest && singleton.IsClient && !singleton.IsServer)
			{
				_pendingSyncRequest = false;
				TryRequestFullSyncFromServer("PendingSyncRequest");
			}
			return true;
		}

		public bool TrySendChangeToServer(string source, int clipIndex, double seekTime, bool tvOn)
		{
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			if (!TryEnsureMessagingReady(source))
			{
				return false;
			}
			string currentMediaId = BestestTV.GetCurrentMediaId();
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton.IsServer)
			{
				if (TVSyncState.LockFeatureEnabled && TVSyncState.IsLocked && string.Equals(source, "TVScript.TurnTVOnOff", StringComparison.Ordinal) && !TVSyncPatches.ConsumeHostControlIntent())
				{
					TVSyncPlugin.Log.LogDebug((object)"[CymruTVSync] Ignoring mirrored TurnTVOnOff while locked because it was not host-initiated.");
					return false;
				}
				HandleRequestChange(singleton.LocalClientId, clipIndex, seekTime, tvOn, currentMediaId);
				return true;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(4096, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref clipIndex, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<double>(ref seekTime, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref tvOn, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(currentMediaId ?? string.Empty, false);
				singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/RequestChange", 0uL, val, (NetworkDelivery)3);
				return true;
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		public bool TryRequestFullSyncFromServer(string source)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			if (!TryEnsureMessagingReady(source))
			{
				_pendingSyncRequest = true;
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null)
			{
				_pendingSyncRequest = true;
				return false;
			}
			if (singleton.IsServer)
			{
				SendCurrentStateToClient(singleton.LocalClientId);
				return true;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1);
			try
			{
				singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/RequestSync", 0uL, val, (NetworkDelivery)3);
				return true;
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		public bool TryToggleLockFromHost(string source)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if (!TryEnsureMessagingReady(source))
			{
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton.IsServer)
			{
				HandleToggleLock(singleton.LocalClientId);
				return true;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1);
			try
			{
				singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/ToggleLock", 0uL, val, (NetworkDelivery)3);
				return true;
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		public bool TryToggleLockFeatureFromHost(string source)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if (!TryEnsureMessagingReady(source))
			{
				return false;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton.IsServer)
			{
				HandleToggleLockFeature(singleton.LocalClientId);
				return true;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1);
			try
			{
				singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/ToggleLockFeature", 0uL, val, (NetworkDelivery)3);
				return true;
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		private void OnRequestChangeMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			int clipIndex = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref clipIndex, default(ForPrimitives));
			double seekTime = default(double);
			((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref seekTime, default(ForPrimitives));
			bool tvOn = default(bool);
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref tvOn, default(ForPrimitives));
			string mediaId = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref mediaId, false);
			HandleRequestChange(senderClientId, clipIndex, seekTime, tvOn, mediaId);
		}

		private void HandleRequestChange(ulong senderClientId, int clipIndex, double seekTime, bool tvOn, string mediaId)
		{
			if (mediaId == null)
			{
				mediaId = string.Empty;
			}
			bool flag = clipIndex == TVSyncState.CurrentClip;
			bool flag2 = tvOn == TVSyncState.IsTvOn;
			bool flag3 = Math.Abs(seekTime - TVSyncState.SeekTime) < 0.01;
			bool flag4 = string.Equals(mediaId, TVSyncState.CurrentMediaId, StringComparison.OrdinalIgnoreCase);
			if (flag && flag2 && flag3 && flag4)
			{
				TVSyncPlugin.Log.LogDebug((object)"[CymruTVSync] Ignoring duplicate TV state request.");
				return;
			}
			if (TVSyncState.LockFeatureEnabled && TVSyncState.IsLocked && senderClientId != NetworkManager.Singleton.LocalClientId)
			{
				TVSyncPlugin.Log.LogDebug((object)$"[CymruTVSync] Client {senderClientId} tried to change TV but it is locked. Ignoring.");
				return;
			}
			TVSyncState.CurrentClip = clipIndex;
			TVSyncState.SeekTime = seekTime;
			TVSyncState.IsTvOn = tvOn;
			TVSyncState.CurrentMediaId = mediaId;
			BroadcastStateToClients(clipIndex, seekTime, tvOn, mediaId);
		}

		private void OnRequestSyncMessage(ulong senderClientId, FastBufferReader reader)
		{
			SendCurrentStateToClient(senderClientId);
		}

		private void SendCurrentStateToClient(ulong targetClientId)
		{
			SendStateMessage(targetClientId, TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId);
			SendLockStateMessage(targetClientId, TVSyncState.IsLocked);
			SendLockFeatureStateMessage(targetClientId, TVSyncState.LockFeatureEnabled);
		}

		private void OnToggleLockMessage(ulong senderClientId, FastBufferReader reader)
		{
			HandleToggleLock(senderClientId);
		}

		private void HandleToggleLock(ulong senderClientId)
		{
			if (senderClientId != NetworkManager.Singleton.LocalClientId)
			{
				TVSyncPlugin.Log.LogWarning((object)$"[CymruTVSync][NGO-RX S<-C] Non-host client {senderClientId} tried to toggle lock. Ignoring.");
				return;
			}
			if (!TVSyncState.LockFeatureEnabled)
			{
				TVSyncPlugin.Log.LogDebug((object)"[CymruTVSync] Host tried to toggle TV lock while lock feature is disabled.");
				return;
			}
			TVSyncState.IsLocked = !TVSyncState.IsLocked;
			TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO-RX S<-C] Host toggled TV lock: {TVSyncState.IsLocked}. Broadcasting.");
			BroadcastLockStateToClients(TVSyncState.IsLocked);
			BroadcastStateToClients(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId);
		}

		private void OnToggleLockFeatureMessage(ulong senderClientId, FastBufferReader reader)
		{
			HandleToggleLockFeature(senderClientId);
		}

		private void HandleToggleLockFeature(ulong senderClientId)
		{
			if (senderClientId != NetworkManager.Singleton.LocalClientId)
			{
				TVSyncPlugin.Log.LogWarning((object)$"[CymruTVSync][NGO-RX S<-C] Non-host client {senderClientId} tried to toggle lock feature. Ignoring.");
				return;
			}
			TVSyncState.LockFeatureEnabled = !TVSyncState.LockFeatureEnabled;
			if (!TVSyncState.LockFeatureEnabled)
			{
				TVSyncState.IsLocked = false;
			}
			TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO-RX S<-C] Host toggled lock feature: {TVSyncState.LockFeatureEnabled}. Current lock={TVSyncState.IsLocked}. Broadcasting.");
			BroadcastLockFeatureStateToClients(TVSyncState.LockFeatureEnabled);
			BroadcastStateToClients(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId);
			if (!TVSyncState.LockFeatureEnabled)
			{
				BroadcastLockStateToClients(isLocked: false);
			}
		}

		private void BroadcastStateToClients(int clipIndex, double seekTime, bool tvOn, string mediaId)
		{
			if (mediaId == null)
			{
				mediaId = string.Empty;
			}
			ReceiveBroadcastState(clipIndex, seekTime, tvOn, mediaId, "LOCAL-HOST");
			foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds)
			{
				if (connectedClientsId != NetworkManager.Singleton.LocalClientId)
				{
					SendStateMessage(connectedClientsId, clipIndex, seekTime, tvOn, mediaId);
				}
			}
		}

		private void SendStateMessage(ulong targetClientId, int clipIndex, double seekTime, bool tvOn, string mediaId)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(4096, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref clipIndex, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<double>(ref seekTime, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref tvOn, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(mediaId ?? string.Empty, false);
				NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/BroadcastState", targetClientId, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		private void OnBroadcastStateMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			int clipIndex = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref clipIndex, default(ForPrimitives));
			double seekTime = default(double);
			((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref seekTime, default(ForPrimitives));
			bool tvOn = default(bool);
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref tvOn, default(ForPrimitives));
			string mediaId = default(string);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref mediaId, false);
			ReceiveBroadcastState(clipIndex, seekTime, tvOn, mediaId, senderClientId.ToString());
		}

		private void ReceiveBroadcastState(int clipIndex, double seekTime, bool tvOn, string mediaId, string target)
		{
			if (mediaId == null)
			{
				mediaId = string.Empty;
			}
			TVSyncState.CurrentClip = clipIndex;
			TVSyncState.SeekTime = seekTime;
			TVSyncState.IsTvOn = tvOn;
			TVSyncState.CurrentMediaId = mediaId;
			TVSyncNetworkHandler.OnTVStateReceived?.Invoke(clipIndex, seekTime, tvOn, mediaId);
		}

		private void BroadcastLockStateToClients(bool isLocked)
		{
			ReceiveBroadcastLockState(isLocked, "LOCAL-HOST");
			foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds)
			{
				if (connectedClientsId != NetworkManager.Singleton.LocalClientId)
				{
					SendLockStateMessage(connectedClientsId, isLocked);
				}
			}
		}

		private void SendLockStateMessage(ulong targetClientId, bool isLocked)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref isLocked, default(ForPrimitives));
				NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/BroadcastLockState", targetClientId, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		private void OnBroadcastLockStateMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			bool isLocked = default(bool);
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref isLocked, default(ForPrimitives));
			ReceiveBroadcastLockState(isLocked, senderClientId.ToString());
		}

		private void ReceiveBroadcastLockState(bool isLocked, string target)
		{
			TVSyncState.IsLocked = isLocked;
			TVSyncNetworkHandler.OnLockStateReceived?.Invoke(isLocked);
			if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost && (Object)(object)HUDManager.Instance != (Object)null)
			{
				string text = (isLocked ? "Host has locked the TV." : "Host has unlocked the TV.");
				HUDManager.Instance.DisplayTip("CymruTV Sync", text, false, false, "TVLockTip");
			}
		}

		private void BroadcastLockFeatureStateToClients(bool enabled)
		{
			ReceiveBroadcastLockFeatureState(enabled, "LOCAL-HOST");
			foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds)
			{
				if (connectedClientsId != NetworkManager.Singleton.LocalClientId)
				{
					SendLockFeatureStateMessage(connectedClientsId, enabled);
				}
			}
		}

		private void SendLockFeatureStateMessage(ulong targetClientId, bool enabled)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref enabled, default(ForPrimitives));
				NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/BroadcastLockFeature", targetClientId, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		private void OnBroadcastLockFeatureMessage(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			bool enabled = default(bool);
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref enabled, default(ForPrimitives));
			ReceiveBroadcastLockFeatureState(enabled, senderClientId.ToString());
		}

		private void ReceiveBroadcastLockFeatureState(bool enabled, string target)
		{
			TVSyncState.LockFeatureEnabled = enabled;
			TVSyncNetworkHandler.OnLockFeatureStateReceived?.Invoke(enabled);
			if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost && (Object)(object)HUDManager.Instance != (Object)null)
			{
				string text = (enabled ? "Host enabled TV lock controls (L toggles lock)." : "Host disabled TV lock controls.");
				HUDManager.Instance.DisplayTip("CymruTV Sync", text, false, false, "TVLockFeatureTip");
			}
		}
	}
	public static class TVSyncState
	{
		public static int CurrentClip = 0;

		public static double SeekTime = 0.0;

		public static bool IsTvOn = false;

		public static string CurrentMediaId = string.Empty;

		public static bool LockFeatureEnabled = true;

		public static bool IsLocked = false;
	}
	[HarmonyPatch]
	public class TVSyncNetworkObjectManager
	{
		private static bool _ngoCallbacksRegistered;

		private static string LocalRole()
		{
			if ((Object)(object)NetworkManager.Singleton == (Object)null)
			{
				return "NO-NET";
			}
			if (NetworkManager.Singleton.IsHost)
			{
				return "HOST";
			}
			if (NetworkManager.Singleton.IsServer)
			{
				return "SERVER";
			}
			if (NetworkManager.Singleton.IsClient)
			{
				return "CLIENT";
			}
			return "OFFLINE";
		}

		private static void EnsureNgoCallbacks()
		{
			if (!_ngoCallbacksRegistered && !((Object)(object)NetworkManager.Singleton == (Object)null))
			{
				NetworkManager.Singleton.OnClientConnectedCallback += delegate(ulong clientId)
				{
					TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO] OnClientConnected callback: client={clientId}, local={NetworkManager.Singleton.LocalClientId}, role={LocalRole()}.");
				};
				NetworkManager.Singleton.OnClientDisconnectCallback += delegate(ulong clientId)
				{
					TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO] OnClientDisconnected callback: client={clientId}, local={NetworkManager.Singleton.LocalClientId}, role={LocalRole()}.");
				};
				_ngoCallbacksRegistered = true;
				TVSyncPlugin.Log.LogInfo((object)"[CymruTVSync][NGO] Registered connection callbacks.");
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(GameNetworkManager), "Start")]
		public static void RegisterNetworkPrefab()
		{
			TVSyncNetworkHandler.EnsureInstance();
			if ((Object)(object)NetworkManager.Singleton == (Object)null)
			{
				TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Could not initialize NGO messaging because NetworkManager.Singleton is null.");
				return;
			}
			EnsureNgoCallbacks();
			TVSyncNetworkHandler.EnsureMessagingReady("GameNetworkManager.Start");
			TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync] NGO message transport ready (role={LocalRole()}, local={NetworkManager.Singleton.LocalClientId}).");
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		public static void SpawnNetworkHandler()
		{
			TVSyncNetworkHandler.EnsureInstance();
			if ((Object)(object)NetworkManager.Singleton == (Object)null)
			{
				TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Cannot refresh NGO messaging because NetworkManager.Singleton is null.");
				return;
			}
			TVSyncNetworkHandler.EnsureMessagingReady("StartOfRound.Awake");
			TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync] NGO message transport refreshed for round start (role={LocalRole()}, local={NetworkManager.Singleton.LocalClientId}).");
		}
	}
	internal static class TVLockGuard
	{
		private static InteractTrigger _cachedTvTrigger;

		private static string _originalHoverTip;

		private static string _originalDisabledHoverTip;

		private static bool _warnedMissingTrigger;

		public static void ApplyAuthoritativeState()
		{
			TVSyncInputPatch.ApplyState(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId);
		}

		public static bool ShouldBlockLocalInput()
		{
			if (!TVSyncState.LockFeatureEnabled)
			{
				return false;
			}
			if (!TVSyncState.IsLocked)
			{
				return false;
			}
			if ((Object)(object)NetworkManager.Singleton == (Object)null)
			{
				return false;
			}
			return !NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer;
		}

		public static void NotifyBlocked(string reason)
		{
		}

		private static string ScenePath(Transform t)
		{
			if ((Object)(object)t == (Object)null)
			{
				return "(null)";
			}
			StringBuilder stringBuilder = new StringBuilder();
			Transform val = t;
			while ((Object)(object)val != (Object)null)
			{
				if (stringBuilder.Length > 0)
				{
					stringBuilder.Insert(0, "/");
				}
				stringBuilder.Insert(0, ((Object)val).name);
				val = val.parent;
			}
			return stringBuilder.ToString();
		}

		private static InteractTrigger FindTVInteractTrigger()
		{
			if ((Object)(object)_cachedTvTrigger != (Object)null)
			{
				return _cachedTvTrigger;
			}
			InteractTrigger[] array = Object.FindObjectsOfType<InteractTrigger>(true);
			string[] array2 = new string[5] { "Switch TV", "Turn on TV", "Turn off TV", "Toggle TV", "TV" };
			string[] array3 = array2;
			foreach (string value in array3)
			{
				InteractTrigger[] array4 = array;
				foreach (InteractTrigger val in array4)
				{
					if (val.hoverTip != null && val.hoverTip.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						_cachedTvTrigger = val;
						return _cachedTvTrigger;
					}
				}
			}
			TVScript[] array5 = Object.FindObjectsOfType<TVScript>(true);
			TVScript[] array6 = array5;
			foreach (TVScript val2 in array6)
			{
				InteractTrigger componentInChildren = ((Component)val2).GetComponentInChildren<InteractTrigger>(true);
				if ((Object)(object)componentInChildren != (Object)null)
				{
					_cachedTvTrigger = componentInChildren;
					return _cachedTvTrigger;
				}
			}
			InteractTrigger[] array7 = array;
			foreach (InteractTrigger val3 in array7)
			{
				Transform val4 = ((Component)val3).transform;
				while ((Object)(object)val4 != (Object)null)
				{
					if (((Object)val4).name.IndexOf("Television", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)val4).name.IndexOf("TVSet", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						_cachedTvTrigger = val3;
						return _cachedTvTrigger;
					}
					val4 = val4.parent;
				}
			}
			return null;
		}

		public static void ApplyTriggerLock()
		{
			if ((Object)(object)NetworkManager.Singleton == (Object)null)
			{
				return;
			}
			bool flag = TVSyncState.LockFeatureEnabled && TVSyncState.IsLocked;
			bool flag2 = NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer;
			InteractTrigger val = FindTVInteractTrigger();
			if ((Object)(object)val == (Object)null)
			{
				if (!_warnedMissingTrigger)
				{
					_warnedMissingTrigger = true;
					TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Could not find TV InteractTrigger to apply lock state.");
				}
				return;
			}
			_warnedMissingTrigger = false;
			if (string.IsNullOrEmpty(_originalHoverTip))
			{
				_originalHoverTip = val.hoverTip;
			}
			if (string.IsNullOrEmpty(_originalDisabledHoverTip))
			{
				_originalDisabledHoverTip = val.disabledHoverTip;
			}
			string text = (string.IsNullOrWhiteSpace(_originalHoverTip) ? "Switch TV" : _originalHoverTip);
			if (flag2)
			{
				val.interactable = true;
				val.hoverTip = (flag ? (text + "\nTV is locked for clients") : (text + "\nTV is unlocked"));
				val.disabledHoverTip = _originalDisabledHoverTip;
			}
			else if (flag)
			{
				val.interactable = false;
				val.disabledHoverTip = "TV locked";
				val.hoverTip = text;
			}
			else
			{
				val.interactable = true;
				val.hoverTip = text;
				val.disabledHoverTip = _originalDisabledHoverTip;
			}
		}
	}
	internal static class BestestTV
	{
		private static Type _patchType;

		private static Type _videoManagerType;

		private static Type _configType;

		public static FieldInfo TVIndexField;

		public static FieldInfo VideoSourceField;

		public static FieldInfo AudioSourceField;

		public static FieldInfo TvIsOnField;

		public static FieldInfo VideosField;

		public static FieldInfo EnableChannelsField;

		public static FieldInfo EnableSeekingField;

		public static FieldInfo SkipForwardKeyField;

		public static FieldInfo SkipReverseKeyField;

		public static FieldInfo SeekForwardKeyField;

		public static FieldInfo SeekReverseKeyField;

		public static bool Ready { get; private set; }

		public static int TVIndex
		{
			get
			{
				return (TVIndexField != null) ? ((int)TVIndexField.GetValue(null)) : 0;
			}
			set
			{
				TVIndexField?.SetValue(null, value);
			}
		}

		public static VideoPlayer VideoSource
		{
			get
			{
				object? obj = VideoSourceField?.GetValue(null);
				return (VideoPlayer)((obj is VideoPlayer) ? obj : null);
			}
		}

		public static AudioSource AudioSource
		{
			get
			{
				object? obj = AudioSourceField?.GetValue(null);
				return (AudioSource)((obj is AudioSource) ? obj : null);
			}
		}

		public static bool TvIsOn
		{
			get
			{
				return TvIsOnField != null && (bool)TvIsOnField.GetValue(null);
			}
			set
			{
				TvIsOnField?.SetValue(null, value);
			}
		}

		public static List<string> Videos => VideosField?.GetValue(null) as List<string>;

		public static bool EnableChannels => EnableChannelsField == null || GetConfigValue<bool>(EnableChannelsField);

		public static bool EnableSeeking => EnableSeekingField == null || GetConfigValue<bool>(EnableSeekingField);

		public static void Init()
		{
			try
			{
				Assembly assembly = FindBestestTVAssembly();
				if (assembly == null)
				{
					TVSyncPlugin.Log.LogError((object)("[CymruTVSync] Could not find BestestTVMod assembly. Loaded assemblies: " + string.Join(", ", GetLoadedAssemblyNames())));
					return;
				}
				TVSyncPlugin.Log.LogInfo((object)("[CymruTVSync] Found BestestTVMod assembly: " + assembly.GetName().Name));
				Type[] types = assembly.GetTypes();
				foreach (Type type in types)
				{
					if (_patchType == null && type.GetField("TVIndex", BindingFlags.Static | BindingFlags.Public) != null)
					{
						_patchType = type;
					}
					if (_videoManagerType == null && type.GetField("Videos", BindingFlags.Static | BindingFlags.Public) != null)
					{
						_videoManagerType = type;
					}
					if (_configType == null && type.GetField("enableChannels", BindingFlags.Static | BindingFlags.Public) != null)
					{
						_configType = type;
					}
				}
				if (_patchType == null)
				{
					TVSyncPlugin.Log.LogError((object)"[CymruTVSync] Could not find TVScriptPatches type in BestestTVMod!");
					return;
				}
				TVSyncPlugin.Log.LogInfo((object)("[CymruTVSync] Found patch type: " + _patchType.FullName));
				BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public;
				TVIndexField = _patchType.GetField("TVIndex", bindingAttr);
				VideoSourceField = _patchType.GetField("videoSource", bindingAttr);
				AudioSourceField = _patchType.GetField("audioSource", bindingAttr);
				TvIsOnField = _patchType.GetField("tvIsCurrentlyOn", bindingAttr);
				if (_videoManagerType != null)
				{
					VideosField = _videoManagerType.GetField("Videos", bindingAttr);
				}
				if (_configType != null)
				{
					EnableChannelsField = _configType.GetField("enableChannels", bindingAttr);
					EnableSeekingField = _configType.GetField("enableSeeking", bindingAttr);
					SkipForwardKeyField = _configType.GetField("skipForwardKeyBind", bindingAttr);
					SkipReverseKeyField = _configType.GetField("skipReverseKeyBind", bindingAttr);
					SeekForwardKeyField = _configType.GetField("seekForwardKeyBind", bindingAttr);
					SeekReverseKeyField = _configType.GetField("seekReverseKeyBind", bindingAttr);
				}
				Ready = TVIndexField != null && VideoSourceField != null;
				TVSyncPlugin.Log.LogInfo((object)($"[CymruTVSync] BestestTV reflection ready={Ready}. " + $"TVIndex={TVIndexField != null}, videoSource={VideoSourceField != null}, " + $"Videos={VideosField != null}, Config={_configType != null}"));
			}
			catch (Exception arg)
			{
				TVSyncPlugin.Log.LogError((object)$"[CymruTVSync] Reflection init failed: {arg}");
			}
		}

		private static Assembly FindBestestTVAssembly()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (IsBestestTVAssemblyName(assembly.GetName().Name))
				{
					return assembly;
				}
			}
			try
			{
				string pluginPath = Paths.PluginPath;
				if (!Directory.Exists(pluginPath))
				{
					return null;
				}
				string[] files = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories);
				foreach (string text in files)
				{
					string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text);
					if (IsBestestTVAssemblyName(fileNameWithoutExtension))
					{
						TVSyncPlugin.Log.LogInfo((object)("[CymruTVSync] Loading BestestTV assembly from disk: " + text));
						return Assembly.LoadFrom(text);
					}
				}
			}
			catch (Exception ex)
			{
				TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync] Failed to load BestestTV assembly from disk: " + ex.Message));
			}
			return null;
		}

		private static bool IsBestestTVAssemblyName(string assemblyName)
		{
			if (string.IsNullOrWhiteSpace(assemblyName))
			{
				return false;
			}
			return assemblyName.IndexOf("BestestTelevisionMod", StringComparison.OrdinalIgnoreCase) >= 0 || assemblyName.IndexOf("BestestTV", StringComparison.OrdinalIgnoreCase) >= 0 || assemblyName.IndexOf("DeathWrench", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		private static string[] GetLoadedAssemblyNames()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			string[] array = new string[assemblies.Length];
			for (int i = 0; i < assemblies.Length; i++)
			{
				array[i] = assemblies[i].GetName().Name;
			}
			return array;
		}

		private static T GetConfigValue<T>(FieldInfo fi)
		{
			try
			{
				object value = fi.GetValue(null);
				if (value == null)
				{
					return default(T);
				}
				PropertyInfo property = value.GetType().GetProperty("Value");
				if (property == null)
				{
					return default(T);
				}
				return (T)property.GetValue(value);
			}
			catch
			{
				return default(T);
			}
		}

		public static Key GetKeyBind(FieldInfo fi, Key fallback)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				object obj = fi?.GetValue(null);
				if (obj == null)
				{
					return fallback;
				}
				PropertyInfo property = obj.GetType().GetProperty("Value");
				return (property == null) ? fallback : ConvertToInputSystemKey(property.GetValue(obj), fallback);
			}
			catch
			{
				return fallback;
			}
		}

		private static Key ConvertToInputSystemKey(object value, Key fallback)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			if (value == null)
			{
				return fallback;
			}
			if (value is Key result)
			{
				return result;
			}
			string value2 = value.ToString();
			if (string.IsNullOrWhiteSpace(value2))
			{
				return fallback;
			}
			Key result2;
			return Enum.TryParse<Key>(value2, ignoreCase: true, out result2) ? result2 : fallback;
		}

		public static bool WasPressedThisFrame(Key key)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Keyboard current = Keyboard.current;
			if (current == null)
			{
				return false;
			}
			try
			{
				return ((ButtonControl)current[key]).wasPressedThisFrame;
			}
			catch
			{
				return false;
			}
		}

		public static string NormalizeMediaId(string media)
		{
			if (string.IsNullOrWhiteSpace(media))
			{
				return string.Empty;
			}
			string text = media.Trim();
			if (Uri.TryCreate(text, UriKind.Absolute, out Uri result))
			{
				text = ((!result.IsFile) ? result.AbsoluteUri : result.LocalPath);
			}
			else if (text.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
			{
				text = text.Substring(7);
			}
			string fileName = Path.GetFileName(text);
			if (!string.IsNullOrWhiteSpace(fileName))
			{
				return fileName.Trim().ToLowerInvariant();
			}
			text = text.Replace('\\', '/').TrimStart('/');
			return text.ToLowerInvariant();
		}

		public static string GetCurrentMediaId()
		{
			return ((Object)(object)VideoSource == (Object)null) ? string.Empty : NormalizeMediaId(VideoSource.url);
		}

		public static int FindVideoIndexByMediaId(string mediaId)
		{
			string text = NormalizeMediaId(mediaId);
			if (string.IsNullOrEmpty(text))
			{
				return -1;
			}
			List<string> videos = Videos;
			if (videos == null)
			{
				return -1;
			}
			for (int i = 0; i < videos.Count; i++)
			{
				if (NormalizeMediaId(videos[i]) == text)
				{
					return i;
				}
			}
			return -1;
		}
	}
	[HarmonyPatch]
	public class TVSyncBestestHooks
	{
		[CompilerGenerated]
		private sealed class <TargetMethods>d__9 : IEnumerable<MethodBase>, IEnumerable, IEnumerator<MethodBase>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private MethodBase <>2__current;

			private int <>l__initialThreadId;

			private Type <targetType>5__1;

			private string[] <methodNames>5__2;

			private string[] <>s__3;

			private int <>s__4;

			private string <methodName>5__5;

			private MethodInfo <method>5__6;

			MethodBase IEnumerator<MethodBase>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <TargetMethods>d__9(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<targetType>5__1 = null;
				<methodNames>5__2 = null;
				<>s__3 = null;
				<methodName>5__5 = null;
				<method>5__6 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					<method>5__6 = null;
					<methodName>5__5 = null;
					goto IL_012e;
				}
				<>1__state = -1;
				<targetType>5__1 = AccessTools.TypeByName("BestestTVModPlugin.TVScriptPatches");
				if (<targetType>5__1 == null)
				{
					TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Could not find BestestTVModPlugin.TVScriptPatches for direct input hooks.");
					return false;
				}
				<methodNames>5__2 = new string[5] { "TVIndexUp", "TVIndexDown", "GetTVInput", "Update", "TurnTVOnOff" };
				<>s__3 = <methodNames>5__2;
				<>s__4 = 0;
				goto IL_013c;
				IL_012e:
				<>s__4++;
				goto IL_013c;
				IL_013c:
				if (<>s__4 < <>s__3.Length)
				{
					<methodName>5__5 = <>s__3[<>s__4];
					<method>5__6 = AccessTools.DeclaredMethod(<targetType>5__1, <methodName>5__5, (Type[])null, (Type[])null);
					if (<method>5__6 == null)
					{
						TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync] Could not hook BestestTV method " + <methodName>5__5 + "."));
						goto IL_012e;
					}
					<>2__current = <method>5__6;
					<>1__state = 1;
					return true;
				}
				<>s__3 = null;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<MethodBase> IEnumerable<MethodBase>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <TargetMethods>d__9(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<MethodBase>)this).GetEnumerator();
			}
		}

		private static int _preInputClip;

		private static double _preInputTime;

		private static bool _preInputOn;

		private static bool _updateSnapshotReady;

		private static int _lastUpdateClip;

		private static double _lastUpdateTime;

		private static bool _lastUpdateOn;

		private static bool _warnedBestestNotReady;

		private static bool _warnedHandlerMissing;

		[IteratorStateMachine(typeof(<TargetMethods>d__9))]
		public static IEnumerable<MethodBase> TargetMethods()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <TargetMethods>d__9(-2);
		}

		[HarmonyPrefix]
		[HarmonyPriority(200)]
		public static bool Prefix(MethodBase __originalMethod)
		{
			if (__originalMethod == null)
			{
				return true;
			}
			string name = __originalMethod.Name;
			if (name == "GetTVInput")
			{
				_preInputClip = BestestTV.TVIndex;
				_preInputTime = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0);
				_preInputOn = BestestTV.TvIsOn;
			}
			switch (name)
			{
			default:
				if (!(name == "TurnTVOnOff"))
				{
					break;
				}
				goto case "TVIndexUp";
			case "TVIndexUp":
			case "TVIndexDown":
			case "GetTVInput":
				if (TVLockGuard.ShouldBlockLocalInput())
				{
					if (name == "TurnTVOnOff" && TVSyncPatches.SuppressOutboundSync)
					{
						return true;
					}
					if (name != "GetTVInput")
					{
						TVLockGuard.NotifyBlocked(name);
					}
					return false;
				}
				break;
			}
			return true;
		}

		[HarmonyPostfix]
		[HarmonyPriority(200)]
		public static void Postfix(MethodBase __originalMethod)
		{
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0213: Unknown result type (might be due to invalid IL or missing references)
			//IL_0215: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			if (__originalMethod == null || TVSyncPatches.SuppressOutboundSync)
			{
				return;
			}
			if (!BestestTV.Ready)
			{
				BestestTV.Init();
				if (!BestestTV.Ready)
				{
					if (!_warnedBestestNotReady)
					{
						TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Bestest hooks skipped because BestestTV reflection is not ready on this client.");
						_warnedBestestNotReady = true;
					}
					return;
				}
			}
			_warnedBestestNotReady = false;
			if ((Object)(object)TVSyncNetworkHandler.Instance == (Object)null)
			{
				if (!_warnedHandlerMissing)
				{
					TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Bestest hooks skipped because TVSyncNetworkHandler.Instance is null on this client.");
					_warnedHandlerMissing = true;
				}
				return;
			}
			_warnedHandlerMissing = false;
			int tVIndex = BestestTV.TVIndex;
			double num = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0);
			bool tvIsOn = BestestTV.TvIsOn;
			if (__originalMethod.Name == "TVIndexUp" || __originalMethod.Name == "TVIndexDown")
			{
				TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:" + __originalMethod.Name, tVIndex, 0.0, tvIsOn);
			}
			else if (__originalMethod.Name == "Update")
			{
				if (_updateSnapshotReady)
				{
					bool flag = tVIndex == _lastUpdateClip;
					bool flag2 = tvIsOn == _lastUpdateOn;
					double num2 = Math.Abs(num - _lastUpdateTime);
					if (flag && flag2 && tvIsOn && num2 >= 1.0)
					{
						TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:UpdateSeekJump", tVIndex, num, tvIsOn);
					}
				}
				_lastUpdateClip = tVIndex;
				_lastUpdateTime = num;
				_lastUpdateOn = tvIsOn;
				_updateSnapshotReady = true;
			}
			else
			{
				if (__originalMethod.Name != "GetTVInput")
				{
					return;
				}
				Key keyBind = BestestTV.GetKeyBind(BestestTV.SeekForwardKeyField, (Key)63);
				Key keyBind2 = BestestTV.GetKeyBind(BestestTV.SeekReverseKeyField, (Key)64);
				if ((BestestTV.WasPressedThisFrame(keyBind) || BestestTV.WasPressedThisFrame(keyBind2)) && BestestTV.EnableSeeking)
				{
					TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:GetTVInputSeekKey", tVIndex, num, tvIsOn);
					return;
				}
				bool flag3 = tVIndex != _preInputClip;
				bool flag4 = tvIsOn != _preInputOn;
				bool flag5 = Math.Abs(num - _preInputTime) >= 0.75;
				if (flag3 || flag4 || flag5)
				{
					TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:GetTVInputDelta", tVIndex, num, tvIsOn);
				}
			}
		}
	}
	[HarmonyPatch]
	public class TVSyncPatches
	{
		private static float _ignoreOutboundUntilTime;

		private static bool _skipNextTurnTvOnOffPostfix;

		private static float _hostControlIntentUntilTime;

		private static int _lastObservedClip = -1;

		private static double _lastObservedSeek = 0.0;

		private static bool _lastObservedOn = false;

		private static bool _hasObservedState;

		private static string _lastObservedMediaId = string.Empty;

		private static float _nextWatchSendTime;

		private static bool _watcherWarnedBestestNotReady;

		private static bool _watcherWarnedHandlerMissing;

		internal static bool SuppressOutboundSync => Time.unscaledTime < _ignoreOutboundUntilTime;

		internal static void MarkHostControlIntent(float durationSeconds = 0.5f)
		{
			if (!((Object)(object)NetworkManager.Singleton == (Object)null) && (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer))
			{
				_hostControlIntentUntilTime = Mathf.Max(_hostControlIntentUntilTime, Time.unscaledTime + durationSeconds);
			}
		}

		internal static bool ConsumeHostControlIntent()
		{
			bool flag = Time.unscaledTime <= _hostControlIntentUntilTime;
			if (flag)
			{
				_hostControlIntentUntilTime = 0f;
			}
			return flag;
		}

		internal static void BeginSuppressedRemoteApply()
		{
			_ignoreOutboundUntilTime = Mathf.Max(_ignoreOutboundUntilTime, Time.unscaledTime + 1f);
		}

		internal static void EndSuppressedRemoteApply()
		{
			_ignoreOutboundUntilTime = Mathf.Max(_ignoreOutboundUntilTime, Time.unscaledTime + 0.5f);
		}

		internal static void UpdateObservedStateSnapshot(int clipIndex, double seekTime, bool tvOn, string mediaId)
		{
			_lastObservedClip = clipIndex;
			_lastObservedSeek = seekTime;
			_lastObservedOn = tvOn;
			_lastObservedMediaId = mediaId ?? string.Empty;
			_hasObservedState = true;
			_nextWatchSendTime = Mathf.Max(_nextWatchSendTime, Time.unscaledTime + 0.75f);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(TVScript), "Update")]
		[HarmonyPriority(200)]
		public static void TVUpdate_Postfix()
		{
			if (SuppressOutboundSync)
			{
				return;
			}
			if (!BestestTV.Ready)
			{
				BestestTV.Init();
				if (!BestestTV.Ready)
				{
					if (!_watcherWarnedBestestNotReady)
					{
						TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] TV watcher skipped because BestestTV reflection is not ready on this client.");
						_watcherWarnedBestestNotReady = true;
					}
					return;
				}
			}
			_watcherWarnedBestestNotReady = false;
			if ((Object)(object)TVSyncNetworkHandler.Instance == (Object)null)
			{
				if (!_watcherWarnedHandlerMissing)
				{
					TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] TV watcher skipped because TVSyncNetworkHandler.Instance is null on this client.");
					_watcherWarnedHandlerMissing = true;
				}
				return;
			}
			_watcherWarnedHandlerMissing = false;
			if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsClient)
			{
				return;
			}
			int tVIndex = BestestTV.TVIndex;
			double num = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0);
			bool tvIsOn = BestestTV.TvIsOn;
			string currentMediaId = BestestTV.GetCurrentMediaId();
			if (!_hasObservedState)
			{
				_lastObservedClip = tVIndex;
				_lastObservedSeek = num;
				_lastObservedOn = tvIsOn;
				_lastObservedMediaId = currentMediaId;
				_hasObservedState = true;
				return;
			}
			bool flag = tVIndex != _lastObservedClip;
			bool flag2 = tvIsOn != _lastObservedOn;
			bool flag3 = Math.Abs(num - _lastObservedSeek) >= 1.0;
			bool flag4 = !string.Equals(currentMediaId, _lastObservedMediaId, StringComparison.OrdinalIgnoreCase);
			if (flag || flag2 || flag3 || flag4)
			{
				if (TVLockGuard.ShouldBlockLocalInput())
				{
					_lastObservedClip = TVSyncState.CurrentClip;
					_lastObservedSeek = TVSyncState.SeekTime;
					_lastObservedOn = TVSyncState.IsTvOn;
					_lastObservedMediaId = TVSyncState.CurrentMediaId;
					return;
				}
				if (Time.unscaledTime >= _nextWatchSendTime)
				{
					TVSyncNetworkHandler.Instance.TrySendChangeToServer("TVScript.UpdateWatcher", tVIndex, num, tvIsOn);
					_nextWatchSendTime = Time.unscaledTime + 0.12f;
				}
			}
			_lastObservedClip = tVIndex;
			_lastObservedSeek = num;
			_lastObservedOn = tvIsOn;
			_lastObservedMediaId = currentMediaId;
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(TVScript), "TurnTVOnOff")]
		[HarmonyPriority(600)]
		public static bool TurnTVOnOff_Prefix(bool on)
		{
			MarkHostControlIntent(0.6f);
			if (!TVLockGuard.ShouldBlockLocalInput())
			{
				return true;
			}
			_skipNextTurnTvOnOffPostfix = true;
			return true;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(TVScript), "TurnTVOnOff")]
		[HarmonyPriority(200)]
		public static void TurnTVOnOff_Postfix(bool on)
		{
			if (_skipNextTurnTvOnOffPostfix)
			{
				_skipNextTurnTvOnOffPostfix = false;
			}
			else if (!SuppressOutboundSync && BestestTV.Ready && !((Object)(object)TVSyncNetworkHandler.Instance == (Object)null))
			{
				TVSyncNetworkHandler.Instance.TrySendChangeToServer("TVScript.TurnTVOnOff", BestestTV.TVIndex, ((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0, on);
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(TVScript), "TVFinishedClip")]
		[HarmonyPriority(200)]
		public static void TVFinishedClip_Postfix()
		{
			if (!SuppressOutboundSync && BestestTV.Ready && !((Object)(object)TVSyncNetworkHandler.Instance == (Object)null) && BestestTV.TvIsOn)
			{
				TVSyncNetworkHandler.Instance.TrySendChangeToServer("TVScript.TVFinishedClip", BestestTV.TVIndex, 0.0, tvOn: true);
			}
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "Update")]
	public class TVSyncInputPatch
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static EventHandler <0>__OnVideoPrepared;
		}

		private static VideoPlayer _preparedVideoSource;

		private static AudioSource _preparedAudioSource;

		private static double _preparedSeekTime;

		private static bool _preparedTvOn;

		private static int _preparedClipIndex;

		private static string _preparedMediaId;

		[HarmonyPostfix]
		[HarmonyPriority(200)]
		public static void Update_Postfix(PlayerControllerB __instance)
		{
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: 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_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: 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_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if (TVSyncPatches.SuppressOutboundSync || !BestestTV.Ready || !((NetworkBehaviour)__instance).IsOwner || !__instance.isPlayerControlled || __instance.inTerminalMenu || __instance.isTypingChat || __instance.isPlayerDead || (Object)(object)TVSyncNetworkHandler.Instance == (Object)null)
			{
				return;
			}
			InteractTrigger hoveringOverTrigger = __instance.hoveringOverTrigger;
			if ((Object)(object)hoveringOverTrigger == (Object)null)
			{
				return;
			}
			Transform parent = ((Component)hoveringOverTrigger).transform.parent;
			if ((Object)(object)parent == (Object)null || !((Object)((Component)parent).gameObject).name.Contains("Television"))
			{
				return;
			}
			Key keyBind = BestestTV.GetKeyBind(BestestTV.SkipForwardKeyField, (Key)62);
			Key keyBind2 = BestestTV.GetKeyBind(BestestTV.SkipReverseKeyField, (Key)61);
			Key keyBind3 = BestestTV.GetKeyBind(BestestTV.SeekForwardKeyField, (Key)63);
			Key keyBind4 = BestestTV.GetKeyBind(BestestTV.SeekReverseKeyField, (Key)64);
			bool flag = BestestTV.WasPressedThisFrame(keyBind);
			bool flag2 = BestestTV.WasPressedThisFrame(keyBind2);
			bool flag3 = BestestTV.WasPressedThisFrame(keyBind3);
			bool flag4 = BestestTV.WasPressedThisFrame(keyBind4);
			bool flag5 = (flag || flag2) && BestestTV.EnableChannels && BestestTV.TvIsOn;
			bool flag6 = (flag3 || flag4) && BestestTV.EnableSeeking;
			if (!flag5 && !flag6)
			{
				return;
			}
			if (TVLockGuard.ShouldBlockLocalInput())
			{
				TVLockGuard.NotifyBlocked("PlayerControllerB.Update input");
				return;
			}
			double seekTime = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0);
			if (flag5)
			{
				TVSyncNetworkHandler.Instance.TrySendChangeToServer("PlayerController.Update:ChannelSkip", BestestTV.TVIndex, 0.0, BestestTV.TvIsOn);
			}
			else
			{
				TVSyncNetworkHandler.Instance.TrySendChangeToServer("PlayerController.Update:Seek", BestestTV.TVIndex, seekTime, BestestTV.TvIsOn);
			}
		}

		private static void OnVideoPrepared(VideoPlayer source)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			try
			{
				if ((Object)(object)_preparedVideoSource != (Object)null)
				{
					VideoPlayer preparedVideoSource = _preparedVideoSource;
					object obj = <>O.<0>__OnVideoPrepared;
					if (obj == null)
					{
						EventHandler val = OnVideoPrepared;
						<>O.<0>__OnVideoPrepared = val;
						obj = (object)val;
					}
					preparedVideoSource.prepareCompleted -= (EventHandler)obj;
				}
				source.time = _preparedSeekTime;
				if (_preparedTvOn)
				{
					if (!source.isPlaying)
					{
						source.Play();
					}
					if ((Object)(object)_preparedAudioSource != (Object)null && !_preparedAudioSource.isPlaying)
					{
						_preparedAudioSource.Play();
					}
					BestestTV.TvIsOn = true;
				}
				else
				{
					if (source.isPlaying)
					{
						source.Stop();
					}
					if ((Object)(object)_preparedAudioSource != (Object)null && _preparedAudioSource.isPlaying)
					{
						_preparedAudioSource.Stop();
					}
					BestestTV.TvIsOn = false;
				}
				TVSyncPatches.UpdateObservedStateSnapshot(_preparedClipIndex, _preparedSeekTime, _preparedTvOn, _preparedMediaId);
			}
			finally
			{
				_preparedVideoSource = null;
				_preparedAudioSource = null;
				_preparedMediaId = string.Empty;
				TVSyncPatches.EndSuppressedRemoteApply();
			}
		}

		private static void QueuePreparedPlayback(VideoPlayer source, AudioSource audio, int clipIndex, double seekTime, bool tvOn, string mediaId)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Expected O, but got Unknown
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Expected O, but got Unknown
			if ((Object)(object)_preparedVideoSource != (Object)null)
			{
				VideoPlayer preparedVideoSource = _preparedVideoSource;
				object obj = <>O.<0>__OnVideoPrepared;
				if (obj == null)
				{
					EventHandler val = OnVideoPrepared;
					<>O.<0>__OnVideoPrepared = val;
					obj = (object)val;
				}
				preparedVideoSource.prepareCompleted -= (EventHandler)obj;
				TVSyncPatches.EndSuppressedRemoteApply();
			}
			_preparedVideoSource = source;
			_preparedAudioSource = audio;
			_preparedSeekTime = seekTime;
			_preparedTvOn = tvOn;
			_preparedClipIndex = clipIndex;
			_preparedMediaId = mediaId ?? string.Empty;
			object obj2 = <>O.<0>__OnVideoPrepared;
			if (obj2 == null)
			{
				EventHandler val2 = OnVideoPrepared;
				<>O.<0>__OnVideoPrepared = val2;
				obj2 = (object)val2;
			}
			source.prepareCompleted -= (EventHandler)obj2;
			object obj3 = <>O.<0>__OnVideoPrepared;
			if (obj3 == null)
			{
				EventHandler val3 = OnVideoPrepared;
				<>O.<0>__OnVideoPrepared = val3;
				obj3 = (object)val3;
			}
			source.prepareCompleted += (EventHandler)obj3;
			source.Prepare();
		}

		private static void ApplyVisualPowerForClients(bool tvOn)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsClient || singleton.IsServer)
			{
				return;
			}
			TVScript[] array = Object.FindObjectsOfType<TVScript>(true);
			TVScript[] array2 = array;
			foreach (TVScript val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					try
					{
						val.TurnTVOnOff(tvOn);
					}
					catch (Exception ex)
					{
						TVSyncPlugin.Log.LogDebug((object)("[CymruTVSync] Visual power fallback failed on TVScript: " + ex.Message));
					}
				}
			}
		}

		public static void ApplyState(int clipIndex, double seekTime, bool tvOn, string mediaId = "")
		{
			if (!BestestTV.Ready || (Object)(object)BestestTV.VideoSource == (Object)null)
			{
				return;
			}
			TVSyncPatches.BeginSuppressedRemoteApply();
			try
			{
				bool tvIsOn = BestestTV.TvIsOn;
				VideoPlayer videoSource = BestestTV.VideoSource;
				AudioSource audioSource = BestestTV.AudioSource;
				List<string> videos = BestestTV.Videos;
				string text = BestestTV.NormalizeMediaId(mediaId);
				string b = BestestTV.NormalizeMediaId(videoSource.url);
				int num = clipIndex;
				if (!string.IsNullOrEmpty(text))
				{
					int num2 = BestestTV.FindVideoIndexByMediaId(text);
					if (num2 >= 0)
					{
						num = num2;
					}
				}
				bool flag = videos != null && num >= 0 && num < videos.Count;
				bool flag2 = !string.IsNullOrEmpty(text) && !string.Equals(text, b, StringComparison.OrdinalIgnoreCase);
				bool flag3 = false;
				if (flag && (BestestTV.TVIndex != num || flag2))
				{
					BestestTV.TVIndex = num;
					videoSource.Stop();
					if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying)
					{
						audioSource.Stop();
					}
					videoSource.url = "file://" + videos[num];
					videoSource.time = 0.0;
					flag3 = true;
				}
				else if (!flag && flag2)
				{
					TVSyncPlugin.Log.LogWarning((object)$"[CymruTVSync] Received media id '{text}' that does not exist in local list. Using incoming clip index {clipIndex} as fallback.");
				}
				if (!flag3 && tvOn && !videoSource.isPrepared)
				{
					if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying)
					{
						audioSource.Stop();
					}
					flag3 = true;
				}
				if (flag3)
				{
					QueuePreparedPlayback(videoSource, audioSource, num, seekTime, tvOn, mediaId);
					if (tvIsOn != tvOn)
					{
						ApplyVisualPowerForClients(tvOn);
					}
					return;
				}
				videoSource.time = seekTime;
				if (tvOn)
				{
					if (!BestestTV.TvIsOn)
					{
						BestestTV.TvIsOn = true;
					}
					if (!videoSource.isPlaying)
					{
						videoSource.Play();
					}
					if ((Object)(object)audioSource != (Object)null && !audioSource.isPlaying)
					{
						audioSource.Play();
					}
				}
				else
				{
					if (videoSource.isPlaying)
					{
						videoSource.Stop();
					}
					if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying)
					{
						audioSource.Stop();
					}
					BestestTV.TvIsOn = false;
				}
				if (tvIsOn != tvOn)
				{
					ApplyVisualPowerForClients(tvOn);
				}
				TVSyncPatches.UpdateObservedStateSnapshot(num, seekTime, tvOn, mediaId);
			}
			finally
			{
				if ((Object)(object)_preparedVideoSource == (Object)null)
				{
					TVSyncPatches.EndSuppressedRemoteApply();
				}
			}
		}
	}
	[HarmonyPatch(typeof(TVScript))]
	public class TVSyncReceiver
	{
		private static bool _subscribed;

		[HarmonyPostfix]
		[HarmonyPatch("OnEnable")]
		public static void OnEnable_Postfix(TVScript __instance)
		{
			BestestTV.Init();
			if (!_subscribed)
			{
				TVSyncNetworkHandler.OnTVStateReceived -= OnStateReceived;
				TVSyncNetworkHandler.OnTVStateReceived += OnStateReceived;
				TVSyncNetworkHandler.OnLockStateReceived -= OnLockReceived;
				TVSyncNetworkHandler.OnLockStateReceived += OnLockReceived;
				TVSyncNetworkHandler.OnLockFeatureStateReceived -= OnLockFeatureReceived;
				TVSyncNetworkHandler.OnLockFeatureStateReceived += OnLockFeatureReceived;
				_subscribed = true;
			}
			TVLockGuard.ApplyTriggerLock();
			if (!NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer)
			{
				if ((Object)(object)TVSyncNetworkHandler.Instance != (Object)null)
				{
					TVSyncNetworkHandler.Instance.TryRequestFullSyncFromServer("TVScript.OnEnable");
				}
				else
				{
					TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] TV enabled before network handler existed, so no sync request could be sent yet.");
				}
			}
		}

		[HarmonyPostfix]
		[HarmonyPatch("OnDisable")]
		public static void OnDisable_Postfix()
		{
			TVSyncNetworkHandler.OnTVStateReceived -= OnStateReceived;
			TVSyncNetworkHandler.OnLockStateReceived -= OnLockReceived;
			TVSyncNetworkHandler.OnLockFeatureStateReceived -= OnLockFeatureReceived;
			_subscribed = false;
		}

		private static void OnStateReceived(int clipIndex, double seekTime, bool tvOn, string mediaId)
		{
			TVSyncInputPatch.ApplyState(clipIndex, seekTime, tvOn, mediaId);
		}

		private static void OnLockReceived(bool isLocked)
		{
			TVLockGuard.ApplyTriggerLock();
		}

		private static void OnLockFeatureReceived(bool enabled)
		{
			TVLockGuard.ApplyTriggerLock();
			if (!enabled)
			{
				TVSyncInputPatch.ApplyState(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId);
			}
		}
	}
	[HarmonyPatch(typeof(InteractTrigger), "Interact")]
	public class TVSyncInteractTriggerPatch
	{
		[HarmonyPrefix]
		[HarmonyPriority(600)]
		public static bool Prefix(InteractTrigger __instance)
		{
			if (!TVLockGuard.ShouldBlockLocalInput())
			{
				return true;
			}
			if (__instance.hoverTip == null || !__instance.hoverTip.Contains("Switch TV"))
			{
				return true;
			}
			TVLockGuard.NotifyBlocked("InteractTrigger.Interact");
			return false;
		}
	}
	[HarmonyPatch(typeof(PlayerControllerB), "Update")]
	public class TVSyncLockKeyPatch
	{
		[HarmonyPostfix]
		public static void Update_Postfix(PlayerControllerB __instance)
		{
			if (!((NetworkBehaviour)__instance).IsOwner || !__instance.isPlayerControlled || __instance.inTerminalMenu || __instance.isTypingChat || __instance.isPlayerDead || (!NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer) || (Object)(object)TVSyncNetworkHandler.Instance == (Object)null)
			{
				return;
			}
			if (BestestTV.WasPressedThisFrame((Key)29))
			{
				TVSyncNetworkHandler.Instance.TryToggleLockFeatureFromHost("HostKey:O");
			}
			else
			{
				if (!BestestTV.WasPressedThisFrame((Key)26))
				{
					return;
				}
				if (!TVSyncState.LockFeatureEnabled)
				{
					if ((Object)(object)HUDManager.Instance != (Object)null)
					{
						HUDManager.Instance.DisplayTip("CymruTV Sync", "Lock controls are disabled. Press O to enable.", false, false, "TVLockDisabledTip");
					}
				}
				else
				{
					TVSyncNetworkHandler.Instance.TryToggleLockFromHost("HostKey:L");
				}
			}
		}
	}
	[HarmonyPatch(typeof(ShipBuildModeManager))]
	public static class TVSyncStorageBlockPatch
	{
		private static MethodInfo _returnUnlockableFromStorageClientRpcMethod;

		private static bool IsTelevision(PlaceableShipObject shipObject)
		{
			if ((Object)(object)shipObject == (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)shipObject).GetComponentInChildren<TVScript>(true) != (Object)null)
			{
				return true;
			}
			StartOfRound instance = StartOfRound.Instance;
			if (instance?.unlockablesList?.unlockables == null)
			{
				return false;
			}
			int unlockableID = shipObject.unlockableID;
			if (unlockableID < 0 || unlockableID >= instance.unlockablesList.unlockables.Count)
			{
				return false;
			}
			UnlockableItem val = instance.unlockablesList.unlockables[unlockableID];
			if (val == null)
			{
				return false;
			}
			string text = val.unlockableName ?? string.Empty;
			return text.IndexOf("television", StringComparison.OrdinalIgnoreCase) >= 0 || text.Equals("TV", StringComparison.OrdinalIgnoreCase);
		}

		private static void TryReturnUnlockableToClient(int unlockableId, int playerWhoStored)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			if ((object)_returnUnlockableFromStorageClientRpcMethod == null)
			{
				_returnUnlockableFromStorageClientRpcMethod = AccessTools.Method(typeof(StartOfRound), "ReturnUnlockableFromStorageClientRpc", (Type[])null, (Type[])null);
			}
			if (!(_returnUnlockableFromStorageClientRpcMethod == null))
			{
				ClientRpcParams val = default(ClientRpcParams);
				val.Send = new ClientRpcSendParams
				{
					TargetClientIds = new ulong[1] { (ulong)playerWhoStored }
				};
				ClientRpcParams val2 = val;
				ParameterInfo[] parameters = _returnUnlockableFromStorageClientRpcMethod.GetParameters();
				if (parameters.Length == 1)
				{
					_returnUnlockableFromStorageClientRpcMethod.Invoke(instance, new object[1] { unlockableId });
				}
				else if (parameters.Length >= 2)
				{
					_returnUnlockableFromStorageClientRpcMethod.Invoke(instance, new object[2] { unlockableId, val2 });
				}
			}
		}

		[HarmonyPrefix]
		[HarmonyPatch("StoreObjectLocalClient")]
		public static bool PreventStoreHost(ShipBuildModeManager __instance)
		{
			if ((Object)(object)__instance == (Object)null || !((NetworkBehaviour)__instance).IsServer)
			{
				return true;
			}
			if (__instance.timeSincePlacingObject <= 0.25f || !__instance.InBuildMode || !Object.op_Implicit((Object)(object)__instance.placingObject))
			{
				return true;
			}
			PlaceableShipObject placingObject = __instance.placingObject;
			if (!IsTelevision(placingObject))
			{
				return true;
			}
			TVSyncPlugin.Log.LogInfo((object)"[CymruTVSync] Blocked host from storing the TV.");
			__instance.CancelBuildMode(false);
			AudioSource component = ((Component)placingObject).GetComponent<AudioSource>();
			if ((Object)(object)component != (Object)null && (Object)(object)placingObject.placeObjectSFX != (Object)null)
			{
				component.PlayOneShot(placingObject.placeObjectSFX);
			}
			return false;
		}

		[HarmonyPrefix]
		[HarmonyPatch("StoreObjectServerRpc")]
		public static bool PreventStoreServer(ShipBuildModeManager __instance, NetworkObjectReference objectRef, int playerWhoStored)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance == (Object)null || !((NetworkBehaviour)__instance).IsServer)
			{
				return true;
			}
			NetworkObjectReference val = objectRef;
			NetworkObject val2 = default(NetworkObject);
			if (!((NetworkObjectReference)(ref val)).TryGet(ref val2, (NetworkManager)null) || (Object)(object)val2 == (Object)null)
			{
				return true;
			}
			PlaceableShipObject componentInChildren = ((Component)val2).GetComponentInChildren<PlaceableShipObject>();
			if (!IsTelevision(componentInChildren))
			{
				return true;
			}
			TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync] Blocked client {playerWhoStored} from storing the TV.");
			if ((Object)(object)componentInChildren != (Object)null)
			{
				TryReturnUnlockableToClient(componentInChildren.unlockableID, playerWhoStored);
			}
			return false;
		}
	}
	[BepInPlugin("CymruTV.Sync", "CymruTV Sync", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class TVSyncPlugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		private readonly Harmony _harmony = new Harmony("CymruTV.Sync");

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			if ((Object)(object)Object.FindObjectOfType<HostLockStatusOverlay>() == (Object)null)
			{
				GameObject val = new GameObject("CymruTVSyncHostLockOverlay");
				Object.DontDestroyOnLoad((Object)(object)val);
				val.AddComponent<HostLockStatusOverlay>();
			}
			TVSyncNetworkHandler.EnsureInstance();
			NetcodePatcher();
			_harmony.PatchAll(typeof(TVSyncNetworkObjectManager));
			_harmony.PatchAll(typeof(TVSyncPatches));
			_harmony.PatchAll(typeof(TVSyncInputPatch));
			_harmony.PatchAll(typeof(TVSyncBestestHooks));
			_harmony.PatchAll(typeof(TVSyncReceiver));
			_harmony.PatchAll(typeof(TVSyncLockKeyPatch));
			_harmony.PatchAll(typeof(TVSyncStorageBlockPatch));
			Log.LogInfo((object)"[CymruTVSync] Plugin loaded. Host keys: L = lock/unlock TV, O = enable/disable lock controls.");
		}

		private static void NetcodePatcher()
		{
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			Type[] array = types;
			foreach (Type type in array)
			{
				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic);
				MethodInfo[] array2 = methods;
				foreach (MethodInfo methodInfo in array2)
				{
					object[] customAttributes = methodInfo.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), inherit: false);
					if (customAttributes.Length != 0)
					{
						methodInfo.Invoke(null, null);
					}
				}
			}
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "CymruTVSync";

		public const string PLUGIN_NAME = "CymruTVSync";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace CymruTVSync.Patches
{
	[HarmonyPatch(typeof(TVScript))]
	public class ExampleTVPatch
	{
		[HarmonyPatch("SwitchTVLocalClient")]
		[HarmonyPrefix]
		private static void SwitchTVPrefix(TVScript __instance)
		{
			StartOfRound.Instance.shipRoomLights.SetShipLightsBoolean(__instance.tvOn);
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace __GEN
{
	internal class NetworkVariableSerializationHelper
	{
		[RuntimeInitializeOnLoadMethod]
		internal static void InitializeSerialization()
		{
		}
	}
}
namespace CymruTVSync.NetcodePatcher
{
	[AttributeUsage(AttributeTargets.Module)]
	internal class NetcodePatchedAssemblyAttribute : Attribute
	{
	}
}