Decompiled source of BoomBoxOverhaul v2.0.2

BepInEx/plugins/BoomBoxOverhaulV2.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using GameNetcodeStuff;
using HarmonyLib;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.Networking;

[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: AssemblyCompany("BoomBoxOverhaulV2")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BoomBoxOverhaulV2")]
[assembly: AssemblyTitle("BoomBoxOverhaulV2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BoomBoxOverhaul;

internal static class BoomBoxOverhaulNet
{
	[CompilerGenerated]
	private sealed class <BootLoop>d__15 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

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

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

		[DebuggerHidden]
		public <BootLoop>d__15(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				break;
			case 1:
				<>1__state = -1;
				break;
			}
			TryBindToNetworkManager();
			if ((Object)(object)boundManager != (Object)null && handlersRegistered && boundManager.IsServer)
			{
				BroadcastSyncSettings(Plugin.LocalVolumeOnly.Value);
			}
			<>2__current = (object)new WaitForSeconds(1f);
			<>1__state = 1;
			return true;
		}

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

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

	private const string MsgRequestPlay = "BoomBoxOverhaul_RequestPlay";

	private const string MsgPrepareTrack = "BoomBoxOverhaul_PrepareTrack";

	private const string MsgNotifyReady = "BoomBoxOverhaul_NotifyReady";

	private const string MsgBeginPlayback = "BoomBoxOverhaul_BeginPlayback";

	private const string MsgRequestStop = "BoomBoxOverhaul_RequestStop";

	private const string MsgStopPlayback = "BoomBoxOverhaul_StopPlayback";

	private const string MsgRejectPlay = "BoomBoxOverhaul_RejectPlay";

	private const string MsgSetVolume = "BoomBoxOverhaul_SetVolume";

	private const string MsgApplyVolume = "BoomBoxOverhaul_ApplyVolume";

	private const string MsgSyncSettings = "BoomBoxOverhaul_SyncSettings";

	private static MonoBehaviour host;

	private static bool initialized;

	private static bool handlersRegistered;

	private static NetworkManager boundManager;

	public static void Initialize(MonoBehaviour coroutineHost)
	{
		if (!initialized)
		{
			initialized = true;
			host = coroutineHost;
			host.StartCoroutine(BootLoop());
		}
	}

	[IteratorStateMachine(typeof(<BootLoop>d__15))]
	private static IEnumerator BootLoop()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <BootLoop>d__15(0);
	}

	private static void TryBindToNetworkManager()
	{
		NetworkManager singleton = NetworkManager.Singleton;
		if (!((Object)(object)singleton == (Object)null))
		{
			if ((Object)(object)boundManager != (Object)(object)singleton)
			{
				UnregisterHandlers();
				boundManager = singleton;
				handlersRegistered = false;
				Plugin.Log("BoomBoxOverhaul bound to NetworkManager.");
			}
			if (!handlersRegistered)
			{
				RegisterHandlers();
			}
		}
	}

	private static void RegisterHandlers()
	{
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Expected O, but got Unknown
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Expected O, but got Unknown
		//IL_0070: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Expected O, but got Unknown
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Expected O, but got Unknown
		//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Expected O, but got Unknown
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Expected O, but got Unknown
		//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Expected O, but got Unknown
		//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Expected O, but got Unknown
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Expected O, but got Unknown
		//IL_0118: Unknown result type (might be due to invalid IL or missing references)
		//IL_0122: Expected O, but got Unknown
		if (!((Object)(object)boundManager == (Object)null) && boundManager.CustomMessagingManager != null)
		{
			CustomMessagingManager customMessagingManager = boundManager.CustomMessagingManager;
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_RequestPlay", new HandleNamedMessageDelegate(OnRequestPlay));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_PrepareTrack", new HandleNamedMessageDelegate(OnPrepareTrack));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_NotifyReady", new HandleNamedMessageDelegate(OnNotifyReady));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_BeginPlayback", new HandleNamedMessageDelegate(OnBeginPlayback));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_RequestStop", new HandleNamedMessageDelegate(OnRequestStop));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_StopPlayback", new HandleNamedMessageDelegate(OnStopPlayback));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_RejectPlay", new HandleNamedMessageDelegate(OnRejectPlay));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_SetVolume", new HandleNamedMessageDelegate(OnSetVolume));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_ApplyVolume", new HandleNamedMessageDelegate(OnApplyVolume));
			customMessagingManager.RegisterNamedMessageHandler("BoomBoxOverhaul_SyncSettings", new HandleNamedMessageDelegate(OnSyncSettings));
			handlersRegistered = true;
			Plugin.Log("BoomBoxOverhaul network handlers registered.");
		}
	}

	private static void UnregisterHandlers()
	{
		if (!((Object)(object)boundManager == (Object)null) && boundManager.CustomMessagingManager != null && handlersRegistered)
		{
			CustomMessagingManager customMessagingManager = boundManager.CustomMessagingManager;
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_RequestPlay");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_PrepareTrack");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_NotifyReady");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_BeginPlayback");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_RequestStop");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_StopPlayback");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_RejectPlay");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_SetVolume");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_ApplyVolume");
			customMessagingManager.UnregisterNamedMessageHandler("BoomBoxOverhaul_SyncSettings");
			handlersRegistered = false;
			Plugin.Log("BoomBoxOverhaul network handlers unregistered.");
		}
	}

	public static UnifiedBoomboxController GetController(ulong networkObjectId)
	{
		if ((Object)(object)boundManager == (Object)null || boundManager.SpawnManager == null)
		{
			return null;
		}
		if (!boundManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var value))
		{
			return null;
		}
		return ((Component)value).GetComponent<UnifiedBoomboxController>();
	}

	public static void SendRequestPlay(ulong networkObjectId, string url)
	{
		//IL_004c: 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_0074: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsClient)
		{
			Plugin.Warn("SendRequestPlay failed: network not ready.");
			return;
		}
		FastBufferWriter val = default(FastBufferWriter);
		((FastBufferWriter)(ref val))..ctor(8192, (Allocator)2, -1);
		try
		{
			((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe(url, false);
			boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_RequestPlay", 0uL, val, (NetworkDelivery)3);
		}
		finally
		{
			((IDisposable)(FastBufferWriter)(ref val)).Dispose();
		}
		Plugin.Log("Sent play request for object " + networkObjectId);
	}

	public static void BroadcastPrepareTrack(ulong networkObjectId, string canonicalUrl, string videoId, int playlistIndex, string[] playlistIds)
	{
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: 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_00e8: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsServer)
		{
			Plugin.Warn("BroadcastPrepareTrack failed: server network not ready.");
			return;
		}
		ulong[] connectedClientIds = GetConnectedClientIds();
		FastBufferWriter val = default(FastBufferWriter);
		for (int i = 0; i < connectedClientIds.Length; i++)
		{
			((FastBufferWriter)(ref val))..ctor(16384, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(canonicalUrl, false);
				((FastBufferWriter)(ref val)).WriteValueSafe(videoId, false);
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref playlistIndex, default(ForPrimitives));
				int num = playlistIds.Length;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives));
				for (int j = 0; j < playlistIds.Length; j++)
				{
					((FastBufferWriter)(ref val)).WriteValueSafe(playlistIds[j], false);
				}
				boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_PrepareTrack", connectedClientIds[i], val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}
		Plugin.Log("Broadcast prepare track for object " + networkObjectId);
	}

	public static void SendNotifyReady(ulong networkObjectId, bool success)
	{
		//IL_004f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0080: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsClient)
		{
			Plugin.Warn("SendNotifyReady failed: network not ready.");
			return;
		}
		FastBufferWriter val = default(FastBufferWriter);
		((FastBufferWriter)(ref val))..ctor(256, (Allocator)2, -1);
		try
		{
			((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref success, default(ForPrimitives));
			boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_NotifyReady", 0uL, val, (NetworkDelivery)3);
		}
		finally
		{
			((IDisposable)(FastBufferWriter)(ref val)).Dispose();
		}
		Plugin.Log("Sent ready state " + success + " for object " + networkObjectId);
	}

	public static void BroadcastBeginPlaybackReadyOnly(ulong networkObjectId, string videoId, HashSet<ulong> readyClientIds)
	{
		//IL_0062: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsServer)
		{
			Plugin.Warn("BroadcastBeginPlaybackReadyOnly failed: server network not ready.");
			return;
		}
		FastBufferWriter val = default(FastBufferWriter);
		foreach (ulong readyClientId in readyClientIds)
		{
			((FastBufferWriter)(ref val))..ctor(2048, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(videoId, false);
				boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_BeginPlayback", readyClientId, val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}
		Plugin.Log("Broadcast begin playback to ready clients only: " + readyClientIds.Count);
	}

	public static void SendRequestStop(ulong networkObjectId)
	{
		//IL_004c: 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_006a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsClient)
		{
			Plugin.Warn("SendRequestStop failed: network not ready.");
			return;
		}
		FastBufferWriter val = default(FastBufferWriter);
		((FastBufferWriter)(ref val))..ctor(128, (Allocator)2, -1);
		try
		{
			((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
			boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_RequestStop", 0uL, val, (NetworkDelivery)3);
		}
		finally
		{
			((IDisposable)(FastBufferWriter)(ref val)).Dispose();
		}
		Plugin.Log("Sent stop request for object " + networkObjectId);
	}

	public static void BroadcastStopPlayback(ulong networkObjectId)
	{
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsServer)
		{
			Plugin.Warn("BroadcastStopPlayback failed: server network not ready.");
			return;
		}
		ulong[] connectedClientIds = GetConnectedClientIds();
		FastBufferWriter val = default(FastBufferWriter);
		for (int i = 0; i < connectedClientIds.Length; i++)
		{
			((FastBufferWriter)(ref val))..ctor(128, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
				boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_StopPlayback", connectedClientIds[i], val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}
		Plugin.Log("Broadcast stop playback for object " + networkObjectId);
	}

	public static void SendRejectPlay(ulong targetClientId, ulong networkObjectId, string reason)
	{
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsServer)
		{
			return;
		}
		FastBufferWriter val = default(FastBufferWriter);
		((FastBufferWriter)(ref val))..ctor(2048, (Allocator)2, -1);
		try
		{
			((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe(reason, false);
			boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_RejectPlay", targetClientId, val, (NetworkDelivery)3);
		}
		finally
		{
			((IDisposable)(FastBufferWriter)(ref val)).Dispose();
		}
	}

	public static void SendSetVolume(ulong networkObjectId, float volume)
	{
		//IL_004c: 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_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsClient)
		{
			Plugin.Warn("SendSetVolume failed: network not ready.");
			return;
		}
		FastBufferWriter val = default(FastBufferWriter);
		((FastBufferWriter)(ref val))..ctor(256, (Allocator)2, -1);
		try
		{
			((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
			((FastBufferWriter)(ref val)).WriteValueSafe<float>(ref volume, default(ForPrimitives));
			boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_SetVolume", 0uL, val, (NetworkDelivery)3);
		}
		finally
		{
			((IDisposable)(FastBufferWriter)(ref val)).Dispose();
		}
	}

	public static void BroadcastApplyVolume(ulong networkObjectId, float volume)
	{
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_006b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsServer)
		{
			Plugin.Warn("BroadcastApplyVolume failed: server network not ready.");
			return;
		}
		ulong[] connectedClientIds = GetConnectedClientIds();
		FastBufferWriter val = default(FastBufferWriter);
		for (int i = 0; i < connectedClientIds.Length; i++)
		{
			((FastBufferWriter)(ref val))..ctor(256, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe<float>(ref volume, default(ForPrimitives));
				boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_ApplyVolume", connectedClientIds[i], val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}
	}

	public static void BroadcastSyncSettings(bool localVolumeOnly)
	{
		//IL_0049: 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_0069: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)boundManager == (Object)null || !handlersRegistered || !boundManager.IsServer)
		{
			return;
		}
		ulong[] connectedClientIds = GetConnectedClientIds();
		FastBufferWriter val = default(FastBufferWriter);
		for (int i = 0; i < connectedClientIds.Length; i++)
		{
			((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1);
			try
			{
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref localVolumeOnly, default(ForPrimitives));
				boundManager.CustomMessagingManager.SendNamedMessage("BoomBoxOverhaul_SyncSettings", connectedClientIds[i], val, (NetworkDelivery)3);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}
		Plugin.Log("Broadcast synced settings. LocalVolumeOnly = " + localVolumeOnly);
	}

	private static void OnRequestPlay(ulong senderClientId, FastBufferReader reader)
	{
		//IL_001e: 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)
		Plugin.Log("OnRequestPlay received from client " + senderClientId);
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		string url = default(string);
		((FastBufferReader)(ref reader)).ReadValueSafe(ref url, false);
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ServerHandlePlay(senderClientId, url);
		}
		else
		{
			Plugin.Warn("OnRequestPlay could not find controller for object " + networkObjectId);
		}
	}

	private static void OnPrepareTrack(ulong senderClientId, FastBufferReader reader)
	{
		//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_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		Plugin.Log("OnPrepareTrack received");
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		string canonicalUrl = default(string);
		((FastBufferReader)(ref reader)).ReadValueSafe(ref canonicalUrl, false);
		string videoId = default(string);
		((FastBufferReader)(ref reader)).ReadValueSafe(ref videoId, false);
		int playlistIndex = default(int);
		((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref playlistIndex, default(ForPrimitives));
		int num = default(int);
		((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
		string[] array = new string[num];
		for (int i = 0; i < num; i++)
		{
			((FastBufferReader)(ref reader)).ReadValueSafe(ref array[i], false);
		}
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ClientPrepareTrack(canonicalUrl, videoId, playlistIndex, array);
		}
		else
		{
			Plugin.Warn("OnPrepareTrack could not find controller for object " + networkObjectId);
		}
	}

	private static void OnNotifyReady(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_001a: 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)
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		bool success = default(bool);
		((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref success, default(ForPrimitives));
		Plugin.Log("OnNotifyReady received from " + senderClientId + " success=" + success);
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ServerNotifyReady(senderClientId, success);
		}
		else
		{
			Plugin.Warn("OnNotifyReady could not find controller for object " + networkObjectId);
		}
	}

	private static void OnBeginPlayback(ulong senderClientId, FastBufferReader reader)
	{
		//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)
		Plugin.Log("OnBeginPlayback received");
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		string videoId = default(string);
		((FastBufferReader)(ref reader)).ReadValueSafe(ref videoId, false);
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ClientBeginPlayback(videoId);
		}
		else
		{
			Plugin.Warn("OnBeginPlayback could not find controller for object " + networkObjectId);
		}
	}

	private static void OnRequestStop(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)
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ServerHandleStop();
		}
		else
		{
			Plugin.Warn("OnRequestStop could not find controller for object " + networkObjectId);
		}
	}

	private static void OnStopPlayback(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)
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ClientStopPlayback();
		}
		else
		{
			Plugin.Warn("OnStopPlayback could not find controller for object " + networkObjectId);
		}
	}

	private static void OnRejectPlay(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)
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		string reason = default(string);
		((FastBufferReader)(ref reader)).ReadValueSafe(ref reason, false);
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ClientRejectPlay(reason);
		}
	}

	private static void OnSetVolume(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_001a: 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)
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		float volume = default(float);
		((FastBufferReader)(ref reader)).ReadValueSafe<float>(ref volume, default(ForPrimitives));
		Plugin.Log("OnSetVolume received from " + senderClientId + " volume=" + volume);
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ServerHandleSetVolume(volume);
		}
		else
		{
			Plugin.Warn("OnSetVolume could not find controller for object " + networkObjectId);
		}
	}

	private static void OnApplyVolume(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_001a: 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)
		ulong networkObjectId = default(ulong);
		((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
		float volume = default(float);
		((FastBufferReader)(ref reader)).ReadValueSafe<float>(ref volume, default(ForPrimitives));
		Plugin.Log("OnApplyVolume received volume=" + volume);
		UnifiedBoomboxController controller = GetController(networkObjectId);
		if ((Object)(object)controller != (Object)null)
		{
			controller.ClientApplyNetworkVolume(volume);
		}
		else
		{
			Plugin.Warn("OnApplyVolume could not find controller for object " + networkObjectId);
		}
	}

	private static void OnSyncSettings(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 syncedLocalVolumeOnly = default(bool);
		((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref syncedLocalVolumeOnly, default(ForPrimitives));
		Plugin.SyncedLocalVolumeOnly = syncedLocalVolumeOnly;
		Plugin.HasSyncedVolumeMode = true;
		Plugin.Log("Received synced settings. LocalVolumeOnly = " + syncedLocalVolumeOnly);
	}

	private static ulong[] GetConnectedClientIds()
	{
		List<ulong> list = new List<ulong>();
		if ((Object)(object)boundManager == (Object)null || boundManager.ConnectedClients == null)
		{
			return list.ToArray();
		}
		foreach (KeyValuePair<ulong, NetworkClient> connectedClient in boundManager.ConnectedClients)
		{
			list.Add(connectedClient.Key);
		}
		return list.ToArray();
	}
}
public class BoomBoxOverhaulNetBoot : MonoBehaviour
{
	private void Awake()
	{
		Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
	}

	private void Start()
	{
		BoomBoxOverhaulNet.Initialize((MonoBehaviour)(object)this);
	}
}
internal static class CacheManager
{
	public static string BuildTrackBasePath(string videoId)
	{
		return Path.Combine(Plugin.CacheFolder, videoId);
	}

	public static string BuildAudioPath(string videoId)
	{
		return BuildTrackBasePath(videoId) + ".mp3";
	}

	public static string BuildMetaPath(string videoId)
	{
		return BuildTrackBasePath(videoId) + ".txt";
	}

	public static bool HasTrack(string videoId)
	{
		return File.Exists(BuildAudioPath(videoId));
	}

	public static void Touch(string path)
	{
		try
		{
			if (File.Exists(path))
			{
				File.SetLastWriteTimeUtc(path, DateTime.UtcNow);
			}
		}
		catch
		{
		}
	}

	public static void WriteMeta(string videoId, string title)
	{
		try
		{
			File.WriteAllText(BuildMetaPath(videoId), title ?? "");
		}
		catch
		{
		}
	}

	public static string ReadMeta(string videoId)
	{
		try
		{
			string path = BuildMetaPath(videoId);
			if (File.Exists(path))
			{
				return File.ReadAllText(path);
			}
		}
		catch
		{
		}
		return "";
	}

	public static void Prune()
	{
		try
		{
			DirectoryInfo directoryInfo = new DirectoryInfo(Plugin.CacheFolder);
			FileInfo[] files = directoryInfo.GetFiles("*.mp3");
			List<FileInfo> list = new List<FileInfo>(files);
			list.Sort((FileInfo a, FileInfo b) => b.LastWriteTimeUtc.CompareTo(a.LastWriteTimeUtc));
			for (int i = Plugin.MaxCacheFiles.Value; i < list.Count; i++)
			{
				try
				{
					string fullName = list[i].FullName;
					string text = Path.Combine(list[i].DirectoryName ?? Plugin.CacheFolder, Path.GetFileNameWithoutExtension(fullName));
					File.Delete(fullName);
					string path = text + ".txt";
					if (File.Exists(path))
					{
						File.Delete(path);
					}
				}
				catch
				{
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Cache prune failed: " + ex);
		}
	}
}
internal sealed class DependencyState
{
	public string YtDlpPath;

	public string FfmpegPath;

	public bool IsReady()
	{
		return !string.IsNullOrEmpty(YtDlpPath) && !string.IsNullOrEmpty(FfmpegPath) && File.Exists(YtDlpPath) && File.Exists(FfmpegPath);
	}
}
internal static class DependencyBootstrapper
{
	[CompilerGenerated]
	private sealed class <>c__DisplayClass10_0
	{
		public Action action;

		public Exception caught;

		public bool done;

		internal void <RunBlockingTask>b__0()
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				caught = ex;
			}
			finally
			{
				done = true;
			}
		}
	}

	[CompilerGenerated]
	private sealed class <BootstrapCoroutine>d__7 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

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

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

		[DebuggerHidden]
		public <BootstrapCoroutine>d__7(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				status = "Checking dependencies...";
				Plugin.Log(status);
				<>2__current = RunBlockingTask(delegate
				{
					try
					{
						ResolveYtDlp();
						ResolveFfmpeg();
						if (state.IsReady())
						{
							status = "Dependencies ready.";
							Plugin.Log(status);
						}
						else
						{
							Plugin.Warn("Dependencies not fully ready. Status: " + status);
						}
					}
					catch (Exception ex)
					{
						status = "Dependency bootstrap failed: " + ex.Message;
						Plugin.Error(status);
						Plugin.Error(ex.ToString());
					}
				});
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				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();
		}
	}

	[CompilerGenerated]
	private sealed class <RunBlockingTask>d__10 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public Action action;

		private <>c__DisplayClass10_0 <>8__1;

		private Thread <thread>5__2;

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

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

		[DebuggerHidden]
		public <RunBlockingTask>d__10(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>8__1 = null;
			<thread>5__2 = null;
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>8__1 = new <>c__DisplayClass10_0();
				<>8__1.action = action;
				<>8__1.done = false;
				<>8__1.caught = null;
				<thread>5__2 = new Thread((ThreadStart)delegate
				{
					try
					{
						<>8__1.action();
					}
					catch (Exception caught)
					{
						<>8__1.caught = caught;
					}
					finally
					{
						<>8__1.done = true;
					}
				});
				<thread>5__2.IsBackground = true;
				<thread>5__2.Start();
				break;
			case 1:
				<>1__state = -1;
				break;
			}
			if (!<>8__1.done)
			{
				<>2__current = null;
				<>1__state = 1;
				return true;
			}
			if (<>8__1.caught != null)
			{
				Plugin.Error("Dependency worker thread failed: " + <>8__1.caught);
			}
			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();
		}
	}

	private static readonly object Gate = new object();

	private static bool started;

	private static string status = "Dependency check not started.";

	private static DependencyState state = new DependencyState();

	public static string GetStatus()
	{
		return status;
	}

	public static DependencyState GetState()
	{
		return state;
	}

	public static void EnsureStarted(MonoBehaviour host)
	{
		lock (Gate)
		{
			if (started)
			{
				Plugin.Log("Dependency bootstrap already started.");
				return;
			}
			started = true;
			Plugin.Log("Dependency bootstrap starting.");
			host.StartCoroutine(BootstrapCoroutine());
		}
	}

	[IteratorStateMachine(typeof(<BootstrapCoroutine>d__7))]
	private static IEnumerator BootstrapCoroutine()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <BootstrapCoroutine>d__7(0);
	}

	private static void ResolveYtDlp()
	{
		Plugin.Log("Checking for yt-dlp...");
		string text = Path.Combine(Plugin.ToolsFolder, "yt-dlp.exe");
		if (File.Exists(text))
		{
			state.YtDlpPath = text;
			status = "Using local yt-dlp.";
			Plugin.Log("yt-dlp found in tools folder: " + text);
			return;
		}
		if (Plugin.SearchPathForTools.Value)
		{
			string text2 = FileSystemHelpers.TryFindOnPath("yt-dlp.exe");
			if (!string.IsNullOrEmpty(text2))
			{
				state.YtDlpPath = text2;
				status = "Using PATH yt-dlp.";
				Plugin.Log("yt-dlp found on PATH: " + text2);
				return;
			}
		}
		if (!Plugin.AutoDownloadYtDlp.Value)
		{
			status = "yt-dlp missing.";
			Plugin.Warn("yt-dlp not found and auto-download is disabled.");
			return;
		}
		try
		{
			status = "Downloading yt-dlp...";
			Plugin.Log(status);
			using (WebClient webClient = new WebClient())
			{
				webClient.Headers.Add("User-Agent", "BoomBoxOverhaul/2.0.0");
				webClient.DownloadFile("https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", text);
			}
			state.YtDlpPath = text;
			status = "yt-dlp downloaded successfully.";
			Plugin.Log("yt-dlp ready at: " + text);
		}
		catch (Exception ex)
		{
			status = "Failed downloading yt-dlp: " + ex.Message;
			Plugin.Error(status);
		}
	}

	private static void ResolveFfmpeg()
	{
		Plugin.Log("Checking for ffmpeg...");
		string text = Path.Combine(Plugin.ToolsFolder, "ffmpeg.exe");
		if (File.Exists(text))
		{
			state.FfmpegPath = text;
			status = "Using local ffmpeg.";
			Plugin.Log("ffmpeg found in tools folder: " + text);
			return;
		}
		if (Plugin.SearchPathForTools.Value)
		{
			string text2 = FileSystemHelpers.TryFindOnPath("ffmpeg.exe");
			if (!string.IsNullOrEmpty(text2))
			{
				state.FfmpegPath = text2;
				status = "Using PATH ffmpeg.";
				Plugin.Log("ffmpeg found on PATH: " + text2);
				return;
			}
		}
		if (!Plugin.AutoDownloadFfmpeg.Value)
		{
			status = "ffmpeg missing.";
			Plugin.Warn("ffmpeg not found and auto-download is disabled.");
			return;
		}
		string text3 = ((Plugin.FfmpegZipUrl.Value == null) ? "" : Plugin.FfmpegZipUrl.Value.Trim());
		if (string.IsNullOrEmpty(text3))
		{
			text3 = "https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip";
			Plugin.Warn("ffmpeg ZIP URL was empty, falling back to built-in default.");
		}
		try
		{
			status = "Downloading FFmpeg...";
			Plugin.Log(status);
			string text4 = Path.Combine(Plugin.ToolsFolder, "ffmpeg_package.zip");
			string text5 = Path.Combine(Plugin.ToolsFolder, "ffmpeg_extract");
			if (File.Exists(text4))
			{
				File.Delete(text4);
			}
			if (Directory.Exists(text5))
			{
				Directory.Delete(text5, recursive: true);
			}
			using (WebClient webClient = new WebClient())
			{
				webClient.Headers.Add("User-Agent", "BoomBoxOverhaul/2.0.0");
				webClient.DownloadFile(text3, text4);
			}
			Plugin.Log("FFmpeg archive downloaded: " + text4);
			ZipFile.ExtractToDirectory(text4, text5);
			Plugin.Log("FFmpeg archive extracted.");
			string[] files = Directory.GetFiles(text5, "ffmpeg.exe", SearchOption.AllDirectories);
			string[] files2 = Directory.GetFiles(text5, "ffprobe.exe", SearchOption.AllDirectories);
			if (files.Length != 0)
			{
				File.Copy(files[0], text, overwrite: true);
				state.FfmpegPath = text;
				Plugin.Log("ffmpeg ready at: " + text);
				if (files2.Length != 0)
				{
					string text6 = Path.Combine(Plugin.ToolsFolder, "ffprobe.exe");
					File.Copy(files2[0], text6, overwrite: true);
					Plugin.Log("ffprobe ready at: " + text6);
				}
				status = "ffmpeg downloaded successfully.";
			}
			else
			{
				status = "ffmpeg.exe not found in archive.";
				Plugin.Warn(status);
			}
		}
		catch (Exception ex)
		{
			status = "Failed resolving ffmpeg: " + ex.Message;
			Plugin.Error(status);
		}
	}

	[IteratorStateMachine(typeof(<RunBlockingTask>d__10))]
	private static IEnumerator RunBlockingTask(Action action)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <RunBlockingTask>d__10(0)
		{
			action = action
		};
	}
}
internal static class FileSystemHelpers
{
	public static void TryDeleteDirectoryContents(string path)
	{
		try
		{
			if (!Directory.Exists(path))
			{
				return;
			}
			string[] files = Directory.GetFiles(path);
			for (int i = 0; i < files.Length; i++)
			{
				try
				{
					File.Delete(files[i]);
				}
				catch
				{
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Error("Failed clearing directory contents for '" + path + "': " + ex);
		}
	}

	public static string TryFindOnPath(string exeName)
	{
		try
		{
			string environmentVariable = Environment.GetEnvironmentVariable("PATH");
			if (string.IsNullOrEmpty(environmentVariable))
			{
				return null;
			}
			string[] array = environmentVariable.Split(Path.PathSeparator);
			for (int i = 0; i < array.Length; i++)
			{
				try
				{
					string text = array[i];
					if (!string.IsNullOrEmpty(text))
					{
						string text2 = Path.Combine(text.Trim(), exeName);
						if (File.Exists(text2))
						{
							return text2;
						}
					}
				}
				catch
				{
				}
			}
		}
		catch
		{
		}
		return null;
	}
}
[Serializable]
internal sealed class TrackInfo
{
	public string videoId = "";

	public string title = "";

	public string sourceUrl = "";

	public string cachePath = "";

	public float durationSeconds = 0f;
}
internal sealed class PlaylistState
{
	public readonly List<string> VideoIds = new List<string>();

	public int Index = 0;

	public bool HasCurrent()
	{
		return Index >= 0 && Index < VideoIds.Count;
	}

	public string GetCurrentId()
	{
		if (!HasCurrent())
		{
			return null;
		}
		return VideoIds[Index];
	}

	public string Advance()
	{
		Index++;
		return GetCurrentId();
	}

	public void Shuffle(Random rng)
	{
		for (int num = VideoIds.Count - 1; num > 0; num--)
		{
			int index = rng.Next(num + 1);
			string value = VideoIds[num];
			VideoIds[num] = VideoIds[index];
			VideoIds[index] = value;
		}
	}
}
[HarmonyPatch(typeof(BoomboxItem))]
public static class BoomboxItemPatches
{
	[HarmonyPostfix]
	[HarmonyPatch("Start")]
	public static void StartPostfix(BoomboxItem __instance, ref Item ___itemProperties)
	{
		try
		{
			if (Plugin.InfiniteBattery.Value)
			{
				___itemProperties.requiresBattery = false;
			}
			if ((Object)(object)((Component)__instance).GetComponent<UnifiedBoomboxController>() == (Object)null)
			{
				((Component)__instance).gameObject.AddComponent<UnifiedBoomboxController>();
			}
		}
		catch (Exception ex)
		{
			Plugin.Error("Boombox Start patch failed: " + ex);
		}
	}

	[HarmonyTranspiler]
	[HarmonyPatch("PocketItem")]
	public static IEnumerable<CodeInstruction> PocketItemTranspiler(IEnumerable<CodeInstruction> instructions)
	{
		List<CodeInstruction> list = new List<CodeInstruction>(instructions);
		try
		{
			int num = -1;
			int num2 = -1;
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i].opcode != OpCodes.Call)
				{
					continue;
				}
				string text = ((object)list[i]).ToString();
				if (text == null || text.IndexOf("BoomboxItem::StartMusic", StringComparison.Ordinal) < 0)
				{
					continue;
				}
				num2 = i;
				for (int num3 = i; num3 >= 0; num3--)
				{
					if (list[num3].opcode == OpCodes.Ldarg_0)
					{
						num = num3;
						break;
					}
				}
				break;
			}
			if (num > -1 && num2 > -1 && num2 >= num)
			{
				list.RemoveRange(num, num2 - num + 1);
				Plugin.Log("Patched BoomboxItem.PocketItem to preserve playback.");
			}
			else
			{
				Plugin.Warn("PocketItem patch could not find BoomboxItem::StartMusic block.");
			}
		}
		catch (Exception ex)
		{
			Plugin.Error("PocketItem transpiler failed: " + ex);
		}
		return list.AsEnumerable();
	}
}
[BepInPlugin("henreh.boomboxoverhaul", "BoomBoxOverhaulV2", "2.0.2")]
public class Plugin : BaseUnityPlugin
{
	public const string ModGuid = "henreh.boomboxoverhaul";

