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("\"", "\\\"");
}
}