Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BoomBoxOverhaul v2.0.2
BepInEx/plugins/BoomBoxOverhaulV2.dll
Decompiled a month agousing 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("\"", "\\\""); } }