	public const string ModName = "BoomBoxOverhaulV2";

	public const string ModVersion = "2.0.2";

	internal static Plugin Instance;

	internal static Harmony Harmony;

	internal static ConfigEntry<bool> InfiniteBattery;

	internal static ConfigEntry<bool> KeepPlayingPocketed;

	internal static ConfigEntry<float> VolumeStep;

	internal static ConfigEntry<float> DefaultVolume;

	internal static ConfigEntry<KeyCode> OpenUiKey;

	internal static ConfigEntry<KeyCode> VolumeUpKey;

	internal static ConfigEntry<KeyCode> VolumeDownKey;

	internal static ConfigEntry<int> MaxCacheFiles;

	internal static ConfigEntry<float> ReadyTimeoutSeconds;

	internal static ConfigEntry<bool> AutoplayPlaylist;

	internal static ConfigEntry<bool> ShufflePlaylist;

	internal static ConfigEntry<int> MaxTrackSeconds;

	internal static ConfigEntry<bool> DeleteCacheOnBoot;

	internal static ConfigEntry<bool> LocalVolumeOnly;

	internal static ConfigEntry<bool> AutoDownloadYtDlp;

	internal static ConfigEntry<bool> AutoDownloadFfmpeg;

	internal static ConfigEntry<string> FfmpegZipUrl;

