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.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Logging;
using BoomBoxCartMod.Patches;
using BoomBoxCartMod.Util;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
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: AssemblyTitle("BoomBoxCartMod")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BoomBoxCartMod")]
[assembly: AssemblyCopyright("Copyright © 2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("37e852e0-b511-4315-8182-68c0a54e1ba9")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
internal sealed class RefSafetyRulesAttribute : Attribute
{
public readonly int Version;
public RefSafetyRulesAttribute(int P_0)
{
Version = P_0;
}
}
}
namespace BoomBoxCartMod
{
public class Boombox : MonoBehaviourPunCallbacks
{
[CompilerGenerated]
private sealed class <DownloadTimeoutCoroutine>d__35 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
public string requestId;
public string url;
public Boombox <>4__this;
private Player[] <>s__1;
private int <>s__2;
private Player <player>5__3;
private string <timeoutMessage>5__4;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <DownloadTimeoutCoroutine>d__35(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>s__1 = null;
<player>5__3 = null;
<timeoutMessage>5__4 = null;
<>1__state = -2;
}
private bool MoveNext()
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_0030: Expected O, but got Unknown
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = (object)new WaitForSeconds(40f);
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
if (currentRequestId == requestId && isDownloadInProgress)
{
Logger.LogWarning((object)("Download timeout for url: " + url));
if (PhotonNetwork.IsMasterClient)
{
Logger.LogInfo((object)"Master client initiating timeout recovery");
<>4__this.isTimeoutRecovery = true;
<>s__1 = PhotonNetwork.PlayerList;
for (<>s__2 = 0; <>s__2 < <>s__1.Length; <>s__2++)
{
<player>5__3 = <>s__1[<>s__2];
if (!downloadsReady[url].Contains(<player>5__3.ActorNumber))
{
if (!downloadErrors.ContainsKey(url))
{
downloadErrors[url] = new HashSet<int>();
}
downloadErrors[url].Add(<player>5__3.ActorNumber);
Logger.LogWarning((object)$"Player {<player>5__3.ActorNumber} timed out during download");
}
<player>5__3 = null;
}
<>s__1 = null;
if (downloadsReady.ContainsKey(url) && downloadsReady[url].Count > 0)
{
<timeoutMessage>5__4 = $"Some players timed out. Continuing playback for {downloadsReady[url].Count} players.";
<>4__this.photonView.RPC("NotifyPlayersOfErrors", (RpcTarget)0, new object[1] { <timeoutMessage>5__4 });
<>4__this.photonView.RPC("SyncPlayback", (RpcTarget)0, new object[2]
{
url,
PhotonNetwork.LocalPlayer.ActorNumber
});
<timeoutMessage>5__4 = null;
}
else
{
<>4__this.photonView.RPC("NotifyPlayersOfErrors", (RpcTarget)0, new object[1] { "Download timed out for all players." });
}
isDownloadInProgress = false;
currentDownloadUrl = "";
currentRequestId = "";
<>4__this.isTimeoutRecovery = false;
}
}
<>4__this.timeoutCoroutines.Remove(requestId);
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 BoomBoxCartMod Instance = BoomBoxCartMod.instance;
public PhotonView photonView;
public AudioSource audioSource;
public float maxVolumeLimit = 0.8f;
private float minDistance = 3f;
private float maxDistanceBase = 10f;
private float maxDistanceAddition = 20f;
private static bool isDownloadInProgress = false;
private static string currentDownloadUrl = "";
private static string currentRequestId = "";
public string currentSongUrl = "";
public string currentSongTitle = "No song playing";
private bool isAwaitingSyncPlayback = false;
public bool isPlaying = false;
private bool isTimeoutRecovery = false;
private AudioLowPassFilter lowPassFilter;
public int qualityLevel = 3;
private static Dictionary<string, AudioClip> downloadedClips = new Dictionary<string, AudioClip>();
private static Dictionary<string, string> songTitles = new Dictionary<string, string>();
private static Dictionary<string, HashSet<int>> downloadsReady = new Dictionary<string, HashSet<int>>();
private static Dictionary<string, HashSet<int>> downloadErrors = new Dictionary<string, HashSet<int>>();
private const float DOWNLOAD_TIMEOUT = 40f;
private Dictionary<string, Coroutine> timeoutCoroutines = new Dictionary<string, Coroutine>();
private static readonly Regex youtubeUrlRegex = new Regex("^https?:\\/\\/(www\\.)?(youtube\\.com\\/watch\\?v=|youtu\\.be\\/)[a-zA-Z0-9_-]+(\\?|&|$)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static ManualLogSource Logger => Instance.logger;
private void Awake()
{
audioSource = ((Component)this).gameObject.AddComponent<AudioSource>();
audioSource.volume = 0.15f;
audioSource.spatialBlend = 1f;
audioSource.playOnAwake = false;
audioSource.rolloffMode = (AudioRolloffMode)2;
audioSource.spread = 90f;
audioSource.dopplerLevel = 0f;
audioSource.reverbZoneMix = 1f;
audioSource.spatialize = true;
lowPassFilter = ((Component)this).gameObject.AddComponent<AudioLowPassFilter>();
((Behaviour)lowPassFilter).enabled = false;
UpdateAudioRangeBasedOnVolume(audioSource.volume);
photonView = ((Component)this).GetComponent<PhotonView>();
isAwaitingSyncPlayback = false;
if ((Object)(object)photonView == (Object)null)
{
Logger.LogError((object)"PhotonView not found on Boombox object.");
return;
}
if ((Object)(object)((Component)this).GetComponent<BoomboxController>() == (Object)null)
{
((Component)this).gameObject.AddComponent<BoomboxController>();
}
Logger.LogInfo((object)$"Boombox initialized on this cart. AudioSource: {audioSource}, PhotonView: {photonView}");
}
private void Update()
{
if (isAwaitingSyncPlayback && audioSource.isPlaying)
{
audioSource.Stop();
isPlaying = false;
}
}
private void OnDestroy()
{
foreach (Coroutine value in timeoutCoroutines.Values)
{
if (value != null)
{
((MonoBehaviour)this).StopCoroutine(value);
}
}
timeoutCoroutines.Clear();
}
public bool IsDownloadInProgress()
{
return isDownloadInProgress;
}
public string GetCurrentDownloadUrl()
{
return currentDownloadUrl;
}
public static bool IsValidYoutubeUrl(string url)
{
return !string.IsNullOrWhiteSpace(url) && youtubeUrlRegex.IsMatch(url);
}
[PunRPC]
public async void RequestSong(string url, int requesterId)
{
if (!IsValidYoutubeUrl(url))
{
Logger.LogError((object)("Invalid YouTube URL: " + url));
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Error: Invalid YouTube URL.");
}
return;
}
string requestId = Guid.NewGuid().ToString();
if (isDownloadInProgress && requesterId != PhotonNetwork.MasterClient.ActorNumber)
{
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Please wait for the current download to complete.");
}
return;
}
isDownloadInProgress = true;
isAwaitingSyncPlayback = true;
currentDownloadUrl = url;
currentRequestId = requestId;
CleanupCurrentPlayback();
timeoutCoroutines[requestId] = ((MonoBehaviour)this).StartCoroutine(DownloadTimeoutCoroutine(requestId, url));
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Downloading audio from " + url + "...");
}
if (downloadsReady.ContainsKey(url))
{
downloadsReady[url].Clear();
}
else
{
downloadsReady[url] = new HashSet<int>();
}
if (downloadErrors.ContainsKey(url))
{
downloadErrors[url].Clear();
}
else
{
downloadErrors[url] = new HashSet<int>();
}
if (!downloadedClips.ContainsKey(url))
{
try
{
var (filePath, title) = await YoutubeDL.DownloadAudioWithTitleAsync(url);
songTitles[url] = title;
photonView.RPC("SetSongTitle", (RpcTarget)3, new object[2] { url, title });
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Processing audio: " + title);
}
AudioClip clip = await GetAudioClipAsync(filePath);
downloadedClips[url] = clip;
Logger.LogInfo((object)("Downloaded and cached clip for url: " + url));
}
catch (Exception ex2)
{
Exception ex = ex2;
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Error: " + ex.Message);
}
photonView.RPC("ReportDownloadError", (RpcTarget)0, new object[3]
{
PhotonNetwork.LocalPlayer.ActorNumber,
url,
ex.Message
});
isAwaitingSyncPlayback = false;
return;
}
}
photonView.RPC("ReportDownloadComplete", (RpcTarget)0, new object[2]
{
PhotonNetwork.LocalPlayer.ActorNumber,
url
});
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Waiting for all players to be ready...");
}
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber || (PhotonNetwork.IsMasterClient && (requesterId != PhotonNetwork.LocalPlayer.ActorNumber || isTimeoutRecovery)))
{
await WaitForPlayersReadyOrFailed(url);
if (currentRequestId != requestId)
{
Logger.LogWarning((object)("Request ID mismatch. Current: " + currentRequestId + ", This request: " + requestId));
return;
}
if (downloadErrors.ContainsKey(url) && downloadErrors[url].Count > 0)
{
string errorMessage = $"Some players had download errors. Continuing playback for {downloadsReady[url].Count} players.";
Logger.LogWarning((object)errorMessage);
photonView.RPC("NotifyPlayersOfErrors", (RpcTarget)0, new object[1] { errorMessage });
}
Logger.LogInfo((object)$"All players ready for playback. Initiating sync playback for {downloadsReady[url].Count} players.");
photonView.RPC("SyncPlayback", (RpcTarget)0, new object[2] { url, requesterId });
}
if (timeoutCoroutines.TryGetValue(requestId, out var coroutine) && coroutine != null)
{
((MonoBehaviour)this).StopCoroutine(coroutine);
timeoutCoroutines.Remove(requestId);
}
if (currentRequestId == requestId)
{
isDownloadInProgress = false;
currentDownloadUrl = "";
currentRequestId = "";
}
}
[PunRPC]
public void NotifyPlayersOfErrors(string message)
{
Logger.LogWarning((object)message);
UpdateUIStatus(message);
}
[PunRPC]
public void ReportDownloadError(int actorNumber, string url, string errorMessage)
{
Logger.LogError((object)$"Player {actorNumber} reported download error for {url}: {errorMessage}");
if (!downloadErrors.ContainsKey(url))
{
downloadErrors[url] = new HashSet<int>();
}
downloadErrors[url].Add(actorNumber);
if (actorNumber == PhotonNetwork.LocalPlayer.ActorNumber)
{
UpdateUIStatus("Error: " + errorMessage);
}
}
[IteratorStateMachine(typeof(<DownloadTimeoutCoroutine>d__35))]
private IEnumerator DownloadTimeoutCoroutine(string requestId, string url)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <DownloadTimeoutCoroutine>d__35(0)
{
<>4__this = this,
requestId = requestId,
url = url
};
}
[PunRPC]
public void SetSongTitle(string url, string title)
{
songTitles[url] = title;
if (currentSongUrl == url)
{
currentSongTitle = title;
UpdateUIStatus("Now playing: " + title);
}
}
[PunRPC]
public void ReportDownloadComplete(int actorNumber, string url)
{
if (!downloadsReady.ContainsKey(url))
{
downloadsReady[url] = new HashSet<int>();
}
downloadsReady[url].Add(actorNumber);
}
private async Task WaitForPlayersReadyOrFailed(string url)
{
int totalPlayers = PhotonNetwork.PlayerList.Length;
int readyCount;
int errorCount;
while (true)
{
readyCount = (downloadsReady.ContainsKey(url) ? downloadsReady[url].Count : 0);
errorCount = (downloadErrors.ContainsKey(url) ? downloadErrors[url].Count : 0);
bool flag = readyCount + errorCount >= totalPlayers;
bool flag2 = flag;
if (!flag2)
{
bool flag3 = readyCount > 0 && errorCount > 0;
bool flag4 = flag3;
if (flag4)
{
flag4 = await Task.Delay(5000).ContinueWith((Task _) => true);
}
flag2 = flag4;
}
if (flag2)
{
break;
}
await Task.Delay(100);
}
Logger.LogInfo((object)$"Ready to proceed with playback. Ready: {readyCount}, Errors: {errorCount}, Total: {totalPlayers}");
}
[PunRPC]
public void SyncPlayback(string url, int requesterId)
{
if (isPlaying && currentSongUrl == url && audioSource.isPlaying)
{
return;
}
if (!downloadedClips.ContainsKey(url))
{
Logger.LogError((object)("Clip not found for url: " + url));
return;
}
CleanupCurrentPlayback();
currentSongUrl = url;
if (songTitles.TryGetValue(url, out var value))
{
currentSongTitle = value;
}
isAwaitingSyncPlayback = false;
audioSource.clip = downloadedClips[url];
SetQuality(qualityLevel);
UpdateAudioRangeBasedOnVolume(audioSource.volume);
audioSource.Play();
isPlaying = true;
UpdateUIStatus("Now playing: " + currentSongTitle);
}
private void CleanupCurrentPlayback()
{
if (audioSource.isPlaying)
{
audioSource.Stop();
isPlaying = false;
}
audioSource.clip = null;
}
public void RemoveFromCache(string url)
{
if (downloadedClips.ContainsKey(url))
{
AudioClip val = downloadedClips[url];
downloadedClips.Remove(url);
if ((Object)(object)val != (Object)null)
{
Object.Destroy((Object)(object)val);
}
}
if (songTitles.ContainsKey(url))
{
songTitles.Remove(url);
}
if (downloadsReady.ContainsKey(url))
{
downloadsReady.Remove(url);
}
if (downloadErrors.ContainsKey(url))
{
downloadErrors.Remove(url);
}
}
public void SetQuality(int level)
{
qualityLevel = Mathf.Clamp(level, 0, 4);
switch (qualityLevel)
{
case 0:
((Behaviour)lowPassFilter).enabled = true;
lowPassFilter.cutoffFrequency = 1500f;
break;
case 1:
((Behaviour)lowPassFilter).enabled = true;
lowPassFilter.cutoffFrequency = 3000f;
break;
case 2:
((Behaviour)lowPassFilter).enabled = true;
lowPassFilter.cutoffFrequency = 4500f;
break;
case 3:
((Behaviour)lowPassFilter).enabled = true;
lowPassFilter.cutoffFrequency = 6000f;
break;
case 4:
((Behaviour)lowPassFilter).enabled = false;
break;
}
}
[PunRPC]
public void UpdateQuality(int level, int requesterId)
{
BoomboxController component = ((Component)this).GetComponent<BoomboxController>();
SetQuality(level);
}
[PunRPC]
public void UpdateVolume(float volume, int requesterId)
{
BoomboxController component = ((Component)this).GetComponent<BoomboxController>();
float volume2 = volume * maxVolumeLimit;
audioSource.volume = volume2;
UpdateAudioRangeBasedOnVolume(volume2);
}
private void UpdateAudioRangeBasedOnVolume(float volume)
{
//IL_004c: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: 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_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0085: Expected O, but got Unknown
float num = Mathf.Lerp(maxDistanceBase, maxDistanceBase + maxDistanceAddition, volume);
audioSource.minDistance = minDistance;
audioSource.maxDistance = num;
AnimationCurve val = new AnimationCurve((Keyframe[])(object)new Keyframe[3]
{
new Keyframe(0f, 1f),
new Keyframe(minDistance, 0.9f),
new Keyframe(num, 0f)
});
audioSource.SetCustomCurve((AudioSourceCurveType)0, val);
}
[PunRPC]
public void PausePlayback(int requesterId)
{
if (audioSource.isPlaying)
{
audioSource.Pause();
isPlaying = false;
UpdateUIStatus("Paused: " + currentSongTitle);
}
}
[PunRPC]
public void StopPlayback(int requesterId)
{
if (audioSource.isPlaying)
{
audioSource.Stop();
isPlaying = false;
UpdateUIStatus("Stopped: " + currentSongTitle);
}
}
private void UpdateUIStatus(string message)
{
BoomboxUI component = ((Component)this).GetComponent<BoomboxUI>();
if ((Object)(object)component != (Object)null && component.IsUIVisible())
{
component.UpdateStatus(message);
}
}
public override void OnPlayerLeftRoom(Player otherPlayer)
{
if (isDownloadInProgress && !string.IsNullOrEmpty(currentDownloadUrl) && downloadsReady.ContainsKey(currentDownloadUrl) && !downloadsReady[currentDownloadUrl].Contains(otherPlayer.ActorNumber))
{
if (!downloadErrors.ContainsKey(currentDownloadUrl))
{
downloadErrors[currentDownloadUrl] = new HashSet<int>();
}
downloadErrors[currentDownloadUrl].Add(otherPlayer.ActorNumber);
Logger.LogInfo((object)$"Player {otherPlayer.ActorNumber} left during download - marking as error");
}
((MonoBehaviourPunCallbacks)this).OnPlayerLeftRoom(otherPlayer);
}
public override void OnPlayerEnteredRoom(Player newPlayer)
{
((MonoBehaviourPunCallbacks)this).OnPlayerEnteredRoom(newPlayer);
if (PhotonNetwork.IsMasterClient && isPlaying && !string.IsNullOrEmpty(currentSongUrl))
{
Logger.LogInfo((object)$"New player {newPlayer.ActorNumber} joined - syncing current playback state");
if (songTitles.ContainsKey(currentSongUrl))
{
photonView.RPC("SetSongTitle", newPlayer, new object[2]
{
currentSongUrl,
songTitles[currentSongUrl]
});
}
photonView.RPC("SyncPlayback", newPlayer, new object[2]
{
currentSongUrl,
PhotonNetwork.LocalPlayer.ActorNumber
});
photonView.RPC("UpdateQuality", newPlayer, new object[2]
{
qualityLevel,
PhotonNetwork.LocalPlayer.ActorNumber
});
}
}
public static async Task<AudioClip> GetAudioClipAsync(string filePath)
{
string uri = "file://" + filePath;
UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(uri, (AudioType)13);
try
{
TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
UnityWebRequestAsyncOperation operation = www.SendWebRequest();
((AsyncOperation)operation).completed += delegate
{
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Invalid comparison between Unknown and I4
if ((int)www.result != 1)
{
tcs.SetException(new Exception("Failed to load audio file: " + www.error));
}
else
{
tcs.SetResult(result: true);
}
};
await tcs.Task;
AudioClip clip = DownloadHandlerAudioClip.GetContent(www);
try
{
if (Directory.Exists(Path.GetDirectoryName(filePath)))
{
Directory.Delete(Path.GetDirectoryName(filePath), recursive: true);
}
}
catch (Exception ex2)
{
Exception ex = ex2;
Logger.LogWarning((object)("Failed to clean up temp directory: " + ex.Message));
}
return clip;
}
finally
{
if (www != null)
{
((IDisposable)www).Dispose();
}
}
}
}
public class BoomboxController : MonoBehaviourPun
{
private static BoomBoxCartMod Instance = BoomBoxCartMod.instance;
private PhysGrabCart cart;
private BoomboxUI boomboxUI;
private Boombox boombox;
private int currentControllerId = -1;
private static ManualLogSource Logger => Instance.logger;
private void Awake()
{
cart = ((Component)this).GetComponent<PhysGrabCart>();
boombox = ((Component)this).GetComponent<Boombox>();
if ((Object)(object)cart == (Object)null)
{
Logger.LogError((object)"BoomboxController: PhysGrabCart component not found!");
}
else if ((Object)(object)boombox == (Object)null)
{
Logger.LogError((object)"BoomboxController: Boombox component not found!");
}
}
private bool IsLocalPlayerGrabbingCart()
{
return PlayerGrabbingTracker.IsLocalPlayerGrabbingCart(((Component)this).gameObject);
}
public void RequestBoomboxControl()
{
if (IsLocalPlayerGrabbingCart())
{
int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber;
((MonoBehaviourPun)this).photonView.RPC("RequestControl", (RpcTarget)2, new object[1] { actorNumber });
}
}
[PunRPC]
private void RequestControl(int requesterId)
{
if (PhotonNetwork.IsMasterClient)
{
bool flag = true;
if (requesterId == PhotonNetwork.LocalPlayer.ActorNumber)
{
flag = PlayerGrabbingTracker.IsLocalPlayerGrabbingCart(((Component)this).gameObject);
}
if (currentControllerId == -1 && flag)
{
((MonoBehaviourPun)this).photonView.RPC("SetController", (RpcTarget)0, new object[1] { requesterId });
}
}
}
[PunRPC]
private void SetController(int controllerId)
{
try
{
currentControllerId = controllerId;
if (controllerId == PhotonNetwork.LocalPlayer.ActorNumber)
{
EnsureBoomboxUIExists();
if ((Object)(object)boomboxUI != (Object)null)
{
boomboxUI.ShowUI();
}
else
{
Logger.LogError((object)"Failed to create BoomboxUI component");
}
}
else if ((Object)(object)boomboxUI != (Object)null && boomboxUI.IsUIVisible())
{
boomboxUI.HideUI();
}
}
catch (Exception ex)
{
Logger.LogError((object)("Error in SetController: " + ex.Message + "\n" + ex.StackTrace));
}
}
private void EnsureBoomboxUIExists()
{
if ((Object)(object)boomboxUI == (Object)null)
{
boomboxUI = ((Component)this).gameObject.GetComponent<BoomboxUI>();
if ((Object)(object)boomboxUI == (Object)null)
{
boomboxUI = ((Component)this).gameObject.AddComponent<BoomboxUI>();
}
}
}
public void ReleaseControl()
{
if (currentControllerId == PhotonNetwork.LocalPlayer.ActorNumber)
{
((MonoBehaviourPun)this).photonView.RPC("RequestRelease", (RpcTarget)2, new object[1] { PhotonNetwork.LocalPlayer.ActorNumber });
}
}
[PunRPC]
private void RequestRelease(int releaserId)
{
if (PhotonNetwork.IsMasterClient && currentControllerId == releaserId)
{
((MonoBehaviourPun)this).photonView.RPC("SetController", (RpcTarget)0, new object[1] { -1 });
}
}
private void OnPlayerReleasedCart(int playerActorNumber)
{
if (playerActorNumber == currentControllerId && PhotonNetwork.IsMasterClient)
{
((MonoBehaviourPun)this).photonView.RPC("SetController", (RpcTarget)0, new object[1] { -1 });
}
}
public void LocalPlayerReleasedCart()
{
int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber;
if (currentControllerId == actorNumber)
{
ReleaseControl();
}
}
}
public class BoomboxUI : MonoBehaviourPun
{
private static BoomBoxCartMod Instance = BoomBoxCartMod.instance;
public PhotonView photonView;
private bool showUI = false;
private string urlInput = "";
private float normalizedVolume = 0.3f;
private float lastSentNormalizedVolume = 0.3f;
private bool isSliderBeingDragged = false;
private int qualityLevel = 3;
private string[] qualityLabels = new string[5] { "REALLY Low (You Freak)", "Low", "Medium-Low", "Medium-High (Recommended)", "High" };
private bool isQualitySliderBeingDragged = false;
private int lastSentQualityLevel = 3;
private Rect windowRect = new Rect(100f, 100f, 400f, 700f);
private Boombox boombox;
private BoomboxController controller;
private GUIStyle windowStyle;
private GUIStyle headerStyle;
private GUIStyle buttonStyle;
private GUIStyle textFieldStyle;
private GUIStyle labelStyle;
private GUIStyle sliderStyle;
private GUIStyle statusStyle;
private Texture2D backgroundTexture;
private Texture2D buttonTexture;
private Texture2D sliderBackgroundTexture;
private Texture2D sliderThumbTexture;
private string errorMessage = "";
private float errorMessageTime = 0f;
private string statusMessage = "";
private CursorLockMode previousLockMode;
private bool previousCursorVisible;
private bool stylesInitialized = false;
private Vector2 scrollPosition = Vector2.zero;
private bool shouldClearFocus = false;
private static ManualLogSource Logger => Instance.logger;
private void Awake()
{
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
try
{
boombox = ((Component)this).GetComponent<Boombox>();
if ((Object)(object)boombox != (Object)null)
{
photonView = boombox.photonView;
}
else
{
Logger.LogError((object)"BoomboxUI: Failed to find Boombox component");
photonView = ((Component)this).GetComponent<PhotonView>();
}
controller = ((Component)this).GetComponent<BoomboxController>();
if ((Object)(object)photonView == (Object)null)
{
Logger.LogError((object)"BoomboxUI: Failed to find PhotonView component");
}
windowRect = new Rect((float)(Screen.width / 2 - 200), (float)(Screen.height / 2 - 175), 400f, 500f);
}
catch (Exception ex)
{
Logger.LogError((object)("Error in BoomboxUI.Awake: " + ex.Message + "\n" + ex.StackTrace));
}
}
private void Update()
{
if (Time.time > errorMessageTime && !string.IsNullOrEmpty(errorMessage))
{
errorMessage = "";
}
if (showUI && Keyboard.current != null && ((ButtonControl)Keyboard.current.escapeKey).wasPressedThisFrame)
{
if ((Object)(object)controller != (Object)null)
{
controller.ReleaseControl();
}
else
{
HideUI();
}
}
if (isSliderBeingDragged && Input.GetMouseButtonUp(0))
{
isSliderBeingDragged = false;
SendVolumeUpdate();
}
if (isQualitySliderBeingDragged && Input.GetMouseButtonUp(0))
{
isQualitySliderBeingDragged = false;
SendQualityUpdate();
}
}
public void ShowUI()
{
//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)
if (showUI)
{
return;
}
if ((Object)(object)boombox == (Object)null || (Object)(object)photonView == (Object)null)
{
Logger.LogError((object)"Cannot show UI - boombox or photonView is null");
return;
}
showUI = true;
previousLockMode = Cursor.lockState;
previousCursorVisible = Cursor.visible;
Cursor.visible = true;
Cursor.lockState = (CursorLockMode)0;
if ((Object)(object)boombox != (Object)null)
{
normalizedVolume = boombox.audioSource.volume / boombox.maxVolumeLimit;
lastSentNormalizedVolume = normalizedVolume;
qualityLevel = boombox.qualityLevel;
lastSentQualityLevel = qualityLevel;
}
UpdateStatusFromBoombox();
}
public void UpdateStatusFromBoombox()
{
if ((Object)(object)boombox != (Object)null)
{
if (boombox.IsDownloadInProgress())
{
statusMessage = "Downloading audio from " + boombox.GetCurrentDownloadUrl() + "...";
}
else if (boombox.isPlaying)
{
statusMessage = "Now playing: " + boombox.currentSongTitle;
}
else if (!string.IsNullOrEmpty(boombox.currentSongUrl))
{
statusMessage = "Ready to play: " + boombox.currentSongTitle;
}
else
{
statusMessage = "Ready to play music! Enter a YouTube URL";
}
}
}
private void SendVolumeUpdate()
{
if (normalizedVolume != lastSentNormalizedVolume)
{
lastSentNormalizedVolume = normalizedVolume;
if ((Object)(object)boombox.audioSource != (Object)null)
{
float volume = normalizedVolume * boombox.maxVolumeLimit;
boombox.audioSource.volume = volume;
}
if ((Object)(object)photonView != (Object)null)
{
photonView.RPC("UpdateVolume", (RpcTarget)3, new object[2]
{
normalizedVolume,
PhotonNetwork.LocalPlayer.ActorNumber
});
}
}
}
private void SendQualityUpdate()
{
if (qualityLevel != lastSentQualityLevel)
{
lastSentQualityLevel = qualityLevel;
if ((Object)(object)boombox != (Object)null)
{
boombox.SetQuality(qualityLevel);
}
if ((Object)(object)photonView != (Object)null)
{
photonView.RPC("UpdateQuality", (RpcTarget)3, new object[2]
{
qualityLevel,
PhotonNetwork.LocalPlayer.ActorNumber
});
}
}
}
public void HideUI()
{
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
if (showUI)
{
showUI = false;
Cursor.lockState = previousLockMode;
Cursor.visible = previousCursorVisible;
}
}
public bool IsUIVisible()
{
return showUI;
}
public void UpdateStatus(string message)
{
statusMessage = message;
}
private void InitializeStyles()
{
//IL_0026: Unknown result type (might be due to invalid IL or missing references)
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_00af: Unknown result type (might be due to invalid IL or missing references)
//IL_00b9: Expected O, but got Unknown
//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
//IL_00ff: Expected O, but got Unknown
//IL_010e: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Expected O, but got Unknown
//IL_0124: Unknown result type (might be due to invalid IL or missing references)
//IL_012e: Expected O, but got Unknown
//IL_0154: Unknown result type (might be due to invalid IL or missing references)
//IL_0178: Unknown result type (might be due to invalid IL or missing references)
//IL_0182: Expected O, but got Unknown
//IL_018e: Unknown result type (might be due to invalid IL or missing references)
//IL_0198: Expected O, but got Unknown
//IL_01cf: Unknown result type (might be due to invalid IL or missing references)
//IL_01ff: Unknown result type (might be due to invalid IL or missing references)
//IL_021a: Unknown result type (might be due to invalid IL or missing references)
//IL_0230: Unknown result type (might be due to invalid IL or missing references)
//IL_0246: Unknown result type (might be due to invalid IL or missing references)
//IL_026b: Unknown result type (might be due to invalid IL or missing references)
//IL_0275: Expected O, but got Unknown
//IL_0280: Unknown result type (might be due to invalid IL or missing references)
//IL_028a: Expected O, but got Unknown
//IL_02a3: Unknown result type (might be due to invalid IL or missing references)
//IL_02ad: Expected O, but got Unknown
//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
//IL_02e8: Unknown result type (might be due to invalid IL or missing references)
//IL_030d: Unknown result type (might be due to invalid IL or missing references)
//IL_0317: Expected O, but got Unknown
//IL_0323: Unknown result type (might be due to invalid IL or missing references)
//IL_032d: Expected O, but got Unknown
//IL_0338: Unknown result type (might be due to invalid IL or missing references)
//IL_035c: Unknown result type (might be due to invalid IL or missing references)
//IL_0366: Expected O, but got Unknown
//IL_0372: Unknown result type (might be due to invalid IL or missing references)
//IL_037c: Expected O, but got Unknown
//IL_0387: Unknown result type (might be due to invalid IL or missing references)
//IL_03c5: Unknown result type (might be due to invalid IL or missing references)
//IL_03cf: Expected O, but got Unknown
if (!stylesInitialized)
{
backgroundTexture = CreateColorTexture(new Color(0.1f, 0.1f, 0.1f, 0.9f));
buttonTexture = CreateColorTexture(new Color(0.2f, 0.2f, 0.3f, 1f));
sliderBackgroundTexture = CreateColorTexture(new Color(0.15f, 0.15f, 0.2f, 1f));
sliderThumbTexture = CreateColorTexture(new Color(0.7f, 0.7f, 0.8f, 1f));
windowStyle = new GUIStyle(GUI.skin.window);
windowStyle.normal.background = backgroundTexture;
windowStyle.onNormal.background = backgroundTexture;
windowStyle.border = new RectOffset(10, 10, 10, 10);
windowStyle.padding = new RectOffset(15, 15, 20, 15);
headerStyle = new GUIStyle(GUI.skin.label);
headerStyle.fontSize = 18;
headerStyle.fontStyle = (FontStyle)1;
headerStyle.normal.textColor = Color.white;
headerStyle.alignment = (TextAnchor)4;
headerStyle.margin = new RectOffset(0, 0, 10, 20);
buttonStyle = new GUIStyle(GUI.skin.button);
buttonStyle.normal.background = buttonTexture;
buttonStyle.hover.background = CreateColorTexture(new Color(0.3f, 0.3f, 0.4f, 1f));
buttonStyle.active.background = CreateColorTexture(new Color(0.4f, 0.4f, 0.5f, 1f));
buttonStyle.normal.textColor = Color.white;
buttonStyle.hover.textColor = Color.white;
buttonStyle.active.textColor = Color.white;
buttonStyle.fontSize = 14;
buttonStyle.padding = new RectOffset(15, 15, 8, 8);
buttonStyle.margin = new RectOffset(5, 5, 5, 5);
buttonStyle.alignment = (TextAnchor)4;
textFieldStyle = new GUIStyle(GUI.skin.textField);
textFieldStyle.normal.background = CreateColorTexture(new Color(0.2f, 0.2f, 0.2f, 1f));
textFieldStyle.normal.textColor = Color.white;
textFieldStyle.fontSize = 14;
textFieldStyle.padding = new RectOffset(10, 10, 8, 8);
labelStyle = new GUIStyle(GUI.skin.label);
labelStyle.normal.textColor = Color.white;
labelStyle.fontSize = 14;
labelStyle.margin = new RectOffset(0, 0, 10, 5);
statusStyle = new GUIStyle(GUI.skin.label);
statusStyle.normal.textColor = Color.cyan;
statusStyle.fontSize = 14;
statusStyle.wordWrap = true;
statusStyle.alignment = (TextAnchor)4;
sliderStyle = new GUIStyle(GUI.skin.horizontalSlider);
sliderStyle.normal.background = sliderBackgroundTexture;
stylesInitialized = true;
}
}
private Texture2D CreateColorTexture(Color color)
{
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Expected O, but got Unknown
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
Texture2D val = new Texture2D(1, 1);
val.SetPixel(0, 0, color);
val.Apply();
return val;
}
private void OnGUI()
{
//IL_0027: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Expected O, but got Unknown
//IL_0048: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
if (showUI)
{
if (!stylesInitialized)
{
InitializeStyles();
}
windowRect = GUILayout.Window(0, windowRect, new WindowFunction(DrawUI), "Boombox Controller", windowStyle, Array.Empty<GUILayoutOption>());
}
}
private void DrawUI(int windowID)
{
//IL_0019: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_0032: Unknown result type (might be due to invalid IL or missing references)
//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
//IL_00f2: 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_0207: Unknown result type (might be due to invalid IL or missing references)
//IL_020e: Unknown result type (might be due to invalid IL or missing references)
//IL_0213: Unknown result type (might be due to invalid IL or missing references)
//IL_021c: Unknown result type (might be due to invalid IL or missing references)
//IL_031a: Unknown result type (might be due to invalid IL or missing references)
//IL_033c: Unknown result type (might be due to invalid IL or missing references)
//IL_0599: Unknown result type (might be due to invalid IL or missing references)
GUILayout.Label("Control The Boombox In The Cart", headerStyle, Array.Empty<GUILayoutOption>());
scrollPosition = GUILayout.BeginScrollView(scrollPosition, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) });
GUILayout.Space(10f);
GUILayout.Label("Enter YouTube URL:", labelStyle, Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
urlInput = GUILayout.TextField(urlInput, 200, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(30f) });
GUILayout.EndHorizontal();
GUILayout.Space(15f);
float num = normalizedVolume * 100f;
GUILayout.Label($"Volume: {Mathf.Round(num)}%", labelStyle, Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
Rect lastRect;
if ((int)Event.current.type == 0)
{
lastRect = GUILayoutUtility.GetLastRect();
if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition))
{
isSliderBeingDragged = true;
}
}
float num2 = GUILayout.HorizontalSlider(normalizedVolume, 0f, 1f, sliderStyle, GUI.skin.horizontalSliderThumb, Array.Empty<GUILayoutOption>());
if (num2 != normalizedVolume && !isSliderBeingDragged)
{
isSliderBeingDragged = true;
}
normalizedVolume = num2;
if ((Object)(object)boombox != (Object)null && (Object)(object)boombox.audioSource != (Object)null)
{
float volume = normalizedVolume * boombox.maxVolumeLimit;
boombox.audioSource.volume = volume;
}
GUILayout.EndHorizontal();
GUILayout.Space(15f);
GUILayout.Label("Audio Quality: " + qualityLabels[qualityLevel], labelStyle, Array.Empty<GUILayoutOption>());
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
if ((int)Event.current.type == 0)
{
lastRect = GUILayoutUtility.GetLastRect();
if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition))
{
isQualitySliderBeingDragged = true;
}
}
float num3 = GUILayout.HorizontalSlider((float)qualityLevel, 0f, 4f, sliderStyle, GUI.skin.horizontalSliderThumb, Array.Empty<GUILayoutOption>());
int num4 = Mathf.RoundToInt(num3);
if (num4 != qualityLevel && !isQualitySliderBeingDragged)
{
isQualitySliderBeingDragged = true;
}
qualityLevel = num4;
if ((Object)(object)boombox != (Object)null)
{
boombox.SetQuality(qualityLevel);
}
GUILayout.EndHorizontal();
GUILayout.Space(15f);
if (!string.IsNullOrEmpty(statusMessage))
{
GUILayout.Label(statusMessage, statusStyle, Array.Empty<GUILayoutOption>());
GUILayout.Space(5f);
}
if (!string.IsNullOrEmpty(errorMessage))
{
GUI.color = Color.red;
GUILayout.Label(errorMessage, labelStyle, Array.Empty<GUILayoutOption>());
GUI.color = Color.white;
GUILayout.Space(5f);
}
GUILayout.Space(10f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUI.enabled = (Object)(object)boombox != (Object)null && !boombox.IsDownloadInProgress();
if (GUILayout.Button("▶ PLAY", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }))
{
if (IsValidYoutubeUrl(urlInput))
{
photonView.RPC("RequestSong", (RpcTarget)0, new object[2]
{
urlInput,
PhotonNetwork.LocalPlayer.ActorNumber
});
GUI.FocusControl((string)null);
}
else
{
ShowErrorMessage("Invalid YouTube URL!");
}
}
GUI.enabled = true;
GUI.enabled = (Object)(object)boombox != (Object)null && boombox.isPlaying;
if (GUILayout.Button("■ STOP", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }))
{
photonView.RPC("StopPlayback", (RpcTarget)0, new object[1] { PhotonNetwork.LocalPlayer.ActorNumber });
GUI.FocusControl((string)null);
}
GUI.enabled = true;
GUILayout.EndHorizontal();
if ((Object)(object)boombox != (Object)null && boombox.IsDownloadInProgress())
{
GUILayout.Space(10f);
GUILayout.Label("Download in progress...", statusStyle, Array.Empty<GUILayoutOption>());
}
GUILayout.EndScrollView();
GUILayout.Space(10f);
GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>());
GUILayout.FlexibleSpace();
if (GUILayout.Button("Close", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2]
{
GUILayout.Width(100f),
GUILayout.Height(30f)
}))
{
if ((Object)(object)controller != (Object)null)
{
controller.ReleaseControl();
}
else
{
HideUI();
}
}
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref windowRect)).width, 30f));
}
private bool IsValidYoutubeUrl(string url)
{
return Boombox.IsValidYoutubeUrl(url);
}
private void ShowErrorMessage(string message)
{
Debug.LogError((object)message);
errorMessage = message;
errorMessageTime = Time.time + 3f;
}
private void OnDestroy()
{
if ((Object)(object)backgroundTexture != (Object)null)
{
Object.Destroy((Object)(object)backgroundTexture);
}
if ((Object)(object)buttonTexture != (Object)null)
{
Object.Destroy((Object)(object)buttonTexture);
}
if ((Object)(object)sliderBackgroundTexture != (Object)null)
{
Object.Destroy((Object)(object)sliderBackgroundTexture);
}
if ((Object)(object)sliderThumbTexture != (Object)null)
{
Object.Destroy((Object)(object)sliderThumbTexture);
}
}
}
[BepInPlugin("ColtG5.BoomboxCart", "BoomboxCart", "1.0.1")]
public class BoomBoxCartMod : BaseUnityPlugin
{
private const string modGUID = "ColtG5.BoomboxCart";
private const string modName = "BoomboxCart";
private const string modVersion = "1.0.1";
private readonly Harmony harmony = new Harmony("ColtG5.BoomboxCart");
internal static BoomBoxCartMod instance;
internal ManualLogSource logger;
private void Awake()
{
if ((Object)(object)instance == (Object)null)
{
instance = this;
}
logger = Logger.CreateLogSource("ColtG5.BoomboxCart");
logger.LogInfo((object)"BoomBoxCartMod loaded!");
Task.Run(delegate
{
YoutubeDL.InitializeAsync().Wait();
});
harmony.PatchAll();
}
}
}
namespace BoomBoxCartMod.Util
{
public static class YoutubeDL
{
private static BoomBoxCartMod Instance = BoomBoxCartMod.instance;
private static readonly string baseFolder = Path.Combine(Directory.GetCurrentDirectory(), "BoomboxedCart");
private const string YtDLP_URL = "https://github.com/yt-dlp/yt-dlp/releases/download/2025.02.19/yt-dlp.exe";
private const string FFMPEG_URL = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip";
private static readonly string ytDlpPath = Path.Combine(baseFolder, "yt-dlp.exe");
private static readonly string ffmpegFolder = Path.Combine(baseFolder, "ffmpeg");
private static readonly string ffmpegBinPath = Path.Combine(ffmpegFolder, "ffmpeg-master-latest-win64-gpl", "bin", "ffmpeg.exe");
private static ManualLogSource Logger => Instance.logger;
public static async Task InitializeAsync()
{
if (!Directory.Exists(baseFolder))
{
Directory.CreateDirectory(baseFolder);
}
if (!File.Exists(ytDlpPath))
{
Console.WriteLine("yt-dlp not found. Downloading...");
await DownloadFileAsync("https://github.com/yt-dlp/yt-dlp/releases/download/2025.02.19/yt-dlp.exe", ytDlpPath);
}
if (!Directory.Exists(ffmpegFolder))
{
Console.WriteLine("ffmpeg not found. Downloading and extracting...");
await DownloadAndExtractFFmpegAsync();
}
if (!File.Exists(ffmpegBinPath))
{
throw new Exception("ffmpeg executable was not found after extraction.");
}
Console.WriteLine("Initialization complete.");
}
private static async Task DownloadFileAsync(string url, string destinationPath)
{
HttpClient client = new HttpClient();
try
{
File.WriteAllBytes(destinationPath, await client.GetByteArrayAsync(url));
}
finally
{
((IDisposable)client)?.Dispose();
}
}
private static async Task DownloadAndExtractFFmpegAsync()
{
string zipPath = Path.Combine(baseFolder, "ffmpeg.zip");
await DownloadFileAsync("https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", zipPath);
ZipFile.ExtractToDirectory(zipPath, ffmpegFolder);
File.Delete(zipPath);
}
public static async Task<(string filePath, string title)> DownloadAudioWithTitleAsync(string videoUrl)
{
await InitializeAsync();
string tempFolder = Path.Combine(baseFolder, Guid.NewGuid().ToString());
Directory.CreateDirectory(tempFolder);
return await Task.Run(async delegate
{
try
{
string title = await GetVideoTitleInternalAsync(videoUrl);
if (string.IsNullOrEmpty(title))
{
title = "Unknown Title";
}
string command = "-x --audio-format mp3 --ffmpeg-location \"" + ffmpegBinPath + "\" --output \"" + Path.Combine(tempFolder, "%(title)s.%(ext)s") + "\" " + videoUrl;
ProcessStartInfo processInfo = new ProcessStartInfo
{
FileName = ytDlpPath,
Arguments = command,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (Process process = Process.Start(processInfo))
{
if (process == null)
{
throw new Exception("Failed to start yt-dlp process.");
}
process.WaitForExit();
if (process.ExitCode != 0)
{
string error = process.StandardError.ReadToEnd();
throw new Exception("yt-dlp error: " + error);
}
}
string audioFilePath = Directory.GetFiles(tempFolder, "*.mp3").FirstOrDefault();
if (audioFilePath == null)
{
throw new Exception("Audio download failed.");
}
return (audioFilePath, title);
}
catch (Exception ex2)
{
Exception ex = ex2;
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, recursive: true);
}
throw new Exception("Error downloading audio: " + ex.Message);
}
});
}
private static async Task<string> GetVideoTitleInternalAsync(string url)
{
try
{
await InitializeAsync();
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = ytDlpPath,
Arguments = "--get-title " + url,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
Process process = new Process
{
StartInfo = psi
};
try
{
new TaskCompletionSource<string>();
process.Start();
string title2 = await process.StandardOutput.ReadToEndAsync();
await process.StandardError.ReadToEndAsync();
Task timeoutTask = Task.Delay(10000);
Task processExitTask = Task.Run(delegate
{
process.WaitForExit();
});
if (await Task.WhenAny(new Task[2] { processExitTask, timeoutTask }) == timeoutTask)
{
try
{
process.Kill();
}
catch
{
}
Logger.LogWarning((object)"yt-dlp title fetch timed out");
return "Unknown Title (Timeout)";
}
if (process.ExitCode != 0)
{
Logger.LogError((object)$"yt-dlp error code: {process.ExitCode}");
return "Unknown Title";
}
title2 = title2.Trim();
return string.IsNullOrEmpty(title2) ? "Unknown Title" : title2;
}
finally
{
if (process != null)
{
((IDisposable)process).Dispose();
}
}
}
catch (Exception ex2)
{
Exception ex = ex2;
Logger.LogError((object)("Error getting video title: " + ex.Message));
return "Unknown Title";
}
}
}
}
namespace BoomBoxCartMod.Patches
{
[HarmonyPatch(typeof(PhysGrabCart))]
internal class PhysGrabCartPatch
{
private static BoomBoxCartMod Instance = BoomBoxCartMod.instance;
private static ManualLogSource Logger => Instance.logger;
[HarmonyPatch("Start")]
[HarmonyPostfix]
private static void PatchPhysGrabCartStart(PhysGrabCart __instance)
{
if (!((Object)(object)RunManager.instance.levelCurrent == (Object)(object)RunManager.instance.levelShop))
{
if ((Object)(object)((Component)__instance).GetComponent<Boombox>() == (Object)null)
{
((Component)__instance).gameObject.AddComponent<Boombox>();
}
if ((Object)(object)((Component)__instance).GetComponent<BoomboxController>() == (Object)null)
{
((Component)__instance).gameObject.AddComponent<BoomboxController>();
}
}
}
}
public static class PlayerGrabbingTracker
{
public static Dictionary<int, GameObject> playerGrabbingMap = new Dictionary<int, GameObject>();
public static bool IsLocalPlayerGrabbingCart(GameObject cart)
{
int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber;
return playerGrabbingMap.ContainsKey(actorNumber) && (Object)(object)playerGrabbingMap[actorNumber] == (Object)(object)cart;
}
public static void SetLocalPlayerGrabbing(GameObject obj)
{
int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber;
if ((Object)(object)obj != (Object)null)
{
playerGrabbingMap[actorNumber] = obj;
}
else if (playerGrabbingMap.ContainsKey(actorNumber))
{
playerGrabbingMap.Remove(actorNumber);
}
}
}
[HarmonyPatch(typeof(PlayerController))]
internal class PlayerControllerPatch
{
private static BoomBoxCartMod Instance = BoomBoxCartMod.instance;
private static ManualLogSource Logger => Instance.logger;
[HarmonyPatch("Update")]
[HarmonyPostfix]
private static void PatchPlayerControllerUpdate(PlayerController __instance)
{
if ((Object)(object)__instance.physGrabObject != (Object)null)
{
PlayerGrabbingTracker.SetLocalPlayerGrabbing(__instance.physGrabObject.gameObject);
if (Keyboard.current != null && ((ButtonControl)Keyboard.current.yKey).wasPressedThisFrame && (Object)(object)__instance.physGrabObject.GetComponent<Boombox>() != (Object)null)
{
BoomboxController component = __instance.physGrabObject.GetComponent<BoomboxController>();
if ((Object)(object)component != (Object)null)
{
component.RequestBoomboxControl();
}
}
}
else
{
PlayerGrabbingTracker.SetLocalPlayerGrabbing(null);
}
}
}
}