	internal static ConfigEntry<bool> SearchPathForTools;

	internal static bool SyncedLocalVolumeOnly = true;

	internal static bool HasSyncedVolumeMode = false;

	internal static string PluginFolder = "";

	internal static string CacheFolder = "";

	internal static string ToolsFolder = "";

	private void Awake()
	{
		//IL_02db: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e5: Expected O, but got Unknown
		//IL_031e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0324: Expected O, but got Unknown
		Instance = this;
		InfiniteBattery = ((BaseUnityPlugin)this).Config.Bind<bool>("Gameplay", "InfiniteBattery", true, "Boombox does not require battery.");
		KeepPlayingPocketed = ((BaseUnityPlugin)this).Config.Bind<bool>("Gameplay", "KeepPlayingPocketed", true, "Boombox keeps playing when pocketed.");
		VolumeStep = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "VolumeStep", 0.1f, "Volume increment/decrement amount.");
		DefaultVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Audio", "DefaultVolume", 1f, "Default local boombox volume.");
		OpenUiKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "OpenUiKey", (KeyCode)98, "Open URL input UI.");
		VolumeUpKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "VolumeUpKey", (KeyCode)61, "Increase boombox volume.");
		VolumeDownKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Input", "VolumeDownKey", (KeyCode)45, "Decrease boombox volume.");
		MaxCacheFiles = ((BaseUnityPlugin)this).Config.Bind<int>("Cache", "MaxCacheFiles", 15, "Maximum amount of downloaded tracks to keep.");
		ReadyTimeoutSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Networking", "ReadyTimeoutSeconds", 20f, "How long the server waits for clients to prepare before starting anyway.");
		AutoplayPlaylist = ((BaseUnityPlugin)this).Config.Bind<bool>("Playlist", "AutoplayPlaylist", true, "Automatically continue to next playlist track.");
		ShufflePlaylist = ((BaseUnityPlugin)this).Config.Bind<bool>("Playlist", "ShufflePlaylist", false, "Shuffle playlist order after resolving entries.");
		MaxTrackSeconds = ((BaseUnityPlugin)this).Config.Bind<int>("Downloads", "MaxTrackSeconds", 1800, "Maximum allowed track duration in seconds.");
		DeleteCacheOnBoot = ((BaseUnityPlugin)this).Config.Bind<bool>("Cache", "DeleteCacheOnBoot", true, "Clear cache when the plugin loads.");
		LocalVolumeOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio", "LocalVolumeOnly", false, "If true, volume changes are local only. If false, boombox volume is shared server-wide.");
		AutoDownloadYtDlp = ((BaseUnityPlugin)this).Config.Bind<bool>("Dependencies", "AutoDownloadYtDlp", true, "Automatically download yt-dlp if it is missing.");
		AutoDownloadFfmpeg = ((BaseUnityPlugin)this).Config.Bind<bool>("Dependencies", "AutoDownloadFfmpeg", true, "Automatically download ffmpeg if it is missing.");
		FfmpegZipUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Dependencies", "FfmpegZipUrl", "https://github.com/yt-dlp/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", "FFmpeg ZIP URL.");
		SearchPathForTools = ((BaseUnityPlugin)this).Config.Bind<bool>("Dependencies", "SearchPathForTools", true, "Search PATH for yt-dlp and ffmpeg.");
		PluginFolder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? Paths.PluginPath;
		ToolsFolder = Path.Combine(PluginFolder, "tools");
		CacheFolder = Path.Combine(PluginFolder, "cache");
		Directory.CreateDirectory(ToolsFolder);
		Directory.CreateDirectory(CacheFolder);
		if (DeleteCacheOnBoot.Value)
		{
			FileSystemHelpers.TryDeleteDirectoryContents(CacheFolder);
		}
		Harmony = new Harmony("henreh.boomboxoverhaul");
		Harmony.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"BoomBoxOverhaulV2 2.0.2 loaded.");
		DependencyBootstrapper.EnsureStarted((MonoBehaviour)(object)this);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Dependency bootstrap started.");
		GameObject val = new GameObject("BoomBoxOverhaulNetBoot");
		((Object)val).hideFlags = (HideFlags)61;
		val.AddComponent<BoomBoxOverhaulNetBoot>();
		Object.DontDestroyOnLoad((Object)(object)val);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"BoomBoxOverhaul network boot started.");
	}

	internal static bool UseLocalVolumeOnly()
	{
		if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsClient && HasSyncedVolumeMode)
		{
			return SyncedLocalVolumeOnly;
		}
		return LocalVolumeOnly.Value;
	}

	internal static void Log(string msg)
	{
		if ((Object)(object)Instance != (Object)null)
		{
			((BaseUnityPlugin)Instance).Logger.LogInfo((object)msg);
		}
	}

	internal static void Warn(string msg)
	{
		if ((Object)(object)Instance != (Object)null)
		{
			((BaseUnityPlugin)Instance).Logger.LogWarning((object)msg);
		}
	}

	internal static void Error(string msg)
	{
		if ((Object)(object)Instance != (Object)null)
		{
			((BaseUnityPlugin)Instance).Logger.LogError((object)msg);
		}
	}
}
public class UnifiedBoomboxController : MonoBehaviour
{
	[CompilerGenerated]
	private sealed class <>c__DisplayClass38_0
	{
		public bool fetchOk;

		public string canonicalUrl;

		public string videoId;

		public TrackInfo info;

		internal void <PrepareTrackLocalCoroutine>b__0()
		{
			fetchOk = YtDlpBridge.FetchTrack(canonicalUrl, videoId, out info);
		}
	}

	[CompilerGenerated]
	private sealed class <>c__DisplayClass45_0
	{
		public Action action;

		public Exception caught;

		public bool done;

		internal void <RunBlockingTask>b__0()
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				caught = ex;
			}
			finally
			{
				done = true;
			}
		}
	}

	[CompilerGenerated]
	private sealed class <PrepareTrackLocalCoroutine>d__38 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public string canonicalUrl;

		public string videoId;

		public UnifiedBoomboxController <>4__this;

		private <>c__DisplayClass38_0 <>8__1;

		private string <fileUrl>5__2;

		private UnityWebRequest <req>5__3;

		private AudioClip <clip>5__4;

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

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

		[DebuggerHidden]
		public <PrepareTrackLocalCoroutine>d__38(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			int num = <>1__state;
			if (num == -3 || num == 2)
			{
				try
				{
				}
				finally
				{
					<>m__Finally1();
				}
			}
			<>8__1 = null;
			<fileUrl>5__2 = null;
			<req>5__3 = null;
			<clip>5__4 = null;
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0260: Invalid comparison between Unknown and I4
			bool result;
			try
			{
				switch (<>1__state)
				{
				default:
					result = false;
					break;
				case 0:
					<>1__state = -1;
					<>8__1 = new <>c__DisplayClass38_0();
					<>8__1.canonicalUrl = canonicalUrl;
					<>8__1.videoId = videoId;
					<>8__1.fetchOk = false;
					<>8__1.info = new TrackInfo();
					Plugin.Log("Preparing local track download for: " + <>8__1.videoId);
					Plugin.Log("Downloading track: " + <>8__1.videoId);
					<>2__current = <>4__this.RunBlockingTask(delegate
					{
						<>8__1.fetchOk = YtDlpBridge.FetchTrack(<>8__1.canonicalUrl, <>8__1.videoId, out <>8__1.info);
					});
					<>1__state = 1;
					result = true;
					break;
				case 1:
					<>1__state = -1;
					if (!<>8__1.fetchOk || !File.Exists(<>8__1.info.cachePath))
					{
						<>4__this.statusText = "Download failed please try a new link, sorry!";
						Plugin.Warn("Download failed for: " + <>8__1.videoId);
						if ((Object)(object)<>4__this.Boombox != (Object)null && (Object)(object)((NetworkBehaviour)<>4__this.Boombox).NetworkObject != (Object)null)
						{
							BoomBoxOverhaulNet.SendNotifyReady(((NetworkBehaviour)<>4__this.Boombox).NetworkObject.NetworkObjectId, success: false);
						}
						result = false;
					}
					else
					{
						Plugin.Log("Download COMPLETE: " + <>8__1.videoId);
						Plugin.Log("Local track download complete for: " + <>8__1.videoId);
						<>4__this.statusText = "Loading clip...";
						<fileUrl>5__2 = "file:///" + <>8__1.info.cachePath.Replace("\\", "/");
						<req>5__3 = UnityWebRequestMultimedia.GetAudioClip(<fileUrl>5__2, (AudioType)13);
						<>1__state = -3;
						<>2__current = <req>5__3.SendWebRequest();
						<>1__state = 2;
						result = true;
					}
					break;
				case 2:
					<>1__state = -3;
					if ((int)<req>5__3.result != 1)
					{
						<>4__this.statusText = "Clip load failed";
						Plugin.Warn("Clip load failed for: " + <>8__1.videoId);
						if ((Object)(object)<>4__this.Boombox != (Object)null && (Object)(object)((NetworkBehaviour)<>4__this.Boombox).NetworkObject != (Object)null)
						{
							BoomBoxOverhaulNet.SendNotifyReady(((NetworkBehaviour)<>4__this.Boombox).NetworkObject.NetworkObjectId, success: false);
						}
						result = false;
						<>m__Finally1();
						break;
					}
					<clip>5__4 = DownloadHandlerAudioClip.GetContent(<req>5__3);
					((Object)<clip>5__4).name = (string.IsNullOrEmpty(<>8__1.info.title) ? <>8__1.videoId : <>8__1.info.title);
					if ((Object)(object)<>4__this.Audio.clip != (Object)null && (Object)(object)<>4__this.Audio.clip != (Object)(object)<clip>5__4)
					{
						try
						{
							Object.Destroy((Object)(object)<>4__this.Audio.clip);
						}
						catch
						{
						}
					}
					<>4__this.Audio.clip = <clip>5__4;
					<>4__this.tooltipScrollIndex = 0;
					<>4__this.tooltipScrollTimer = 0f;
					<>4__this.ApplyLocalVolume();
					<>4__this.statusText = "Ready: " + ((Object)<clip>5__4).name;
					Plugin.Log("Clip READY: " + <>8__1.videoId);
					Plugin.Log("Local clip ready for: " + <>8__1.videoId);
					if ((Object)(object)<>4__this.Boombox != (Object)null && (Object)(object)((NetworkBehaviour)<>4__this.Boombox).NetworkObject != (Object)null)
					{
						BoomBoxOverhaulNet.SendNotifyReady(((NetworkBehaviour)<>4__this.Boombox).NetworkObject.NetworkObjectId, success: true);
					}
					<clip>5__4 = null;
					<>m__Finally1();
					<req>5__3 = null;
					result = false;
					break;
				}
			}
			catch
			{
				//try-fault
				((IDisposable)this).Dispose();
				throw;
			}
			return result;
		}

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

		private void <>m__Finally1()
		{
			<>1__state = -1;
			if (<req>5__3 != null)
			{
				((IDisposable)<req>5__3).Dispose();
			}
		}

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

	[CompilerGenerated]
	private sealed class <RunBlockingTask>d__45 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public Action action;

		public UnifiedBoomboxController <>4__this;

		private <>c__DisplayClass45_0 <>8__1;

		private Thread <thread>5__2;

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

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

		[DebuggerHidden]
		public <RunBlockingTask>d__45(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>8__1 = null;
			<thread>5__2 = null;
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>8__1 = new <>c__DisplayClass45_0();
				<>8__1.action = action;
				<>8__1.done = false;
				<>8__1.caught = null;
				<thread>5__2 = new Thread((ThreadStart)delegate
				{
					try
					{
						<>8__1.action();
					}
					catch (Exception caught)
					{
						<>8__1.caught = caught;
					}
					finally
					{
						<>8__1.done = true;
					}
				});
				<thread>5__2.IsBackground = true;
				<thread>5__2.Start();
				break;
			case 1:
				<>1__state = -1;
				break;
			}
			if (!<>8__1.done)
			{
				<>2__current = null;
				<>1__state = 1;
				return true;
			}
			if (<>8__1.caught != null)
			{
				Plugin.Error("Background task failed: " + <>8__1.caught);
			}
			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();
		}
	}

	[CompilerGenerated]
	private sealed class <ServerWaitForReadyThenPlay>d__40 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public UnifiedBoomboxController <>4__this;

		private float <timeout>5__1;

		private float <start>5__2;

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

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

		[DebuggerHidden]
		public <ServerWaitForReadyThenPlay>d__40(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<timeout>5__1 = Mathf.Max(1f, Plugin.ReadyTimeoutSeconds.Value);
				<start>5__2 = Time.time;
				Plugin.Log("Waiting for clients to prepare track. Expected: " + <>4__this.expectedClients.Count);
				break;
			case 1:
				<>1__state = -1;
				break;
			}
			if (Time.time - <start>5__2 < <timeout>5__1)
			{
				if (<>4__this.expectedClients.Count <= 0 || <>4__this.readyClients.Count < <>4__this.expectedClients.Count)
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				Plugin.Log("All expected clients are ready. Starting playback immediately.");
			}
			if (<>4__this.readyClients.Count == 0)
			{
				<>4__this.ClientRejectPlay("No client prepared track");
				<>4__this.isPreparing = false;
				Plugin.Warn("No clients were ready before timeout.");
				return false;
			}
			if (<>4__this.readyClients.Count < <>4__this.expectedClients.Count)
			{
				Plugin.Warn("Timeout reached. Starting playback for ready clients only. Ready: " + <>4__this.readyClients.Count + " / Expected: " + <>4__this.expectedClients.Count);
			}
			else
			{
				Plugin.Log("Starting playback for all expected clients.");
			}
			if ((Object)(object)<>4__this.Boombox != (Object)null && (Object)(object)((NetworkBehaviour)<>4__this.Boombox).NetworkObject != (Object)null)
			{
				BoomBoxOverhaulNet.BroadcastBeginPlaybackReadyOnly(((NetworkBehaviour)<>4__this.Boombox).NetworkObject.NetworkObjectId, <>4__this.currentVideoId, <>4__this.readyClients);
			}
			<>4__this.isPreparing = false;
			<>4__this.isPlayingCustom = true;
			<>4__this.statusText = "Playing (" + <>4__this.readyClients.Count + "/" + <>4__this.expectedClients.Count + " ready)";
			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();
		}
	}

	public BoomboxItem Boombox;

	public AudioSource Audio;

	private float localVolume = 1f;

	private bool uiOpen = false;

	private string pendingUrl = "";

	private string statusText = "Idle";

	private PlaylistState playlist = new PlaylistState();

	private string currentVideoId = "";

	private bool isPreparing = false;

	private bool isPlayingCustom = false;

	private readonly HashSet<ulong> readyClients = new HashSet<ulong>();

	private readonly HashSet<ulong> expectedClients = new HashSet<ulong>();

	private readonly HashSet<ulong> failedClients = new HashSet<ulong>();

	private Coroutine localLoadRoutine;

	private Coroutine serverWaitRoutine;

	private bool suppressVanillaStopOnce = false;

	private bool cameraLocked = false;

	private float tooltipScrollTimer = 0f;

	private int tooltipScrollIndex = 0;

	private void Awake()
	{
		Plugin.Log("UnifiedBoomboxController attached to boombox.");
		Boombox = ((Component)this).GetComponent<BoomboxItem>();
		AudioSource[] components = ((Component)this).GetComponents<AudioSource>();
		Audio = null;
		for (int i = 0; i < components.Length; i++)
		{
			if ((Object)(object)components[i] != (Object)null && (Object)(object)components[i] != (Object)(object)Boombox.boomboxAudio && ((Object)components[i]).name == "BoomBoxOverhaulAudio")
			{
				Audio = components[i];
				break;
			}
		}
		if ((Object)(object)Audio == (Object)null)
		{
			Audio = ((Component)this).gameObject.AddComponent<AudioSource>();
			((Object)Audio).name = "BoomBoxOverhaulAudio";
		}
		Audio.playOnAwake = false;
		Audio.loop = false;
		Audio.spatialBlend = 1f;
		Audio.rolloffMode = (AudioRolloffMode)1;
		Audio.maxDistance = 30f;
		localVolume = Mathf.Clamp(Plugin.DefaultVolume.Value, 0f, 2f);
		ApplyLocalVolume();
		UpdateTooltip();
	}

	private void Update()
	{
		HandleInput();
		bool flag = false;
		tooltipScrollTimer += Time.deltaTime;
		if (tooltipScrollTimer >= 0.25f)
		{
			tooltipScrollTimer = 0f;
			tooltipScrollIndex++;
			flag = true;
		}
		if (uiOpen && !isPlayingCustom)
		{
			StopVanillaBoomboxAudio();
		}
		UpdateTooltip();
		if (flag)
		{
			RefreshHeldItemTooltip();
		}
		if (uiOpen)
		{
			UpdateCameraLock();
		}
		else if (cameraLocked)
		{
			SetCameraLocked(locked: false);
		}
		if (isPlayingCustom && (Object)(object)Audio != (Object)null && !Audio.isPlaying && !isPreparing)
		{
			OnTrackEndedLocal();
		}
	}

	private void HandleInput()
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		if (IsConfiguredKeyPressed(Plugin.OpenUiKey.Value) && IsHeldByLocalPlayer())
		{
			uiOpen = !uiOpen;
			if (uiOpen)
			{
				StopAllBoomboxAudioForUi();
			}
			SetCameraLocked(uiOpen);
			Plugin.Log("Toggled boombox UI: " + (uiOpen ? "Open" : "Closed"));
		}
		if (IsConfiguredKeyPressed(Plugin.VolumeUpKey.Value) && IsRelevantToLocalPlayer())
		{
			float volume = Mathf.Clamp(localVolume + Plugin.VolumeStep.Value, 0f, 2f);
			if (Plugin.UseLocalVolumeOnly())
			{
				localVolume = volume;
				ApplyLocalVolume();
				Plugin.Log("Applied local-only volume up: " + localVolume);
			}
			else if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
			{
				Plugin.Log("Sending shared volume up request: " + volume);
				BoomBoxOverhaulNet.SendSetVolume(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, volume);
			}
			else
			{
				localVolume = volume;
				ApplyLocalVolume();
				Plugin.Warn("NetworkObject missing, fell back to local volume up.");
			}
		}
		if (IsConfiguredKeyPressed(Plugin.VolumeDownKey.Value) && IsRelevantToLocalPlayer())
		{
			float volume2 = Mathf.Clamp(localVolume - Plugin.VolumeStep.Value, 0f, 2f);
			if (Plugin.UseLocalVolumeOnly())
			{
				localVolume = volume2;
				ApplyLocalVolume();
				Plugin.Log("Applied local-only volume down: " + localVolume);
			}
			else if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
			{
				Plugin.Log("Sending shared volume down request: " + volume2);
				BoomBoxOverhaulNet.SendSetVolume(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, volume2);
			}
			else
			{
				localVolume = volume2;
				ApplyLocalVolume();
				Plugin.Warn("NetworkObject missing, fell back to local volume down.");
			}
		}
	}

	private bool IsConfiguredKeyPressed(KeyCode keyCode)
	{
		//IL_0017: 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_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0020: Invalid comparison between Unknown and I4
		//IL_0038: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Invalid comparison between Unknown and I4
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0024: Invalid comparison between Unknown and I4
		//IL_0042: 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_01b7: Expected I4, but got Unknown
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_002e: Invalid comparison between Unknown and I4
		//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bf: Invalid comparison between Unknown and I4
		Keyboard current = Keyboard.current;
		if (current == null)
		{
			return false;
		}
		if ((int)keyCode <= 9)
		{
			if ((int)keyCode == 8)
			{
				return ((ButtonControl)current.backspaceKey).wasPressedThisFrame;
			}
			if ((int)keyCode == 9)
			{
				return ((ButtonControl)current.tabKey).wasPressedThisFrame;
			}
		}
		else
		{
			if ((int)keyCode == 13)
			{
				return ((ButtonControl)current.enterKey).wasPressedThisFrame;
			}
			switch (keyCode - 32)
			{
			default:
				if ((int)keyCode != 271)
				{
					break;
				}
				return ((ButtonControl)current.numpadEnterKey).wasPressedThisFrame;
			case 65:
				return ((ButtonControl)current.aKey).wasPressedThisFrame;
			case 66:
				return ((ButtonControl)current.bKey).wasPressedThisFrame;
			case 67:
				return ((ButtonControl)current.cKey).wasPressedThisFrame;
			case 68:
				return ((ButtonControl)current.dKey).wasPressedThisFrame;
			case 69:
				return ((ButtonControl)current.eKey).wasPressedThisFrame;
			case 70:
				return ((ButtonControl)current.fKey).wasPressedThisFrame;
			case 71:
				return ((ButtonControl)current.gKey).wasPressedThisFrame;
			case 72:
				return ((ButtonControl)current.hKey).wasPressedThisFrame;
			case 73:
				return ((ButtonControl)current.iKey).wasPressedThisFrame;
			case 74:
				return ((ButtonControl)current.jKey).wasPressedThisFrame;
			case 75:
				return ((ButtonControl)current.kKey).wasPressedThisFrame;
			case 76:
				return ((ButtonControl)current.lKey).wasPressedThisFrame;
			case 77:
				return ((ButtonControl)current.mKey).wasPressedThisFrame;
			case 78:
				return ((ButtonControl)current.nKey).wasPressedThisFrame;
			case 79:
				return ((ButtonControl)current.oKey).wasPressedThisFrame;
			case 80:
				return ((ButtonControl)current.pKey).wasPressedThisFrame;
			case 81:
				return ((ButtonControl)current.qKey).wasPressedThisFrame;
			case 82:
				return ((ButtonControl)current.rKey).wasPressedThisFrame;
			case 83:
				return ((ButtonControl)current.sKey).wasPressedThisFrame;
			case 84:
				return ((ButtonControl)current.tKey).wasPressedThisFrame;
			case 85:
				return ((ButtonControl)current.uKey).wasPressedThisFrame;
			case 86:
				return ((ButtonControl)current.vKey).wasPressedThisFrame;
			case 87:
				return ((ButtonControl)current.wKey).wasPressedThisFrame;
			case 88:
				return ((ButtonControl)current.xKey).wasPressedThisFrame;
			case 89:
				return ((ButtonControl)current.yKey).wasPressedThisFrame;
			case 90:
				return ((ButtonControl)current.zKey).wasPressedThisFrame;
			case 16:
				return ((ButtonControl)current.digit0Key).wasPressedThisFrame;
			case 17:
				return ((ButtonControl)current.digit1Key).wasPressedThisFrame;
			case 18:
				return ((ButtonControl)current.digit2Key).wasPressedThisFrame;
			case 19:
				return ((ButtonControl)current.digit3Key).wasPressedThisFrame;
			case 20:
				return ((ButtonControl)current.digit4Key).wasPressedThisFrame;
			case 21:
				return ((ButtonControl)current.digit5Key).wasPressedThisFrame;
			case 22:
				return ((ButtonControl)current.digit6Key).wasPressedThisFrame;
			case 23:
				return ((ButtonControl)current.digit7Key).wasPressedThisFrame;
			case 24:
				return ((ButtonControl)current.digit8Key).wasPressedThisFrame;
			case 25:
				return ((ButtonControl)current.digit9Key).wasPressedThisFrame;
			case 13:
				return ((ButtonControl)current.minusKey).wasPressedThisFrame;
			case 29:
				return ((ButtonControl)current.equalsKey).wasPressedThisFrame;
			case 0:
				return ((ButtonControl)current.spaceKey).wasPressedThisFrame;
			case 59:
				return ((ButtonControl)current.leftBracketKey).wasPressedThisFrame;
			case 61:
				return ((ButtonControl)current.rightBracketKey).wasPressedThisFrame;
			case 27:
				return ((ButtonControl)current.semicolonKey).wasPressedThisFrame;
			case 7:
				return ((ButtonControl)current.quoteKey).wasPressedThisFrame;
			case 12:
				return ((ButtonControl)current.commaKey).wasPressedThisFrame;
			case 14:
				return ((ButtonControl)current.periodKey).wasPressedThisFrame;
			case 15:
				return ((ButtonControl)current.slashKey).wasPressedThisFrame;
			case 60:
				return ((ButtonControl)current.backslashKey).wasPressedThisFrame;
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
			case 6:
			case 8:
			case 9:
			case 10:
			case 11:
			case 26:
			case 28:
			case 30:
			case 31:
			case 32:
			case 33:
			case 34:
			case 35:
			case 36:
			case 37:
			case 38:
			case 39:
			case 40:
			case 41:
			case 42:
			case 43:
			case 44:
			case 45:
			case 46:
			case 47:
			case 48:
			case 49:
			case 50:
			case 51:
			case 52:
			case 53:
			case 54:
			case 55:
			case 56:
			case 57:
			case 58:
			case 62:
			case 63:
			case 64:
				break;
			}
		}
		return false;
	}

	private void OnGUI()
	{
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: 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_00fb: 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_0186: Unknown result type (might be due to invalid IL or missing references)
		//IL_028f: Unknown result type (might be due to invalid IL or missing references)
		if (!uiOpen || !IsHeldByLocalPlayer())
		{
			if (cameraLocked)
			{
				SetCameraLocked(locked: false);
			}
			return;
		}
		Cursor.lockState = (CursorLockMode)0;
		Cursor.visible = true;
		GUI.Box(new Rect(20f, 20f, 520f, 180f), "BoomBoxOverhaulV2 By Henreh :D");
		GUI.Label(new Rect(35f, 50f, 460f, 20f), "Paste YouTube video or playlist URL:");
		pendingUrl = GUI.TextField(new Rect(35f, 72f, 470f, 22f), pendingUrl, 1000);
		GUI.Label(new Rect(35f, 97f, 470f, 20f), "State: " + statusText);
		GUI.Label(new Rect(35f, 115f, 470f, 20f), "Dependencies: " + DependencyBootstrapper.GetStatus());
		GUI.Label(new Rect(35f, 133f, 470f, 20f), "Volume: " + Mathf.RoundToInt(localVolume * 100f) + "%");
		bool enabled = GUI.enabled;
		GUI.enabled = DependencyBootstrapper.GetState().IsReady();
		if (GUI.Button(new Rect(35f, 155f, 80f, 20f), "Play"))
		{
			Plugin.Log("Play button pressed.");
			if (string.IsNullOrEmpty(pendingUrl))
			{
				Plugin.Warn("Play failed: URL empty");
			}
			else if ((Object)(object)Boombox == (Object)null)
			{
				Plugin.Warn("Play failed: Boombox null");
			}
			else if ((Object)(object)((NetworkBehaviour)Boombox).NetworkObject == (Object)null)
			{
				Plugin.Warn("Play failed: Boombox.NetworkObject null");
			}
			else
			{
				Plugin.Log("Sending play request → NetworkObjectId=" + ((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId + " URL=" + pendingUrl.Trim());
				BoomBoxOverhaulNet.SendRequestPlay(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, pendingUrl.Trim());
			}
		}
		GUI.enabled = enabled;
		if (GUI.Button(new Rect(125f, 155f, 80f, 20f), "Stop"))
		{
			Plugin.Log("Stop button pressed.");
			if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
			{
				Plugin.Log("Sending stop request → NetworkObjectId=" + ((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId);
				BoomBoxOverhaulNet.SendRequestStop(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId);
			}
			else
			{
				Plugin.Warn("Stop failed: Boombox or NetworkObject missing");
			}
		}
	}

	private void ApplyLocalVolume()
	{
		if ((Object)(object)Audio != (Object)null)
		{
			Audio.volume = localVolume;
		}
		UpdateTooltip();
		RefreshHeldItemTooltip();
	}

	private void RefreshHeldItemTooltip()
	{
		try
		{
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val.currentlyHeldObjectServer == (Object)null) && (Object)(object)val.currentlyHeldObjectServer == (Object)(object)Boombox)
			{
				val.currentlyHeldObjectServer.EquipItem();
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Failed to refresh held item tooltip: " + ex);
		}
	}

	private void StopVanillaBoomboxAudio()
	{
		try
		{
			if (!((Object)(object)Boombox == (Object)null))
			{
				if ((Object)(object)Boombox.boomboxAudio != (Object)null && (Object)(object)Boombox.boomboxAudio != (Object)(object)Audio)
				{
					Boombox.boomboxAudio.Stop();
				}
				Boombox.isPlayingMusic = false;
			}
		}
		catch (Exception ex)
		{
			Plugin.Warn("Failed to stop vanilla boombox audio: " + ex);
		}
	}

	private void StopAllBoomboxAudioForUi()
	{
		StopVanillaBoomboxAudio();
		if ((Object)(object)Audio != (Object)null && (Object)(object)Audio != (Object)(object)Boombox.boomboxAudio && !isPlayingCustom)
		{
			Audio.Stop();
		}
	}

	private void SetCameraLocked(bool locked)
	{
		cameraLocked = locked;
		try
		{
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			if ((Object)(object)val != (Object)null && val.playerActions != null)
			{
				if (locked)
				{
					val.playerActions.Disable();
				}
				else
				{
					val.playerActions.Enable();
				}
			}
			Cursor.lockState = (CursorLockMode)((!locked) ? 1 : 0);
			Cursor.visible = locked;
		}
		catch (Exception ex)
		{
			Plugin.Warn("SetCameraLocked failed: " + ex);
		}
	}

	private void UpdateCameraLock()
	{
		if (!uiOpen)
		{
			if (cameraLocked)
			{
				SetCameraLocked(locked: false);
			}
			return;
		}
		if (!IsHeldByLocalPlayer())
		{
			uiOpen = false;
			SetCameraLocked(locked: false);
			return;
		}
		try
		{
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			if ((Object)(object)val != (Object)null && val.playerActions != null)
			{
				val.playerActions.Disable();
			}
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
		}
		catch (Exception ex)
		{
			Plugin.Warn("Camera lock failed: " + ex);
		}
	}

	private string GetScrollingTrackText()
	{
		string text = "None";
		if ((Object)(object)Audio != (Object)null && (Object)(object)Audio.clip != (Object)null && !string.IsNullOrEmpty(((Object)Audio.clip).name))
		{
			text = ((Object)Audio.clip).name;
		}
		else if (!string.IsNullOrEmpty(currentVideoId))
		{
			text = currentVideoId;
		}
		if (string.IsNullOrEmpty(text))
		{
			return "None";
		}
		if (text.Length <= 28)
		{
			return text;
		}
		string text2 = text + "   ";
		string text3 = text2 + text2;
		if (tooltipScrollIndex >= text2.Length)
		{
			tooltipScrollIndex = 0;
		}
		return text3.Substring(tooltipScrollIndex, 28);
	}

	private void UpdateTooltip()
	{
		//IL_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: 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)
		if (!((Object)(object)Boombox == (Object)null) && !((Object)(object)((GrabbableObject)Boombox).itemProperties == (Object)null))
		{
			string scrollingTrackText = GetScrollingTrackText();
			Item itemProperties = ((GrabbableObject)Boombox).itemProperties;
			string[] array = new string[5];
			KeyCode value = Plugin.OpenUiKey.Value;
			array[0] = "[" + ((object)(KeyCode)(ref value)).ToString() + "] URL Menu";
			string[] obj = new string[7] { "[", null, null, null, null, null, null };
			value = Plugin.VolumeDownKey.Value;
			obj[1] = ((object)(KeyCode)(ref value)).ToString();
			obj[2] = "/";
			value = Plugin.VolumeUpKey.Value;
			obj[3] = ((object)(KeyCode)(ref value)).ToString();
			obj[4] = "] Volume: ";
			obj[5] = Mathf.RoundToInt(localVolume * 100f).ToString();
			obj[6] = "%";
			array[1] = string.Concat(obj);
			array[2] = "Track: " + scrollingTrackText;
			array[3] = "State: " + statusText;
			array[4] = "Deps: " + DependencyBootstrapper.GetStatus();
			itemProperties.toolTips = array;
		}
	}

	private bool IsHeldByLocalPlayer()
	{
		try
		{
			return (Object)(object)Boombox != (Object)null && (Object)(object)((GrabbableObject)Boombox).playerHeldBy != (Object)null && (Object)(object)GameNetworkManager.Instance != (Object)null && (Object)(object)GameNetworkManager.Instance.localPlayerController != (Object)null && (Object)(object)((GrabbableObject)Boombox).playerHeldBy == (Object)(object)GameNetworkManager.Instance.localPlayerController;
		}
		catch
		{
			return false;
		}
	}

	private bool IsRelevantToLocalPlayer()
	{
		//IL_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		if (IsHeldByLocalPlayer())
		{
			return true;
		}
		try
		{
			PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			return Vector3.Distance(((Component)val).transform.position, ((Component)this).transform.position) <= 15f;
		}
		catch
		{
			return false;
		}
	}

	private void PopulateExpectedClients()
	{
		expectedClients.Clear();
		if ((Object)(object)NetworkManager.Singleton == (Object)null || NetworkManager.Singleton.ConnectedClients == null)
		{
			return;
		}
		foreach (KeyValuePair<ulong, NetworkClient> connectedClient in NetworkManager.Singleton.ConnectedClients)
		{
			expectedClients.Add(connectedClient.Key);
		}
		Plugin.Log("Expected clients for boombox playback: " + expectedClients.Count);
	}

	public void ServerHandlePlay(ulong requesterClientId, string url)
	{
		Plugin.Log("ServerHandlePlay called from client " + requesterClientId + " URL=" + url);
		if (!DependencyBootstrapper.GetState().IsReady())
		{
			Plugin.Warn("ServerHandlePlay rejected: dependencies not ready");
			if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
			{
				BoomBoxOverhaulNet.SendRejectPlay(requesterClientId, ((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, "Dependencies not ready");
			}
			return;
		}
		if (!UrlHelpers.IsLikelyYoutubeUrl(url))
		{
			Plugin.Warn("ServerHandlePlay rejected: invalid URL");
			if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
			{
				BoomBoxOverhaulNet.SendRejectPlay(requesterClientId, ((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, "Invalid URL");
			}
			return;
		}
		currentVideoId = "";
		isPreparing = true;
		isPlayingCustom = false;
		statusText = "Resolving...";
		readyClients.Clear();
		expectedClients.Clear();
		failedClients.Clear();
		playlist = new PlaylistState();
		tooltipScrollIndex = 0;
		tooltipScrollTimer = 0f;
		StopVanillaBoomboxAudio();
		if (UrlHelpers.IsPlaylistOnlyUrl(url))
		{
			if (!YtDlpBridge.ResolvePlaylistIds(url, out var ids) || ids.Count == 0)
			{
				Plugin.Warn("Playlist resolve failed");
				if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
				{
					BoomBoxOverhaulNet.SendRejectPlay(requesterClientId, ((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, "Playlist resolve failed");
				}
				isPreparing = false;
				return;
			}
			playlist.VideoIds.AddRange(ids);
			playlist.Index = 0;
			currentVideoId = playlist.GetCurrentId() ?? "";
		}
		else
		{
			string text = UrlHelpers.TryExtractVideoId(url);
			if (string.IsNullOrEmpty(text))
			{
				Plugin.Warn("Could not parse video id");
				if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
				{
					BoomBoxOverhaulNet.SendRejectPlay(requesterClientId, ((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, "Could not parse video id");
				}
				isPreparing = false;
				return;
			}
			playlist.VideoIds.Add(text);
			playlist.Index = 0;
			currentVideoId = text;
		}
		if ((Object)(object)Boombox == (Object)null || (Object)(object)((NetworkBehaviour)Boombox).NetworkObject == (Object)null)
		{
			Plugin.Warn("ServerHandlePlay aborted: Boombox or NetworkObject missing");
			isPreparing = false;
			return;
		}
		PopulateExpectedClients();
		string canonicalUrl = "https://www.youtube.com/watch?v=" + currentVideoId;
		Plugin.Log("Broadcasting prepare track for " + currentVideoId);
		BoomBoxOverhaulNet.BroadcastPrepareTrack(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, canonicalUrl, currentVideoId, playlist.Index, playlist.VideoIds.ToArray());
		if (serverWaitRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(serverWaitRoutine);
		}
		serverWaitRoutine = ((MonoBehaviour)this).StartCoroutine(ServerWaitForReadyThenPlay());
	}

	public void ClientRejectPlay(string reason)
	{
		Plugin.Warn("ClientRejectPlay: " + reason);
		statusText = reason;
		isPreparing = false;
	}

	public void ClientPrepareTrack(string canonicalUrl, string videoId, int playlistIndex, string[] playlistIds)
	{
		Plugin.Log("ClientPrepareTrack START → " + videoId);
		currentVideoId = videoId;
		playlist.VideoIds.Clear();
		playlist.VideoIds.AddRange(playlistIds);
		playlist.Index = playlistIndex;
		statusText = "Preparing...";
		isPreparing = true;
		isPlayingCustom = false;
		tooltipScrollIndex = 0;
		tooltipScrollTimer = 0f;
		StopVanillaBoomboxAudio();
		if (localLoadRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(localLoadRoutine);
		}
		localLoadRoutine = ((MonoBehaviour)this).StartCoroutine(PrepareTrackLocalCoroutine(canonicalUrl, videoId));
	}

	[IteratorStateMachine(typeof(<PrepareTrackLocalCoroutine>d__38))]
	private IEnumerator PrepareTrackLocalCoroutine(string canonicalUrl, string videoId)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <PrepareTrackLocalCoroutine>d__38(0)
		{
			<>4__this = this,
			canonicalUrl = canonicalUrl,
			videoId = videoId
		};
	}

	public void ServerNotifyReady(ulong clientId, bool success)
	{
		if (success)
		{
			readyClients.Add(clientId);
			failedClients.Remove(clientId);
			Plugin.Log("Client ready for boombox playback: " + clientId);
		}
		else
		{
			failedClients.Add(clientId);
			readyClients.Remove(clientId);
			Plugin.Warn("Client failed to prepare boombox playback: " + clientId);
		}
	}

	[IteratorStateMachine(typeof(<ServerWaitForReadyThenPlay>d__40))]
	private IEnumerator ServerWaitForReadyThenPlay()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <ServerWaitForReadyThenPlay>d__40(0)
		{
			<>4__this = this
		};
	}

	public void ClientBeginPlayback(string videoId)
	{
		Plugin.Log("ClientBeginPlayback → " + videoId);
		currentVideoId = videoId;
		isPreparing = false;
		isPlayingCustom = true;
		statusText = "Playing";
		tooltipScrollIndex = 0;
		tooltipScrollTimer = 0f;
		if ((Object)(object)Audio != (Object)null && (Object)(object)Audio.clip != (Object)null)
		{
			suppressVanillaStopOnce = true;
			StopVanillaBoomboxAudio();
			Audio.Stop();
			Audio.time = 0f;
			Audio.Play();
		}
		else
		{
			Plugin.Warn("ClientBeginPlayback received but Audio or Audio.clip was null");
		}
	}

	public void ServerHandleStop()
	{
		Plugin.Log("ServerHandleStop called");
		if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
		{
			BoomBoxOverhaulNet.BroadcastStopPlayback(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId);
		}
	}

	public void ClientStopPlayback()
	{
		Plugin.Log("ClientStopPlayback called");
		isPreparing = false;
		isPlayingCustom = false;
		statusText = "Stopped";
		if (localLoadRoutine != null)
		{
			((MonoBehaviour)this).StopCoroutine(localLoadRoutine);
			localLoadRoutine = null;
		}
		if ((Object)(object)Audio != (Object)null)
		{
			Audio.Stop();
		}
	}

	private void OnTrackEndedLocal()
	{
		if (!Plugin.AutoplayPlaylist.Value || (Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsServer)
		{
			isPlayingCustom = false;
			statusText = "Finished";
			return;
		}
		if (playlist.VideoIds.Count <= 1)
		{
			isPlayingCustom = false;
			statusText = "Finished";
			return;
		}
		string value = playlist.Advance();
		if (string.IsNullOrEmpty(value))
		{
			isPlayingCustom = false;
			statusText = "Playlist ended";
			return;
		}
		currentVideoId = value;
		statusText = "Next track...";
		readyClients.Clear();
		expectedClients.Clear();
		failedClients.Clear();
		tooltipScrollIndex = 0;
		tooltipScrollTimer = 0f;
		if (!((Object)(object)Boombox == (Object)null) && !((Object)(object)((NetworkBehaviour)Boombox).NetworkObject == (Object)null))
		{
			StopVanillaBoomboxAudio();
			PopulateExpectedClients();
			string canonicalUrl = "https://www.youtube.com/watch?v=" + currentVideoId;
			Plugin.Log("Broadcasting next prepare track for " + currentVideoId);
			BoomBoxOverhaulNet.BroadcastPrepareTrack(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, canonicalUrl, currentVideoId, playlist.Index, playlist.VideoIds.ToArray());
			if (serverWaitRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(serverWaitRoutine);
			}
			serverWaitRoutine = ((MonoBehaviour)this).StartCoroutine(ServerWaitForReadyThenPlay());
		}
	}

	[IteratorStateMachine(typeof(<RunBlockingTask>d__45))]
	private IEnumerator RunBlockingTask(Action action)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <RunBlockingTask>d__45(0)
		{
			<>4__this = this,
			action = action
		};
	}

	public bool ShouldSuppressVanillaStop()
	{
		if (!Plugin.KeepPlayingPocketed.Value)
		{
			return false;
		}
		if (suppressVanillaStopOnce)
		{
			suppressVanillaStopOnce = false;
			return true;
		}
		return isPlayingCustom;
	}

	public void ServerHandleSetVolume(float volume)
	{
		if (Plugin.LocalVolumeOnly.Value)
		{
			Plugin.Log("Server rejected shared volume change because LocalVolumeOnly is enabled on host.");
			return;
		}
		float volume2 = Mathf.Clamp(volume, 0f, 2f);
		if ((Object)(object)Boombox != (Object)null && (Object)(object)((NetworkBehaviour)Boombox).NetworkObject != (Object)null)
		{
			Plugin.Log("Broadcasting shared volume: " + volume2);
			BoomBoxOverhaulNet.BroadcastApplyVolume(((NetworkBehaviour)Boombox).NetworkObject.NetworkObjectId, volume2);
		}
	}

	public void ClientApplyNetworkVolume(float volume)
	{
		Plugin.Log("ClientApplyNetworkVolume → " + volume);
		localVolume = Mathf.Clamp(volume, 0f, 2f);
		ApplyLocalVolume();
	}
}
internal static class UrlHelpers
{
	private static readonly Regex WatchRegex = new Regex("(?:youtube\\.com\\/watch\\?[^#\\s]*\\bv=)([A-Za-z0-9_-]{6,})", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private static readonly Regex ShortRegex = new Regex("(?:youtu\\.be\\/)([A-Za-z0-9_-]{6,})", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	private static readonly Regex PlaylistRegex = new Regex("(?:[?&]list=)([A-Za-z0-9_-]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled);

	public static bool IsLikelyYoutubeUrl(string url)
	{
		if (string.IsNullOrEmpty(url))
		{
			return false;
		}
		return url.IndexOf("youtube.com/", StringComparison.OrdinalIgnoreCase) >= 0 || url.IndexOf("youtu.be/", StringComparison.OrdinalIgnoreCase) >= 0;
	}

	public static string TryExtractVideoId(string url)
	{
		if (string.IsNullOrEmpty(url))
		{
			return null;
		}
		Match match = WatchRegex.Match(url);
		if (match.Success)
		{
			return match.Groups[1].Value;
		}
		Match match2 = ShortRegex.Match(url);
		if (match2.Success)
		{
			return match2.Groups[1].Value;
		}
		return null;
	}

	public static string TryExtractPlaylistId(string url)
	{
		if (string.IsNullOrEmpty(url))
		{
			return null;
		}
		Match match = PlaylistRegex.Match(url);
		if (match.Success)
		{
			return match.Groups[1].Value;
		}
		return null;
	}

	public static bool IsPlaylistOnlyUrl(string url)
	{
		string value = TryExtractPlaylistId(url);
		string value2 = TryExtractVideoId(url);
		return !string.IsNullOrEmpty(value) && string.IsNullOrEmpty(value2);
	}
}
internal static class YtDlpBridge
{
	public static bool ResolvePlaylistIds(string url, out List<string> ids)
	{
		ids = new List<string>();
		try
		{
			DependencyState state = DependencyBootstrapper.GetState();
			if (!state.IsReady() || string.IsNullOrEmpty(state.YtDlpPath))
			{
				return false;
			}
			string args = "--flat-playlist --print \"%(id)s\" \"" + EscapeArg(url) + "\"";
			if (!RunProcess(state.YtDlpPath, args, out var stdout, out var stderr, 120000))
			{
				Plugin.Warn("yt-dlp playlist resolve failed: " + stderr);
				return false;
			}
			using (StringReader stringReader = new StringReader(stdout))
			{
				while (true)
				{
					string text = stringReader.ReadLine();
					if (text == null)
					{
						break;
					}
					text = text.Trim();
					if (text.Length > 0)
					{
						ids.Add(text);
					}
				}
			}
			if (Plugin.ShufflePlaylist.Value && ids.Count > 1)
			{
				Random random = new Random();
				for (int num = ids.Count - 1; num > 0; num--)
				{
					int index = random.Next(num + 1);
					string value = ids[num];
					ids[num] = ids[index];
					ids[index] = value;
				}
			}
			return ids.Count > 0;
		}
		catch (Exception ex)
		{
			Plugin.Error("ResolvePlaylistIds exception: " + ex);
			return false;
		}
	}

	public static bool FetchTrack(string sourceUrl, string videoId, out TrackInfo info)
	{
		info = new TrackInfo();
		info.videoId = videoId;
		info.sourceUrl = sourceUrl;
		info.cachePath = CacheManager.BuildAudioPath(videoId);
		try
		{
			DependencyState state = DependencyBootstrapper.GetState();
			if (!state.IsReady() || string.IsNullOrEmpty(state.YtDlpPath) || string.IsNullOrEmpty(state.FfmpegPath))
			{
				return false;
			}
			if (File.Exists(info.cachePath))
			{
				CacheManager.Touch(info.cachePath);
				info.title = CacheManager.ReadMeta(videoId);
				return true;
			}
			string text = CacheManager.BuildTrackBasePath(videoId) + "_tmp";
			string text2 = text + ".mp3";
			if (File.Exists(text2))
			{
				try
				{
					File.Delete(text2);
				}
				catch
				{
				}
			}
			string args = "--no-playlist --print \"%(title)s|||%(duration)s|||%(id)s\" \"" + EscapeArg(sourceUrl) + "\"";
			if (RunProcess(state.YtDlpPath, args, out var stdout, out var _, 120000))
			{
				string[] array = stdout.Trim().Split(new string[1] { "|||" }, StringSplitOptions.None);
				if (array.Length >= 3)
				{
					info.title = array[0];
					if (float.TryParse(array[1], out var result))
					{
						info.durationSeconds = result;
					}
				}
			}
			if (Plugin.MaxTrackSeconds.Value > 0 && info.durationSeconds > (float)Plugin.MaxTrackSeconds.Value)
			{
				return false;
			}
			string value = Path.GetDirectoryName(state.FfmpegPath) ?? Plugin.ToolsFolder;
			string args2 = "--no-playlist --extract-audio --audio-format mp3 --audio-quality 0 --ffmpeg-location \"" + EscapeArg(value) + "\" --output \"" + EscapeArg(text) + ".%(ext)s\" \"" + EscapeArg(sourceUrl) + "\"";
			if (!RunProcess(state.YtDlpPath, args2, out var _, out var stderr2, 900000))
			{
				Plugin.Warn("Download failed: " + stderr2);
				return false;
			}
			if (!File.Exists(text2))
			{
				return false;
			}
			if (File.Exists(info.cachePath))
			{
				File.Delete(info.cachePath);
			}
			File.Move(text2, info.cachePath);
			CacheManager.Touch(info.cachePath);
			CacheManager.WriteMeta(videoId, info.title);
			CacheManager.Prune();
			return true;
		}
		catch (Exception ex)
		{
			Plugin.Error("FetchTrack exception: " + ex);
			return false;
		}
	}

	private static bool RunProcess(string exePath, string args, out string stdout, out string stderr, int timeoutMs)
	{
		stdout = "";
		stderr = "";
		try
		{
			using Process process = new Process();
			process.StartInfo = new ProcessStartInfo
			{
				FileName = exePath,
				Arguments = args,
				WorkingDirectory = Plugin.ToolsFolder,
				UseShellExecute = false,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				CreateNoWindow = true,
				StandardOutputEncoding = Encoding.UTF8,
				StandardErrorEncoding = Encoding.UTF8
			};
			process.Start();
			stdout = process.StandardOutput.ReadToEnd();
			stderr = process.StandardError.ReadToEnd();
			if (!process.WaitForExit(timeoutMs))
			{
				try
				{
					process.Kill();
				}
				catch
				{
				}
				stderr += "\nProcess timed out.";
				return false;
			}
			return process.ExitCode == 0;
		}
		catch (Exception ex)
		{
			stderr = ex.ToString();
			return false;
		}
	}

	private static string EscapeArg(string value)
	{
		return value.Replace("\"", "\\\"");
	}
}