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.Net.Http.Headers; 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; using System.Text.RegularExpressions; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BoomBoxCartMod.Patches; using BoomBoxCartMod.Util; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; 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("BoomboxCartUpgrade")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BoomboxCartUpgrade")] [assembly: AssemblyCopyright("Copyright © 2025")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("37e852e0-b511-4315-8182-68c0a54e1ba9")] [assembly: AssemblyFileVersion("1.3.4.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.4.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 AudioPlayer : MonoBehaviourPunCallbacks { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; public float minDistance = 3f; public float maxDistanceBase = 15f; public float maxDistanceAddition = 30f; private static int qualityLevel = 4; private AudioLowPassFilter lowPassFilter; public AudioSource audioSource; private static ManualLogSource Logger => Instance.logger; private void Awake() { audioSource = ((Component)this).gameObject.AddComponent(); 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; audioSource.loop = false; audioSource.mute = Instance.baseListener.audioMuted; lowPassFilter = ((Component)this).gameObject.AddComponent(); ((Behaviour)lowPassFilter).enabled = false; UpdateAudioRangeBasedOnVolume(); } private void Update() { if (Instance.baseListener.audioMuted != audioSource.mute) { audioSource.mute = Instance.baseListener.audioMuted; } } public AudioClip GetClip() { return ((Object)(object)audioSource == (Object)null) ? null : audioSource.clip; } public void SetVolume(float volume) { audioSource.volume = volume; UpdateAudioRangeBasedOnVolume(); } public void UpdateAudioRangeBasedOnVolume() { UpdateAudioRangeBasedOnVolume(audioSource.volume); } public 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); } public void SetClip(AudioClip clip) { audioSource.Stop(); audioSource.clip = clip; audioSource.time = 0f; SetQuality(qualityLevel); UpdateAudioRangeBasedOnVolume(); } 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; } } public static int GetQuality() { return qualityLevel; } public float GetTime() { return audioSource.time; } public void SetTime(float time) { audioSource.time = time; } public bool IsPlaying() { return audioSource.isPlaying; } public void Play() { audioSource.Play(); } public void Pause() { audioSource.Pause(); } public void Stop() { audioSource.Stop(); audioSource.clip = null; } private void OnDestroy() { Object.Destroy((Object)(object)lowPassFilter); Object.Destroy((Object)(object)audioSource); } } public class Boombox : MonoBehaviourPunCallbacks { private class SharedAudioEntry { public string title; public string url; public double duration; public int startTime; } public class BoomboxData { public string key = Guid.NewGuid().ToString(); public AudioEntry currentSong = null; public List playbackQueue = new List(); public bool isPlaying = false; public float absVolume = 0.6f; public float personalVolumePercentage = 0.35f; public bool loopQueue = false; public bool underglowEnabled = false; public bool visualizerEnabled = true; public bool pendingPlaybackStart = false; public int stateVersion = 0; public int playbackTime = 0; public int playbackStartTimestamp = 0; } public class AudioEntry { public string Title; public string Url; public double Duration = -1.0; public int StartTime = 0; public AudioEntry(string url, SongInfo info) { Url = url; Title = info.title; Duration = info.duration; } private int PeekStartTime(Boombox boombox) { if (boombox.data != null && boombox.data.playbackTime != 0) { return boombox.data.playbackTime; } return StartTime; } public int UseStartTime(Boombox boombox) { int result = PeekStartTime(boombox); if (boombox.data != null && boombox.data.playbackTime != 0) { boombox.data.playbackTime = 0; } else if (Instance.UseTimeStampOnce.Value) { StartTime = 0; } return result; } public AudioClip GetAudioClip() { return DownloadHelper.downloadedClips.ContainsKey(Url) ? DownloadHelper.downloadedClips[Url] : null; } } private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; public PhotonView photonView; public AudioPlayer audioPlayer; public Visualizer visualizer; public VisualEffects visualEffects; public DownloadHelper downloadHelper = null; private bool syncFinished = false; public bool startPlayBackOnDownload = true; public BoomboxData data = new BoomboxData(); private static bool mutePressed = false; private bool isApplyingSharedState = false; private string pendingCurrentSongDownloadUrl = null; private int lastAppliedSharedStateVersion = -1; private Hashtable lastSharedState = null; private float monsterAttractTimer = 0f; private float monsterAttractInterval = 1f; private static bool applyQualityToDownloads = false; private static bool monstersCanHearMusic = false; private static ManualLogSource Logger => Instance.logger; public static bool ApplyQualityToDownloads { get { return applyQualityToDownloads; } set { applyQualityToDownloads = value; } } public static bool MonstersCanHearMusic { get { return monstersCanHearMusic; } set { monstersCanHearMusic = value; } } public bool LoopQueue { get { return data.loopQueue; } set { data.loopQueue = value; } } private void Awake() { audioPlayer = ((Component)this).gameObject.AddComponent(); downloadHelper = ((Component)this).gameObject.AddComponent(); photonView = ((Component)this).GetComponent(); if ((Object)(object)photonView == (Object)null) { Logger.LogError((object)"PhotonView not found on Boombox object."); return; } if ((Object)(object)((Component)this).GetComponent() == (Object)null) { ((Component)this).gameObject.AddComponent(); } if ((Object)(object)((Component)this).GetComponent() == (Object)null) { visualizer = ((Component)this).gameObject.AddComponent(); visualizer.audioSource = audioPlayer.audioSource; } if ((Object)(object)((Component)this).GetComponent() == (Object)null) { visualEffects = ((Component)this).gameObject.AddComponent(); } PersistentData.SetBoomboxViewInitialized(photonView.ViewID); Logger.LogInfo((object)$"Boombox initialized on this cart. AudioPlayer: {audioPlayer}, PhotonView: {photonView}"); } private void Start() { ApplyVisualStateFromData(); LoadSharedStateFromRoom(); } private void Update() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.IsMasterClient && data.isPlaying && MonstersCanHearMusic && (Object)(object)EnemyDirector.instance != (Object)null) { monsterAttractTimer += Time.deltaTime; if (monsterAttractTimer >= monsterAttractInterval) { EnemyDirector.instance.SetInvestigate(((Component)this).transform.position, 5f, false); monsterAttractTimer = 0f; } } else { monsterAttractTimer = 0f; } AudioSource audioSource = audioPlayer.audioSource; bool flag = (Object)(object)audioSource.clip != (Object)null && audioSource.time >= Math.Max(0f, audioSource.clip.length - 0.05f); if (!(syncFinished && data.isPlaying && !audioSource.isPlaying && flag) || !PhotonNetwork.IsMasterClient) { return; } int currentSongIndex = GetCurrentSongIndex(); if (currentSongIndex == -1) { DismissQueueLocal(); Logger.LogDebug((object)"Dismiss queue, invalid current song."); } else if (currentSongIndex + 1 >= data.playbackQueue.Count) { Logger.LogDebug((object)"Finish playing song"); if (LoopQueue && data.playbackQueue.Count > 0) { SelectSongIndex(0); return; } data.currentSong = null; data.playbackTime = 0; data.isPlaying = false; SetPlaybackReferenceFromSeconds(0f); PublishSharedState(updateTime: true); } else { SelectSongIndex(currentSongIndex + 1); } } public void TogglePlaying(bool value) { data.isPlaying = value; } public static long GetCurrentServerTimeMilliseconds() { return PhotonNetwork.ServerTimestamp; } public long GetRelativePlaybackMilliseconds() { return GetCurrentServerTimeMilliseconds() - (long)Math.Round(GetTrackedPlaybackSeconds() * 1000f); } public int GetCurrentSongIndex() { if (data.currentSong == null) { return -1; } return data.playbackQueue.IndexOf(data.currentSong); } private Hashtable CreateSharedState(bool includeTime, bool updateQueue) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Expected O, but got Unknown //IL_0109: Expected O, but got Unknown Hashtable val = new Hashtable(); ((Dictionary)val).Add((object)"propVersion", (object)data.stateVersion); ((Dictionary)val).Add((object)"IDKey", (object)data.key); ((Dictionary)val).Add((object)"isPlaying", (object)(data.currentSong != null && data.isPlaying)); ((Dictionary)val).Add((object)"pendingPlaybackStart", (object)data.pendingPlaybackStart); ((Dictionary)val).Add((object)"absVolume", (object)data.absVolume); ((Dictionary)val).Add((object)"loopQueue", (object)data.loopQueue); ((Dictionary)val).Add((object)"underglow", (object)data.underglowEnabled); ((Dictionary)val).Add((object)"visualizer", (object)data.visualizerEnabled); ((Dictionary)val).Add((object)"currentSongIndex", (object)GetCurrentSongIndex()); Hashtable val2 = val; if (includeTime) { ((Dictionary)(object)val2).Add((object)"playbackStartTimestamp", (object)data.playbackStartTimestamp); } if (updateQueue) { ((Dictionary)(object)val2).Add((object)"queue", (object)JsonConvert.SerializeObject((object)data.playbackQueue.Select((AudioEntry entry) => new SharedAudioEntry { title = entry.Title, url = entry.Url, duration = entry.Duration, startTime = entry.StartTime }).ToArray())); Logger.LogDebug((object)string.Format("Created table with queue size: {0}", JsonConvert.DeserializeObject((string)val2[(object)"queue"]).Count())); } return val2; } private void LoadSharedStateFromRoom() { if (PhotonNetwork.IsConnected && PhotonNetwork.CurrentRoom != null && !((Object)(object)photonView == (Object)null)) { Hashtable customProperties = ((RoomInfo)PhotonNetwork.CurrentRoom).CustomProperties; ApplyChangedValues(customProperties); if (PhotonNetwork.IsMasterClient && ((Dictionary)(object)customProperties).Count > 0) { PublishSharedState(updateTime: true, updateQueue: true, force: true); } } } public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { ((MonoBehaviourPunCallbacks)this).OnRoomPropertiesUpdate(propertiesThatChanged); if (!((Object)(object)photonView == (Object)null) && propertiesThatChanged != null) { ApplyChangedValues(propertiesThatChanged); } } private void ApplyChangedValues(Hashtable propertiesThatChanged) { isApplyingSharedState = true; if (!((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"propVersion")) { isApplyingSharedState = false; return; } int num = (int)propertiesThatChanged[(object)"propVersion"]; if (num < data.stateVersion) { isApplyingSharedState = false; return; } int currentSongIndex = GetCurrentSongIndex(); bool flag = ((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"queue"); try { if (flag) { string text = (string)propertiesThatChanged[(object)"queue"]; SharedAudioEntry[] array = JsonConvert.DeserializeObject(text); if (array != null) { data.playbackQueue = array.Select(delegate(SharedAudioEntry entry) { string title = (string.IsNullOrWhiteSpace(entry.title) ? "Unknown Title" : entry.title); _ = entry.duration; double duration = entry.duration; string url = entry.url; SongInfo info = new SongInfo(title, duration); return new AudioEntry(url, info) { StartTime = entry.startTime }; }).ToList(); Logger.LogDebug((object)$"Updated queue to size {data.playbackQueue.Count}."); } else { data.playbackQueue = new List(); } } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"currentSongIndex")) { int num2 = (int)propertiesThatChanged[(object)"currentSongIndex"]; if (num2 >= 0 && num2 < data.playbackQueue.Count) { data.currentSong = data.playbackQueue[num2]; } else { data.currentSong = null; data.pendingPlaybackStart = false; audioPlayer.Stop(); UpdateUIStatus("Ready to play music! Enter a Video URL"); Logger.LogDebug((object)$"Invalid currentsong index: {num2}."); } } else if (flag) { data.currentSong = data.playbackQueue[currentSongIndex]; } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"pendingPlaybackStart")) { bool pendingPlaybackStart = (bool)propertiesThatChanged[(object)"pendingPlaybackStart"]; data.pendingPlaybackStart = pendingPlaybackStart; } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"isPlaying")) { bool flag2 = (bool)propertiesThatChanged[(object)"isPlaying"]; data.isPlaying = data.currentSong != null && flag2; } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"absVolume")) { float value = (float)propertiesThatChanged[(object)"absVolume"]; float num3 = Mathf.Clamp01(Convert.ToSingle(value)); if (data.absVolume != num3) { data.absVolume = num3; float volume = num3 * data.personalVolumePercentage; audioPlayer.SetVolume(volume); } } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"loopQueue")) { bool loopQueue = (bool)propertiesThatChanged[(object)"loopQueue"]; data.loopQueue = loopQueue; } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"underglow") && Instance.SyncVisuals.Value) { bool underglowEnabled = (bool)propertiesThatChanged[(object)"underglow"]; data.underglowEnabled = underglowEnabled; ApplyVisualStateFromData(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"visualizer") && Instance.SyncVisuals.Value) { bool visualizerEnabled = (bool)propertiesThatChanged[(object)"visualizer"]; data.visualizerEnabled = visualizerEnabled; ApplyVisualStateFromData(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"playbackStartTimestamp")) { int num4 = (int)propertiesThatChanged[(object)"playbackStartTimestamp"]; data.playbackStartTimestamp = num4; SetPlaybackTime(num4); } if (((Dictionary)(object)propertiesThatChanged).ContainsKey((object)"IDKey")) { string text2 = (string)propertiesThatChanged[(object)"IDKey"]; if (!string.IsNullOrWhiteSpace(text2)) { data.key = text2; } } } catch (Exception ex) { Logger.LogWarning((object)("Error occured while reading properties: " + ex.Message)); } ApplySharedPlaybackState(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); syncFinished = true; isApplyingSharedState = false; } private void ApplySharedPlaybackState() { if (data.currentSong?.Url == null) { Logger.LogDebug((object)("ApplySharedPlaybackState: No current song" + ((data.currentSong == null) ? "" : "URL") + "!")); data.pendingPlaybackStart = false; audioPlayer.Stop(); UpdateUIStatus("Ready to play music! Enter a Video URL"); return; } AudioClip val = data.currentSong?.GetAudioClip(); if ((Object)(object)val == (Object)null) { audioPlayer.Stop(); startPlayBackOnDownload = data.pendingPlaybackStart; if (data.pendingPlaybackStart) { UpdateUIStatus("Loading: " + data.currentSong.Title); } if (!data.pendingPlaybackStart) { EnsureCurrentSongDownloaded(); } return; } if ((Object)(object)audioPlayer.GetClip() != (Object)(object)val) { audioPlayer.Stop(); audioPlayer.audioSource.clip = val; audioPlayer.SetQuality(AudioPlayer.GetQuality()); audioPlayer.UpdateAudioRangeBasedOnVolume(); } ApplyVisualStateFromData(); if (data.pendingPlaybackStart) { if (audioPlayer.IsPlaying()) { audioPlayer.Pause(); } UpdateUIStatus("Loading: " + data.currentSong.Title); } else if (data.isPlaying) { if (!audioPlayer.IsPlaying()) { Logger.LogDebug((object)$"ApplySharedPlaybackState: Starting playback - timestamp={data.playbackStartTimestamp}"); audioPlayer.Play(); SetPlaybackTime(data.playbackStartTimestamp); Logger.LogDebug((object)$"ApplySharedPlaybackState: After Play(), audioPlayer.GetTime()={audioPlayer.GetTime()}"); } UpdateUIStatus("Now playing: " + data.currentSong.Title); } else { if (audioPlayer.IsPlaying()) { audioPlayer.Pause(); } UpdateUIStatus("Ready to play: " + data.currentSong.Title); } } public void ApplyVisualStateFromData() { if ((Object)(object)visualEffects == (Object)null) { visualEffects = ((Component)this).gameObject.AddComponent(); visualizer.audioSource = audioPlayer.audioSource; } visualEffects.SetLights(data.underglowEnabled); if (data.visualizerEnabled) { if ((Object)(object)visualizer == (Object)null) { visualizer = ((Component)this).gameObject.AddComponent(); } visualizer.audioSource = audioPlayer.audioSource; } else if ((Object)(object)visualizer != (Object)null) { Object.Destroy((Object)(object)visualizer); visualizer = null; } } private async void EnsureCurrentSongDownloaded() { string url = data.currentSong?.Url; if (string.IsNullOrWhiteSpace(url) || (Object)(object)downloadHelper == (Object)null || DownloadHelper.downloadedClips.ContainsKey(url) || pendingCurrentSongDownloadUrl == url) { return; } pendingCurrentSongDownloadUrl = url; try { if (await downloadHelper.StartAudioDownload(url)) { BaseListener.RPC(photonView, "ReportDownloadComplete", (RpcTarget)2, url, PhotonNetwork.LocalPlayer.ActorNumber); } } finally { if (pendingCurrentSongDownloadUrl == url) { pendingCurrentSongDownloadUrl = null; } } if (!((Object)(object)this == (Object)null) && !(data.currentSong?.Url != url)) { ApplySharedPlaybackState(); } } private void UpdateStatusFromState() { if (data.currentSong == null) { UpdateUIStatus("Ready to play music! Enter a Video URL"); } else if (data.pendingPlaybackStart) { UpdateUIStatus("Loading: " + data.currentSong.Title); } else if (data.isPlaying) { UpdateUIStatus("Now playing: " + data.currentSong.Title); } else { UpdateUIStatus("Ready to play: " + data.currentSong.Title); } } public bool CanPublish() { return !isApplyingSharedState && PhotonNetwork.IsConnected && PhotonNetwork.CurrentRoom != null && (Object)(object)photonView != (Object)null; } public void PublishSharedState(bool updateTime, bool updateQueue = false, bool force = false) { if (CanPublish()) { if (PhotonNetwork.IsMasterClient) { data.stateVersion++; } Hashtable val = CreateSharedState(updateTime, updateQueue); PhotonNetwork.CurrentRoom.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); lastSharedState = val; syncFinished = true; } } private void StopLocalPlayback(bool updateSharedFlag) { audioPlayer.Stop(); if (updateSharedFlag) { TogglePlaying(value: false); } } private void SetPlaybackReferenceFromSeconds(float totalSeconds) { float num = Math.Max(0f, totalSeconds); data.playbackStartTimestamp = (int)((double)GetCurrentServerTimeMilliseconds() - Math.Round(num * 1000f)); Logger.LogDebug((object)$"SetPlaybackReferenceFromSeconds: Set to {num}s, timestamp={data.playbackStartTimestamp}"); } private float GetTrackedPlaybackSeconds() { if ((Object)(object)audioPlayer?.GetClip() != (Object)null) { if (!audioPlayer.IsPlaying() && data.playbackTime > 0 && audioPlayer.GetTime() <= 0f) { Logger.LogDebug((object)$"GetTrackedPlaybackSeconds: Returning cached playbackTime={data.playbackTime} (not playing, time <= 0)"); return data.playbackTime; } float time = audioPlayer.GetTime(); Logger.LogDebug((object)$"GetTrackedPlaybackSeconds: Returning audioPlayer.GetTime()={time}, isPlaying={audioPlayer.IsPlaying()}"); return time; } if (data.playbackTime > 0) { Logger.LogDebug((object)$"GetTrackedPlaybackSeconds: Returning cached playbackTime={data.playbackTime} (no clip)"); return data.playbackTime; } float num = Math.Max(0f, (float)(GetCurrentServerTimeMilliseconds() - data.playbackStartTimestamp) / 1000f); Logger.LogDebug((object)$"GetTrackedPlaybackSeconds: Calculating from timestamp: {num}s"); return num; } private void CleanupCurrentPlayback() { StopLocalPlayback(updateSharedFlag: true); } private bool ShouldRequestMasterMutation() { return PhotonNetwork.IsConnected && PhotonNetwork.CurrentRoom != null && !PhotonNetwork.IsMasterClient; } public void SetPlaybackTime(long relativeStartTimeMillis) { float num = Math.Max(0f, (float)(GetCurrentServerTimeMilliseconds() - relativeStartTimeMillis) / 1000f); Logger.LogDebug((object)$"SetPlaybackTime: Setting audioPlayer time to {num}s (from timestamp {relativeStartTimeMillis})"); audioPlayer.SetTime(num); } public void EnqueueSongLocal(string url, int seconds) { if (string.IsNullOrWhiteSpace(url)) { return; } if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestEnqueueSong", (RpcTarget)2, url, seconds, PhotonNetwork.LocalPlayer.ActorNumber); return; } SongInfo info = (DownloadHelper.songInfo.ContainsKey(url) ? DownloadHelper.songInfo[url] : new SongInfo()); AudioEntry audioEntry = new AudioEntry(url, info) { StartTime = seconds }; data.playbackQueue.Add(audioEntry); bool flag = data.currentSong == null; if (flag) { data.currentSong = audioEntry; data.playbackTime = 0; Logger.LogDebug((object)$"Set currentsong local {data.currentSong}"); StopLocalPlayback(updateSharedFlag: true); data.pendingPlaybackStart = true; startPlayBackOnDownload = true; SetPlaybackReferenceFromSeconds(audioEntry.UseStartTime(this)); } PublishSharedState(flag, updateQueue: true); if (PhotonNetwork.IsMasterClient) { downloadHelper.EnqueueDownload(url); downloadHelper.StartDownloadJob(); } } public void CommitPlaybackSeek() { if (data.currentSong != null) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestCommitPlaybackSeek", (RpcTarget)2, audioPlayer.GetTime(), PhotonNetwork.LocalPlayer.ActorNumber); } else { SetPlaybackReferenceFromSeconds(audioPlayer.GetTime()); PublishSharedState(updateTime: true); } } } public void SetPlaybackStateLocal(bool startPlaying) { if (data.currentSong != null) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestSetPlaybackState", (RpcTarget)2, startPlaying, PhotonNetwork.LocalPlayer.ActorNumber); } else if ((Object)(object)data.currentSong.GetAudioClip() == (Object)null) { startPlayBackOnDownload = startPlaying; data.isPlaying = false; data.pendingPlaybackStart = startPlaying; PublishSharedState(updateTime: true); } else { float trackedPlaybackSeconds = GetTrackedPlaybackSeconds(); Logger.LogDebug((object)$"SetPlaybackStateLocal: trackedPlaybackSeconds={trackedPlaybackSeconds}, isPlaying={startPlaying}"); SetPlaybackReferenceFromSeconds(trackedPlaybackSeconds); Logger.LogDebug((object)$"SetPlaybackStateLocal: Final state - isPlaying={startPlaying}, timestamp={data.playbackStartTimestamp}"); data.isPlaying = startPlaying; data.pendingPlaybackStart = false; PublishSharedState(updateTime: true, updateQueue: false, force: true); } } } public void JumpPlaybackBySeconds(float seconds) { if ((Object)(object)data.currentSong.GetAudioClip() == (Object)null) { return; } if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestJumpPlaybackBySeconds", (RpcTarget)2, seconds, PhotonNetwork.LocalPlayer.ActorNumber); return; } float num = Math.Max(0f, GetTrackedPlaybackSeconds() + seconds); if ((Object)(object)audioPlayer?.GetClip() != (Object)null) { num = Math.Min(num, Math.Max(0f, audioPlayer.GetClip().length - 0.5f)); } SetPlaybackReferenceFromSeconds(num); data.pendingPlaybackStart = false; PublishSharedState(updateTime: true); } public void SelectSongIndex(int index) { if (index < 0 || index >= data.playbackQueue.Count) { return; } if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestSelectSongIndex", (RpcTarget)2, index, PhotonNetwork.LocalPlayer.ActorNumber); return; } data.currentSong = data.playbackQueue[index]; StopLocalPlayback(updateSharedFlag: true); data.playbackTime = 0; data.pendingPlaybackStart = true; startPlayBackOnDownload = true; PublishSharedState(updateTime: true); if (PhotonNetwork.IsMasterClient) { string text = data.currentSong?.Url; if (text != null && DownloadHelper.downloadsReady.ContainsKey(text) && DownloadHelper.downloadsReady[text].Count >= Instance.baseListener.GetAllModUsers().Count && data.pendingPlaybackStart) { Logger.LogDebug((object)"Skipping download queue, as all users are ready to play."); FinalizePendingPlaybackStart(startPlayBackOnDownload); } else { downloadHelper.DismissDownloadQueue(); downloadHelper.DownloadQueue(index); } } } public void SetVolumeLocal(float volume) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestSetVolume", (RpcTarget)2, volume, PhotonNetwork.LocalPlayer.ActorNumber); } else { data.absVolume = Mathf.Clamp01(volume); float volume2 = data.absVolume * data.personalVolumePercentage; audioPlayer.SetVolume(volume2); PublishSharedState(updateTime: false); } } public void SetLoopQueueLocal(bool loop) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestSetLoopQueue", (RpcTarget)2, loop, PhotonNetwork.LocalPlayer.ActorNumber); } else { data.loopQueue = loop; PublishSharedState(updateTime: false); } } public void SetUnderglowEnabledLocal(bool enabled) { if (Instance.SyncVisuals.Value) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestSetUnderglowEnabled", (RpcTarget)2, enabled, PhotonNetwork.LocalPlayer.ActorNumber); } else { data.underglowEnabled = enabled; ApplyVisualStateFromData(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); PublishSharedState(updateTime: false); } } else { data.underglowEnabled = enabled; ApplyVisualStateFromData(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); } } public void SetVisualizerEnabledLocal(bool enabled) { if (Instance.SyncVisuals.Value) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestSetVisualizerEnabled", (RpcTarget)2, enabled, PhotonNetwork.LocalPlayer.ActorNumber); } else { data.visualizerEnabled = enabled; ApplyVisualStateFromData(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); PublishSharedState(updateTime: false); } } else { data.visualizerEnabled = enabled; ApplyVisualStateFromData(); ((Component)this).GetComponent()?.UpdateDataFromBoomBox(); } } public void DismissQueueLocal() { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestDismissQueue", (RpcTarget)2, PhotonNetwork.LocalPlayer.ActorNumber); return; } StopLocalPlayback(updateSharedFlag: true); data.playbackQueue.Clear(); data.currentSong = null; data.pendingPlaybackStart = false; data.playbackTime = 0; SetPlaybackReferenceFromSeconds(0f); PublishSharedState(updateTime: true, updateQueue: true); if (PhotonNetwork.IsMasterClient) { downloadHelper.DismissDownloadQueue(); downloadHelper.ForceCancelDownload(); } UpdateUIStatus("Ready to play music! Enter a Video URL"); } public void MoveQueueItemLocal(int index, int newIndex) { if (index >= 0 && index < data.playbackQueue.Count && newIndex >= 0 && newIndex < data.playbackQueue.Count && index != newIndex) { if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestMoveQueueItem", (RpcTarget)2, index, newIndex, PhotonNetwork.LocalPlayer.ActorNumber); } else { AudioEntry item = data.playbackQueue[index]; data.playbackQueue.RemoveAt(index); data.playbackQueue.Insert(newIndex, item); PublishSharedState(updateTime: false, updateQueue: true); } } } public void RemoveQueueItemLocal(int index) { if (index < 0 || index >= data.playbackQueue.Count) { return; } if (ShouldRequestMasterMutation()) { BaseListener.RPC(photonView, "RequestRemoveQueueItem", (RpcTarget)2, index, PhotonNetwork.LocalPlayer.ActorNumber); return; } AudioEntry audioEntry = data.playbackQueue[index]; bool flag = audioEntry == data.currentSong; data.playbackQueue.RemoveAt(index); if (flag) { if (data.playbackQueue.Count == 0) { data.currentSong = null; data.isPlaying = false; data.playbackTime = 0; data.pendingPlaybackStart = false; SetPlaybackReferenceFromSeconds(0f); } else { int index2 = Math.Min(index, data.playbackQueue.Count - 1); if (index >= data.playbackQueue.Count && LoopQueue) { index2 = 0; } data.currentSong = data.playbackQueue[index2]; data.isPlaying = false; data.playbackTime = 0; data.pendingPlaybackStart = true; startPlayBackOnDownload = true; SetPlaybackReferenceFromSeconds(data.currentSong.UseStartTime(this)); } } if (PhotonNetwork.IsMasterClient && data.currentSong?.Url != null) { downloadHelper.DismissDownloadQueue(); downloadHelper.DownloadQueue(GetCurrentSongIndex()); } PublishSharedState(flag, updateQueue: true); } public void FinalizePendingPlaybackStart(bool startPlaying) { if (data.currentSong != null) { data.pendingPlaybackStart = false; data.isPlaying = startPlaying; SetPlaybackReferenceFromSeconds(data.currentSong.UseStartTime(this)); startPlayBackOnDownload = true; PublishSharedState(updateTime: true, updateQueue: false, force: true); } } [PunRPC] public void RequestEnqueueSong(string url, int seconds, int requesterId) { if (PhotonNetwork.IsMasterClient) { EnqueueSongLocal(url, seconds); } } [PunRPC] public void RequestCommitPlaybackSeek(float playbackSeconds, int requesterId) { if (PhotonNetwork.IsMasterClient && data.currentSong != null) { SetPlaybackReferenceFromSeconds(playbackSeconds); PublishSharedState(updateTime: true); } } [PunRPC] public void RequestSetPlaybackState(bool startPlaying, int requesterId) { if (PhotonNetwork.IsMasterClient) { SetPlaybackStateLocal(startPlaying); } } [PunRPC] public void RequestJumpPlaybackBySeconds(float seconds, int requesterId) { if (PhotonNetwork.IsMasterClient) { JumpPlaybackBySeconds(seconds); } } [PunRPC] public void RequestSelectSongIndex(int index, int requesterId) { if (PhotonNetwork.IsMasterClient) { SelectSongIndex(index); } } [PunRPC] public void RequestSetVolume(float volume, int requesterId) { if (PhotonNetwork.IsMasterClient) { SetVolumeLocal(volume); } } [PunRPC] public void RequestSetLoopQueue(bool loop, int requesterId) { if (PhotonNetwork.IsMasterClient) { SetLoopQueueLocal(loop); } } [PunRPC] public void RequestSetUnderglowEnabled(bool enabled, int requesterId) { if (PhotonNetwork.IsMasterClient && Instance.SyncVisuals.Value) { SetUnderglowEnabledLocal(enabled); } } [PunRPC] public void RequestSetVisualizerEnabled(bool enabled, int requesterId) { if (PhotonNetwork.IsMasterClient && Instance.SyncVisuals.Value) { SetVisualizerEnabledLocal(enabled); } } [PunRPC] public void RequestDismissQueue(int requesterId) { if (PhotonNetwork.IsMasterClient && (!Instance.MasterClientDismissQueue.Value || PhotonNetwork.MasterClient.ActorNumber == requesterId)) { DismissQueueLocal(); } } [PunRPC] public void RequestMoveQueueItem(int index, int newIndex, int requesterId) { if (PhotonNetwork.IsMasterClient) { MoveQueueItemLocal(index, newIndex); } } [PunRPC] public void RequestRemoveQueueItem(int index, int requesterId) { if (PhotonNetwork.IsMasterClient) { RemoveQueueItemLocal(index); } } public void HandleDownloadedCurrentSong() { if (!((Object)(object)data.currentSong?.GetAudioClip() == (Object)null)) { ApplySharedPlaybackState(); } } public double EstimateDownloadTimeSeconds(double durationSeconds, int downloadSpeedMbps) { int bitrateKbps = DownloadHelper.GetBitrateKbps(ApplyQualityToDownloads ? 4 : AudioPlayer.GetQuality()); double num = (double)bitrateKbps * 1000.0 / 8.0 * durationSeconds; double num2 = (double)Math.Max(1, downloadSpeedMbps) / 8.0 * 1024.0 * 1024.0; double num3 = num / num2 * 2.5; double num4 = durationSeconds * 0.005; return (5.0 + num3 + num4) * 1.3; } public void UpdateUIStatus(string message) { if (!((Object)(object)this == (Object)null)) { BoomboxUI component = ((Component)this).GetComponent(); if ((Object)(object)component != (Object)null && component.IsUIVisible()) { component.UpdateStatus(message); } } } public override void OnPlayerEnteredRoom(Player newPlayer) { ((MonoBehaviourPunCallbacks)this).OnPlayerEnteredRoom(newPlayer); if (PhotonNetwork.IsMasterClient && !Instance.modDisabled) { PhotonView obj = ((MonoBehaviourPun)Instance.baseListener).photonView; if (obj != null) { obj.RPC("BaseListener", newPlayer, new object[2] { "1.3.4", PhotonNetwork.LocalPlayer.ActorNumber }); } } } private void OnDisable() { PersistentData.RemoveBoomboxViewInitialized(photonView.ViewID); data.playbackTime = (int)Math.Round(audioPlayer.GetTime()); } private void OnDestroy() { Instance.data.GetAllBoomboxes().Remove(this); Object.Destroy((Object)(object)((Component)this).GetComponent()); if ((Object)(object)visualizer != (Object)null) { Object.Destroy((Object)(object)visualizer); } Object.Destroy((Object)(object)((Component)this).gameObject.GetComponent()); Object.Destroy((Object)(object)audioPlayer); Object.Destroy((Object)(object)downloadHelper); Object.Destroy((Object)(object)((Component)this).gameObject.GetComponent()); photonView.RefreshRpcMonoBehaviourCache(); } public void ResetData() { if (PhotonNetwork.IsMasterClient) { Logger.LogInfo((object)$"Resetting Boombox {photonView.ViewID}"); startPlayBackOnDownload = true; data.pendingPlaybackStart = false; UpdateUIStatus("Ready to play music! Enter a Video URL"); CleanupCurrentPlayback(); downloadHelper.DismissDownloadQueue(); List boomboxData = Instance.data.GetBoomboxData(); int index = boomboxData.Count; if (boomboxData.Contains(data)) { index = boomboxData.IndexOf(data); boomboxData.Remove(data); } int stateVersion = data.stateVersion; data = new BoomboxData(); data.stateVersion = stateVersion + 1; ApplyVisualStateFromData(); if (Instance.RestoreBoomboxes.Value) { boomboxData.Insert(index, data); } PublishSharedState(updateTime: true, updateQueue: true, force: true); } } } 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(); boombox = ((Component)this).GetComponent(); 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; BaseListener.RPC(((MonoBehaviourPun)this).photonView, "RequestControl", (RpcTarget)2, 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 || !Instance.baseListener.GetAllModUsers().Contains(requesterId)) && flag) { BaseListener.RPC(((MonoBehaviourPun)this).photonView, "SetController", (RpcTarget)0, 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(); if ((Object)(object)boomboxUI == (Object)null) { boomboxUI = ((Component)this).gameObject.AddComponent(); } } } public void ReleaseControl() { if (currentControllerId == PhotonNetwork.LocalPlayer.ActorNumber) { BaseListener.RPC(((MonoBehaviourPun)this).photonView, "RequestRelease", (RpcTarget)2, PhotonNetwork.LocalPlayer.ActorNumber); } } [PunRPC] private void RequestRelease(int releaserId) { if (PhotonNetwork.IsMasterClient && currentControllerId == releaserId) { BaseListener.RPC(((MonoBehaviourPun)this).photonView, "SetController", (RpcTarget)0, -1); } } private void OnPlayerReleasedCart(int playerActorNumber) { if (playerActorNumber == currentControllerId && PhotonNetwork.IsMasterClient) { BaseListener.RPC(((MonoBehaviourPun)this).photonView, "SetController", (RpcTarget)0, -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; public static bool anyUISHown = false; public bool showUI = false; private string urlInput = ""; private bool isTimeSliderBeingDragged = false; private int songIndexForTime = -2; private float songTimePerc = 0f; private float lastSentSongTimePerc = -1f; private float lastSentVolume = 0.3f; private bool isVolumeSliderBeingDragged = false; private bool isIndividualVolumeBeingDragged = false; private int qualityLevel = 3; private string[] qualityLabels = new string[5] { "REALLY Low (You Freak)", "Low", "Medium-Low", "Medium-High", "High" }; private bool isQualitySliderBeingDragged = false; private int lastSentQualityLevel = 3; private Rect windowRect; private Boombox boombox; private BoomboxController controller; private VisualEffects visualEffects; private Visualizer visualizer; private GUIStyle windowStyle; private GUIStyle headerStyle; private GUIStyle buttonStyle; private GUIStyle smallButtonStyle; private GUIStyle textFieldStyle; private GUIStyle labelStyle; private GUIStyle sliderStyle; private GUIStyle statusStyle; private GUIStyle scrollViewStyle; private GUIStyle queueHeaderStyle; private GUIStyle queueEntryStyle; private GUIStyle currentSongStyle; private Texture2D backgroundTexture; private Texture2D buttonTexture; private Texture2D sliderBackgroundTexture; private Texture2D sliderThumbTexture; private Texture2D textFieldBackgroundTexture; private Vector2 urlScrollPosition = Vector2.zero; private float textFieldVisibleWidth = 350f; private string errorMessage = ""; private float errorMessageTime = 0f; public string statusMessage = ""; private CursorLockMode previousLockMode; private bool previousCursorVisible; private bool stylesInitialized = false; private Vector2 scrollPosition = Vector2.zero; private Vector2 queueScrollPosition = Vector2.zero; private const float refreshHoldTime = 5f; private const int maxRefreshSymbols = 5; private float? refreshObjectStart = null; private bool refreshObjectSent = false; private string lastUrl = null; private static ManualLogSource Logger => Instance.logger; private void Awake() { //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) try { boombox = ((Component)this).GetComponent(); if ((Object)(object)boombox != (Object)null) { photonView = boombox.photonView; } else { Logger.LogError((object)"BoomboxUI: Failed to find Boombox component"); photonView = ((Component)this).GetComponent(); } controller = ((Component)this).GetComponent(); visualEffects = ((Component)this).GetComponent(); if ((Object)(object)visualEffects == (Object)null) { visualEffects = ((Component)this).gameObject.AddComponent(); } visualizer = ((Component)this).GetComponent(); boombox?.ApplyVisualStateFromData(); RefreshVisualComponentRefs(); if ((Object)(object)photonView == (Object)null) { Logger.LogError((object)"BoomboxUI: Failed to find PhotonView component"); } windowRect = new Rect((float)(Screen.width / 2 - 400), (float)(Screen.height / 2 - 175), 800f, 550f); } 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 (isTimeSliderBeingDragged && ((Mouse.current != null && Mouse.current.leftButton.wasReleasedThisFrame) || songIndexForTime != boombox.GetCurrentSongIndex())) { isTimeSliderBeingDragged = false; SendTimeUpdate(); songIndexForTime = -2; songTimePerc = 0f; lastSentSongTimePerc = -1f; } if (isVolumeSliderBeingDragged && Mouse.current != null && Mouse.current.leftButton.wasReleasedThisFrame) { isVolumeSliderBeingDragged = false; SendVolumeUpdate(); } if (isIndividualVolumeBeingDragged && Mouse.current != null && Mouse.current.leftButton.wasReleasedThisFrame) { isIndividualVolumeBeingDragged = false; } if (isQualitySliderBeingDragged && Mouse.current != null && Mouse.current.leftButton.wasReleasedThisFrame) { isQualitySliderBeingDragged = false; SendQualityUpdate(); } if (refreshObjectStart.HasValue && Mouse.current != null && Mouse.current.leftButton.wasReleasedThisFrame) { refreshObjectStart = null; refreshObjectSent = false; } } 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) { if ((Object)(object)boombox == (Object)null || (Object)(object)photonView == (Object)null) { Logger.LogError((object)"Cannot show UI - boombox or photonView is null"); return; } anyUISHown = true; showUI = true; previousLockMode = Cursor.lockState; previousCursorVisible = Cursor.visible; Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; UpdateDataFromBoomBox(); UpdateStatusFromBoombox(); } } public void UpdateDataFromBoomBox() { if ((Object)(object)boombox != (Object)null) { lastSentVolume = boombox.data.absVolume; qualityLevel = AudioPlayer.GetQuality(); lastSentQualityLevel = qualityLevel; RefreshVisualComponentRefs(); } } private void RefreshVisualComponentRefs() { visualEffects = ((Component)this).GetComponent(); visualizer = ((Component)this).GetComponent(); } public void UpdateStatusFromBoombox() { if ((Object)(object)boombox != (Object)null) { if (YoutubeDL.IsUpdatingResources) { statusMessage = YoutubeDL.ResourceUpdateStatus; } else if (boombox.downloadHelper.IsProcessingQueue() && boombox.data.currentSong != null && (Object)(object)boombox.data.currentSong.GetAudioClip() == (Object)null) { statusMessage = "Downloading audio from " + boombox.downloadHelper.GetCurrentDownloadUrl() + "..."; } else if (boombox.data.currentSong != null && boombox.data.pendingPlaybackStart) { statusMessage = "Loading: " + boombox.data.currentSong.Title; } else if (boombox.data.currentSong != null && boombox.data.isPlaying) { statusMessage = "Now playing: " + boombox.data.currentSong.Title; } else if (!string.IsNullOrEmpty(boombox.data.currentSong?.Url)) { statusMessage = "Ready to play: " + boombox.data.currentSong.Title; } else { statusMessage = "Ready to play music! Enter a Video URL"; } } } private void SendTimeUpdate() { if (songTimePerc != lastSentSongTimePerc) { lastSentSongTimePerc = songTimePerc; if (boombox?.GetCurrentSongIndex() == songIndexForTime && songIndexForTime != -1 && (Object)(object)boombox?.audioPlayer?.GetClip() != (Object)null && boombox.audioPlayer.GetClip().length > 0f) { float time = Math.Max(0f, Math.Min(songTimePerc * boombox.audioPlayer.GetClip().length, boombox.audioPlayer.GetClip().length - 0.05f)); boombox.audioPlayer.SetTime(time); boombox.CommitPlaybackSeek(); } else if (songIndexForTime != boombox.GetCurrentSongIndex()) { UpdateDataFromBoomBox(); } } } private void SendVolumeUpdate() { if (boombox.data.absVolume != lastSentVolume) { lastSentVolume = boombox.data.absVolume; boombox.SetVolumeLocal(boombox.data.absVolume); } } private void SendQualityUpdate() { if (qualityLevel != lastSentQualityLevel) { lastSentQualityLevel = qualityLevel; if ((Object)(object)boombox != (Object)null) { boombox.audioPlayer?.SetQuality(qualityLevel); } } } public void HideUI() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (showUI) { anyUISHown = false; showUI = false; Cursor.lockState = previousLockMode; Cursor.visible = previousCursorVisible; } } public bool IsUIVisible() { return showUI; } public void UpdateStatus(string message) { statusMessage = message; } 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 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Expected O, but got Unknown //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Expected O, but got Unknown //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0255: 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_0290: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Expected O, but got Unknown //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Expected O, but got Unknown //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Expected O, but got Unknown //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Expected O, but got Unknown //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Expected O, but got Unknown //IL_0337: 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_039d: Unknown result type (might be due to invalid IL or missing references) //IL_03a7: Expected O, but got Unknown //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Expected O, but got Unknown //IL_03c8: Unknown result type (might be due to invalid IL or missing references) //IL_03d2: Expected O, but got Unknown //IL_03dd: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Expected O, but got Unknown //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_0421: Expected O, but got Unknown //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_046a: Unknown result type (might be due to invalid IL or missing references) //IL_0474: Expected O, but got Unknown //IL_0492: Unknown result type (might be due to invalid IL or missing references) //IL_049c: Expected O, but got Unknown //IL_04c2: Unknown result type (might be due to invalid IL or missing references) //IL_04cc: Expected O, but got Unknown //IL_04d4: Unknown result type (might be due to invalid IL or missing references) //IL_04de: Expected O, but got Unknown //IL_04fe: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Unknown result type (might be due to invalid IL or missing references) //IL_0552: Unknown result type (might be due to invalid IL or missing references) //IL_055c: Expected O, but got Unknown //IL_057c: Unknown result type (might be due to invalid IL or missing references) //IL_0597: Unknown result type (might be due to invalid IL or missing references) 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)); textFieldBackgroundTexture = CreateColorTexture(new Color(0.15f, 0.17f, 0.2f, 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; smallButtonStyle = new GUIStyle(buttonStyle); smallButtonStyle.padding = new RectOffset(8, 8, 4, 4); smallButtonStyle.fontSize = 12; textFieldStyle = new GUIStyle(GUI.skin.textField); textFieldStyle.normal.background = textFieldBackgroundTexture; textFieldStyle.normal.textColor = new Color(1f, 1f, 1f); textFieldStyle.fontSize = 14; textFieldStyle.padding = new RectOffset(10, 10, 8, 8); scrollViewStyle = new GUIStyle(GUI.skin.scrollView); scrollViewStyle.normal.background = textFieldBackgroundTexture; scrollViewStyle.border = new RectOffset(2, 2, 2, 2); scrollViewStyle.padding = new RectOffset(0, 0, 0, 0); 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; queueHeaderStyle = new GUIStyle(headerStyle); queueHeaderStyle.fontSize = 16; queueHeaderStyle.alignment = (TextAnchor)3; queueHeaderStyle.margin = new RectOffset(5, 0, 10, 5); queueEntryStyle = new GUIStyle(textFieldStyle); queueEntryStyle.normal.background = CreateColorTexture(new Color(0.1f, 0.1f, 0.1f, 0.7f)); queueEntryStyle.hover.background = CreateColorTexture(new Color(0.2f, 0.2f, 0.2f, 0.8f)); queueEntryStyle.alignment = (TextAnchor)3; currentSongStyle = new GUIStyle(queueEntryStyle); currentSongStyle.normal.background = CreateColorTexture(new Color(0.1f, 0.3f, 0.1f, 0.9f)); currentSongStyle.normal.textColor = Color.yellow; currentSongStyle.hover.background = currentSongStyle.normal.background; stylesInitialized = true; } } 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()); } } private void DrawUI(int windowID) { //IL_004b: 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) if (boombox?.data == null) { return; } object obj; if (!Instance.baseListener.audioMuted) { obj = ""; } else { Key value = Instance.GlobalMuteKey.Value; obj = " - MUTED(" + ((object)(Key)(ref value)).ToString() + ")"; } GUILayout.Label("Control The Boombox In The Cart" + (string?)obj, headerStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(400f) }); DrawMainPanel(boombox); GUILayout.EndVertical(); GUILayout.BeginVertical((GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(420f), GUILayout.ExpandHeight(true) }); DrawQueue(boombox); GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("Close", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(300f), GUILayout.Height(36f) })) { if ((Object)(object)controller != (Object)null) { controller.ReleaseControl(); } else { HideUI(); } } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUI.DragWindow(); } private void DrawMainPanel(Boombox boombox) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_06d5: Unknown result type (might be due to invalid IL or missing references) //IL_0647: Unknown result type (might be due to invalid IL or missing references) //IL_0669: Unknown result type (might be due to invalid IL or missing references) //IL_06dc: Unknown result type (might be due to invalid IL or missing references) //IL_06e1: Unknown result type (might be due to invalid IL or missing references) //IL_06ea: Unknown result type (might be due to invalid IL or missing references) //IL_0814: Unknown result type (might be due to invalid IL or missing references) //IL_081b: Unknown result type (might be due to invalid IL or missing references) //IL_0820: Unknown result type (might be due to invalid IL or missing references) //IL_0829: Unknown result type (might be due to invalid IL or missing references) //IL_0930: Unknown result type (might be due to invalid IL or missing references) //IL_0937: Unknown result type (might be due to invalid IL or missing references) //IL_093c: Unknown result type (might be due to invalid IL or missing references) //IL_0945: Unknown result type (might be due to invalid IL or missing references) scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.Space(10f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Enter Video URL:", labelStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (GUILayout.Button("Clear", smallButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) })) { urlInput = ""; GUI.FocusControl((string)null); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); urlScrollPosition = GUILayout.BeginScrollView(urlScrollPosition, false, false, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(60f) }); urlInput = GUILayout.TextField(urlInput, textFieldStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(34f) }); urlInput = Regex.Replace(urlInput, "\\s+", ""); GUILayout.EndScrollView(); GUILayout.EndHorizontal(); float num = 0f; float num2 = 0f; float num3 = 0f; string text = "??"; bool flag = (Object)(object)boombox != (Object)null && (Object)(object)boombox.audioPlayer?.GetClip() != (Object)null; if (flag) { num = boombox.audioPlayer.audioSource.time; num2 = boombox.audioPlayer.GetClip().length; if (num2 > 0f) { num3 = num / num2; text = PrintTime(num2); } else { flag = false; } } GUILayout.Label(PrintTime(num) + " / " + text, labelStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); Rect lastRect; if (flag && (int)Event.current.type == 0) { lastRect = GUILayoutUtility.GetLastRect(); if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition)) { isTimeSliderBeingDragged = true; } } float num4 = GUILayout.HorizontalSlider(num3, 0f, 1f, sliderStyle, GUI.skin.horizontalSliderThumb, Array.Empty()); int currentSongIndex = boombox.GetCurrentSongIndex(); if (flag && currentSongIndex != -1 && num4 != num3) { if (!isTimeSliderBeingDragged) { isTimeSliderBeingDragged = true; } if (songIndexForTime == -2) { songIndexForTime = currentSongIndex; } if (songIndexForTime == currentSongIndex) { float time = Math.Max(0f, Math.Min(num4 * num2, boombox.audioPlayer.GetClip().length - 0.05f)); boombox.audioPlayer.audioSource.time = time; songTimePerc = num4; } } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("<<", smallButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(40f), GUILayout.Height(40f) }) && (Object)(object)boombox != (Object)null && boombox.data.currentSong != null) { boombox.JumpPlaybackBySeconds(-10f); } string text2 = ((boombox?.data != null && boombox.data.playbackQueue.Count > 0) ? "+ ENQUEUE" : "▶ PLAY"); if (GUILayout.Button(text2, buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) })) { var (text3, seconds) = IsValidVideoUrl(urlInput); if (string.IsNullOrEmpty(text3)) { ShowErrorMessage("Invalid Video URL!"); } else if (lastUrl != text3) { lastUrl = text3; boombox.EnqueueSongLocal(text3, seconds); GUI.FocusControl((string)null); } } string text4 = ((boombox.data.currentSong == null) ? "..." : (boombox.data.isPlaying ? "▌▌ PAUSE" : "▶ RESUME")); if (GUILayout.Button(text4, buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(40f) }) && boombox.data.currentSong != null) { boombox.SetPlaybackStateLocal(!boombox.data.isPlaying); } if (GUILayout.Button(">>", smallButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(40f), GUILayout.Height(40f) }) && (Object)(object)boombox != (Object)null && boombox.data.currentSong != null) { boombox.JumpPlaybackBySeconds(10f); } GUILayout.EndHorizontal(); if ((Object)(object)boombox != (Object)null && boombox.downloadHelper.IsProcessingQueue() && (Object)(object)boombox.data.currentSong?.GetAudioClip() == (Object)null && boombox.downloadHelper.GetCurrentDownloadUrl() != null) { GUILayout.Space(10f); GUILayout.Label("Download in progress...", statusStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("Force Cancel Download", buttonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(200f), GUILayout.Height(30f) })) { boombox.downloadHelper.ForceCancelDownload(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } GUILayout.Space(15f); if (!string.IsNullOrEmpty(statusMessage)) { GUILayout.Label(statusMessage, statusStyle, Array.Empty()); GUILayout.Space(5f); } if (!string.IsNullOrEmpty(errorMessage)) { GUI.color = Color.red; GUILayout.Label(errorMessage, labelStyle, Array.Empty()); GUI.color = Color.white; GUILayout.Space(5f); } GUILayout.Space(15f); float num5 = boombox.data.absVolume * 100f; GUILayout.Label($"Volume: {Mathf.Round(num5)}%", labelStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if ((int)Event.current.type == 0) { lastRect = GUILayoutUtility.GetLastRect(); if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition)) { isVolumeSliderBeingDragged = true; } } float num6 = GUILayout.HorizontalSlider(boombox.data.absVolume, 0f, 1f, sliderStyle, GUI.skin.horizontalSliderThumb, Array.Empty()); if (num6 != boombox.data.absVolume) { if (!isVolumeSliderBeingDragged) { isVolumeSliderBeingDragged = true; } if ((Object)(object)boombox.audioPlayer?.audioSource != (Object)null) { float volume = boombox.data.absVolume * boombox.data.personalVolumePercentage; boombox.audioPlayer.SetVolume(volume); } boombox.data.absVolume = num6; } GUILayout.EndHorizontal(); GUILayout.Space(15f); GUILayout.Label($"Personal Volume Multiplier: {Mathf.Round(boombox.data.personalVolumePercentage * 100.001f)}%", labelStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if ((int)Event.current.type == 0) { lastRect = GUILayoutUtility.GetLastRect(); if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition)) { isIndividualVolumeBeingDragged = true; } } float num7 = GUILayout.HorizontalSlider(boombox.data.personalVolumePercentage, 0f, 1f, sliderStyle, GUI.skin.horizontalSliderThumb, Array.Empty()); if (num7 != boombox.data.personalVolumePercentage) { isIndividualVolumeBeingDragged = true; boombox.data.personalVolumePercentage = num7; if ((Object)(object)boombox.audioPlayer?.audioSource != (Object)null) { boombox.audioPlayer.SetVolume(boombox.data.absVolume * boombox.data.personalVolumePercentage); } } GUILayout.EndHorizontal(); GUILayout.Space(15f); GUILayout.Label("Audio Quality: " + qualityLabels[qualityLevel], labelStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if ((int)Event.current.type == 0) { lastRect = GUILayoutUtility.GetLastRect(); if (((Rect)(ref lastRect)).Contains(Event.current.mousePosition)) { isQualitySliderBeingDragged = true; } } float num8 = GUILayout.HorizontalSlider((float)qualityLevel, 0f, 4f, sliderStyle, GUI.skin.horizontalSliderThumb, Array.Empty()); int num9 = Mathf.RoundToInt(num8); if (num9 != qualityLevel && !isQualitySliderBeingDragged) { isQualitySliderBeingDragged = true; } qualityLevel = num9; if ((Object)(object)boombox?.audioPlayer != (Object)null) { boombox.audioPlayer.SetQuality(qualityLevel); } GUILayout.EndHorizontal(); GUILayout.Space(10f); bool applyQualityToDownloads = Boombox.ApplyQualityToDownloads; bool flag2 = GUILayout.Toggle(applyQualityToDownloads, "Apply Quality Setting to Downloads", Array.Empty()); if (flag2 != applyQualityToDownloads) { Boombox.ApplyQualityToDownloads = flag2; } GUILayout.Space(10f); bool monstersCanHearMusic = Boombox.MonstersCanHearMusic; bool flag3 = GUILayout.Toggle(monstersCanHearMusic, "Monsters can hear audio", Array.Empty()); if (flag3 != monstersCanHearMusic) { Boombox.MonstersCanHearMusic = flag3; } GUILayout.Space(10f); bool loopQueue = boombox.LoopQueue; bool flag4 = GUILayout.Toggle(loopQueue, "Loop queue", Array.Empty()); if (flag4 != loopQueue && PhotonNetwork.IsMasterClient) { boombox.SetLoopQueueLocal(flag4); } GUILayout.Space(10f); bool underglowEnabled = boombox.data.underglowEnabled; bool flag5 = GUILayout.Toggle(underglowEnabled, "RGB Underglow enabled", Array.Empty()); if (flag5 != underglowEnabled) { boombox.SetUnderglowEnabledLocal(flag5); UpdateDataFromBoomBox(); } GUILayout.Space(10f); bool visualizerEnabled = boombox.data.visualizerEnabled; bool flag6 = GUILayout.Toggle(visualizerEnabled, "Audio Visualizer enabled", Array.Empty()); if (flag6 != visualizerEnabled) { boombox.SetVisualizerEnabledLocal(flag6); UpdateDataFromBoomBox(); } GUILayout.EndScrollView(); } private void DrawQueue(Boombox boombox) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(" Playback Queue", queueHeaderStyle, Array.Empty()); if (PhotonNetwork.IsMasterClient) { float num = Time.time - (refreshObjectStart ?? Time.time); int num2 = 1 + (int)Mathf.Floor(num / 1f); string text = ((num > 0f && num <= 5f) ? (new string('!', num2) + new string('.', Math.Max(0, 5 - num2))) : "RESET"); if (GUILayout.RepeatButton(text, smallButtonStyle, Array.Empty())) { if (!refreshObjectStart.HasValue) { refreshObjectStart = Time.time; } else if (!refreshObjectSent && num >= 5f) { boombox.ResetData(); lastUrl = null; refreshObjectSent = true; } } } if (GUILayout.Button("Dismiss Queue", smallButtonStyle, Array.Empty())) { boombox.DismissQueueLocal(); lastUrl = null; } GUILayout.EndHorizontal(); queueScrollPosition = GUILayout.BeginScrollView(queueScrollPosition, false, true, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); List playbackQueue = boombox.data.playbackQueue; int currentSongIndex = boombox.GetCurrentSongIndex(); if (playbackQueue.Count == 0) { GUILayout.Label(" Queue is empty and no song is playing.", labelStyle, Array.Empty()); } for (int i = 0; i < playbackQueue.Count; i++) { Boombox.AudioEntry audioEntry = playbackQueue[i]; bool flag = i == currentSongIndex; GUIStyle val = (flag ? currentSongStyle : queueEntryStyle); string text2 = (flag ? "▶ " : $"{i - ((currentSongIndex != -1) ? currentSongIndex : 0)}. "); string text3 = ClipText(text2 + audioEntry.Title, 280f, val); GUILayout.BeginHorizontal(val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(32f) }); if (GUILayout.Button(text3, val, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(32f) }) && !flag) { boombox.SelectSongIndex(i); } if (!flag) { if (i > 0) { if (GUILayout.Button("▲", smallButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(25f), GUILayout.Height(28f) })) { boombox.MoveQueueItemLocal(i, i - 1); } } else { GUILayout.Space(30f); } if (i + 1 < playbackQueue.Count) { if (GUILayout.Button("▼", smallButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(25f), GUILayout.Height(28f) })) { boombox.MoveQueueItemLocal(i, i + 1); } } else { GUILayout.Space(30f); } if (GUILayout.Button("X", smallButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(25f), GUILayout.Height(28f) })) { boombox.RemoveQueueItemLocal(i); } } GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); } private string ClipText(string text, float maxWidth, GUIStyle style) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0008: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) Vector2 val = style.CalcSize(new GUIContent(text)); string text2 = text; int length = text.Length; while (val.x > maxWidth) { text2 = text2.Substring(0, text2.Length - 4) + "..."; val = style.CalcSize(new GUIContent(text2)); } return text2; } private string PrintTime(float time) { return $"{(int)Math.Floor(time / 60f)}:{(int)(time % 60f)}"; } private (string cleanedUrl, int seconds) IsValidVideoUrl(string url) { return DownloadHelper.IsValidVideoUrl(url); } private void ShowErrorMessage(string message) { errorMessage = message; errorMessageTime = Time.time + 3f; } private void OnDestroy() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (showUI) { anyUISHown = false; showUI = false; Cursor.lockState = previousLockMode; Cursor.visible = previousCursorVisible; } 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); } if ((Object)(object)textFieldBackgroundTexture != (Object)null) { Object.Destroy((Object)(object)textFieldBackgroundTexture); } } } public class DownloadHelper : MonoBehaviourPunCallbacks { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; private Boombox boomboxParent = null; public static Dictionary downloadedClips = new Dictionary(); public static Dictionary songInfo = new Dictionary(); public static Dictionary> downloadsReady = new Dictionary>(); public static Dictionary> downloadErrors = new Dictionary>(); public const int INITIAL_DOWNLOAD_TIMEOUT = 11; private const int TIMEOUT_THRESHOLD = 10; private Dictionary timeoutCoroutines = new Dictionary(); private Queue downloadJobQueue = new Queue(); private bool isProcessingQueue = false; private string currentRequestId = null; private string currentDownloadUrl = null; private bool isTimeoutRecovery = false; private Coroutine processingCoroutine; private static readonly Regex[] supportedVideoUrlRegexes = new Regex[5] { new Regex("^(?((?:https?:)?\\/\\/)?(((?:www|m)\\.)?((?:youtube(?:-nocookie)?\\.com|youtu\\.be))|music\\.youtube\\.com)(\\/(?:[\\w\\-]+\\?v=|embed\\/|live\\/|v\\/)?)([\\w\\-]+))(?(&(\\S+&)*?(t=(?(?\\d+)))\\S*)?\\S*?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), new Regex("^(?((?:https?:)?\\/\\/)?((?:www)?\\.?)(rutube\\.ru)(\\/video\\/)([\\w\\-]+))(?(\\?(?:\\S+&)*?(t=(?(?\\d+)))\\S*)?\\S*?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), new Regex("^(?((?:https?:)?\\/\\/)?((?:www)?\\.?)(music\\.yandex\\.ru)(\\/album\\/\\d+\\/track\\/)([\\w\\-]+))(?(?:\\?(\\S+&)*?(t=(?(?\\d+)))\\S*)?\\S*?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), new Regex("^(?((?:https?:)?\\/\\/)?((?:www|m)\\.)?(bilibili\\.com)(\\/video\\/)([\\w\\-]+))(?(\\?(?:\\S+&)*?(t=(?(?\\d+)))\\S*)?\\S*?)$", RegexOptions.IgnoreCase | RegexOptions.Compiled), new Regex("^(?((?:https?:)?\\/\\/)?((?:www|m)\\.)?(soundcloud\\.com|snd\\.sc)\\/([\\w\\-]+\\/[\\w\\-]+))(?(?:\\?(?:\\S+&*?#)*?t=(?(?\\d+)(?:\\/|(?:%3A))(?\\d{1,2})))?\\S*)?$", RegexOptions.IgnoreCase | RegexOptions.Compiled) }; private static ManualLogSource Logger => Instance.logger; private void Awake() { boomboxParent = ((Component)this).gameObject.GetComponent(); } private void OnDestroy() { downloadJobQueue.Clear(); foreach (Coroutine value in timeoutCoroutines.Values) { if (value != null) { ((MonoBehaviour)this).StopCoroutine(value); } } timeoutCoroutines.Clear(); } public bool IsProcessingQueue() { return isProcessingQueue; } public string GetCurrentDownloadUrl() { return currentDownloadUrl; } public static (string cleanedUrl, int seconds) IsValidVideoUrl(string url) { if (!string.IsNullOrWhiteSpace(url)) { Regex[] array = supportedVideoUrlRegexes; foreach (Regex regex in array) { Match match = regex.Match(url); if (!match.Success) { continue; } Group group = match.Groups["CleanedUrl"]; if (!group.Success) { continue; } Group group2 = match.Groups["TimeStamp"]; int num = 0; if (group2.Success) { Group group3 = match.Groups["Seconds"]; Group group4 = match.Groups["Minutes"]; if (group3.Success && int.TryParse(group3.Value, out var result)) { num = result; } if (group4.Success && int.TryParse(group4.Value, out var result2)) { num += result2 * 60; } } return (group.Value, num); } } return (null, 0); } public static int CheckDownloadCount(string url, bool includeErrors = false) { if (string.IsNullOrEmpty(url)) { return 0; } int num = 0; if (downloadsReady.ContainsKey(url)) { num += downloadsReady[url].Count; } if (downloadErrors.ContainsKey(url)) { num += downloadErrors[url].Count; } return num; } public void EnqueueDownload(string Url) { downloadJobQueue.Enqueue(Url); } public void DismissDownloadQueue() { downloadJobQueue.Clear(); } public void StartDownloadJob() { if (!isProcessingQueue) { processingCoroutine = ((MonoBehaviour)this).StartCoroutine(ProcessDownloadQueue()); } } public static int GetBitrateKbps(string quality) { if (1 == 0) { } int result = quality switch { "32K" => 32, "64K" => 64, "96K" => 96, "128K" => 128, _ => 192, }; if (1 == 0) { } return result; } public static int GetBitrateKbps(int qualityLevel) { if (1 == 0) { } int result = qualityLevel switch { 0 => 32, 1 => 64, 2 => 96, 3 => 128, _ => 192, }; if (1 == 0) { } return result; } public static double EstimateSizeBytes(int bitrateKbps, double durationSeconds) { return (double)bitrateKbps * 1000.0 / 8.0 * durationSeconds; } public void DownloadQueue(int startIndex) { if (startIndex < 0 || startIndex >= boomboxParent.data.playbackQueue.Count) { startIndex = 0; } for (int i = startIndex; i < boomboxParent.data.playbackQueue.Count; i++) { downloadJobQueue.Enqueue(boomboxParent.data.playbackQueue.ElementAt(i).Url); } for (int j = 0; j < startIndex; j++) { downloadJobQueue.Enqueue(boomboxParent.data.playbackQueue.ElementAt(j).Url); } StartDownloadJob(); } [PunRPC] public void NotifyPlayersOfErrors(string message) { Logger.LogWarning((object)message); boomboxParent.UpdateUIStatus(message); } [PunRPC] public void ReportDownloadError(int actorNumber, string url, string errorMessage) { if (actorNumber == PhotonNetwork.LocalPlayer.ActorNumber) { boomboxParent.UpdateUIStatus("Error: " + errorMessage); } if (PhotonNetwork.IsMasterClient) { if (!downloadErrors.ContainsKey(url)) { downloadErrors[url] = new HashSet(); } if (!downloadErrors[url].Contains(actorNumber)) { downloadErrors[url].Add(actorNumber); Logger.LogError((object)$"Player {actorNumber} reported download error for {url}: {errorMessage}"); } } } [PunRPC] public void SetSongInfo(string url, string title, double duration, int actorNumber) { if (!PhotonNetwork.IsMasterClient || string.IsNullOrWhiteSpace(url) || string.IsNullOrWhiteSpace(title)) { return; } if (PhotonNetwork.IsMasterClient && DownloadHelper.songInfo.ContainsKey(url) && actorNumber != PhotonNetwork.LocalPlayer.ActorNumber) { SongInfo songInfo = DownloadHelper.songInfo[url]; if (!songInfo.IsInvalid()) { return; } } DownloadHelper.songInfo[url] = new SongInfo(title, duration); bool flag = false; if (boomboxParent?.data?.playbackQueue != null) { foreach (Boombox.AudioEntry item in boomboxParent.data.playbackQueue.FindAll((Boombox.AudioEntry entry) => entry.Url == url)) { if (item.Title != title) { item.Title = title; flag = true; } if (item.Duration != duration) { item.Duration = duration; } flag = true; } } if (boomboxParent?.data?.currentSong != null && boomboxParent.data.currentSong.Url == url) { boomboxParent.data.currentSong.Title = title; if (boomboxParent.data.pendingPlaybackStart) { boomboxParent.UpdateUIStatus("Loading: " + title); } else if ((Object)(object)boomboxParent.data.currentSong.GetAudioClip() != (Object)null && boomboxParent.data.isPlaying) { boomboxParent.UpdateUIStatus("Now playing: " + title); } else if ((Object)(object)boomboxParent.data.currentSong.GetAudioClip() != (Object)null) { boomboxParent.UpdateUIStatus("Ready to play: " + title); } else { boomboxParent.UpdateUIStatus("Loading: " + title); } } if (flag) { Boombox boombox = boomboxParent; if (boombox != null) { ((Component)boombox).GetComponent()?.UpdateDataFromBoomBox(); } if (PhotonNetwork.IsMasterClient) { boomboxParent.PublishSharedState(updateTime: false, updateQueue: true); } } } private IEnumerator ProcessDownloadQueue() { isProcessingQueue = true; Logger.LogDebug((object)"Master Client Download Queue Processor started."); while (downloadJobQueue.Count > 0 && isProcessingQueue) { string url = downloadJobQueue.Dequeue(); yield return MasterClientInitiateSync(url); if ((Object)(object)this == (Object)null) { yield break; } } if (!((Object)(object)this == (Object)null)) { isProcessingQueue = false; currentDownloadUrl = null; Logger.LogDebug((object)"Master Client Download Queue Processor finished."); } } private IEnumerator MasterClientInitiateSync(string url) { if (!boomboxParent.data.playbackQueue.Any((Boombox.AudioEntry entry) => entry.Url == url)) { yield break; } string requestId = Guid.NewGuid().ToString(); currentDownloadUrl = url; currentRequestId = requestId; if (downloadsReady.ContainsKey(url)) { if (downloadsReady[url].Count >= Instance.baseListener.GetAllModUsers().Count) { if (boomboxParent.data.pendingPlaybackStart && url == boomboxParent.data.currentSong?.Url) { boomboxParent.FinalizePendingPlaybackStart(boomboxParent.startPlayBackOnDownload); } yield break; } downloadsReady[url].Clear(); } else { downloadsReady[url] = new HashSet(); } if (downloadErrors.ContainsKey(url)) { downloadErrors[url].Clear(); } else { downloadErrors[url] = new HashSet(); } BaseListener.RPC(((MonoBehaviourPun)this).photonView, "StartDownloadAndSync", (RpcTarget)0, url, PhotonNetwork.LocalPlayer.ActorNumber); timeoutCoroutines[requestId] = ((MonoBehaviour)this).StartCoroutine(DownloadTimeoutCoroutine(requestId, url)); yield return WaitForPlayersReadyOrFailed(url); Coroutine coroutine = default(Coroutine); if ((timeoutCoroutines?.TryGetValue(requestId, out coroutine) ?? false) && coroutine != null) { ((MonoBehaviour)this).StopCoroutine(coroutine); timeoutCoroutines.Remove(requestId); } if (!(currentDownloadUrl != url)) { currentDownloadUrl = null; currentRequestId = null; Logger.LogDebug((object)("Consensus finished for " + url + ", Starting Playback.")); Boombox boombox = boomboxParent; if (boombox != null && (boombox.data?.pendingPlaybackStart).GetValueOrDefault() && url == boomboxParent.data.currentSong?.Url) { boomboxParent.FinalizePendingPlaybackStart(boomboxParent.startPlayBackOnDownload); } } } private int SongTimeout(string url) { double num = songInfo[url].duration; if (num < 0.0) { num = 180.0; } double a = boomboxParent.EstimateDownloadTimeSeconds(num, Instance.DownloadSpeed.Value); return (int)Math.Round(a); } private IEnumerator DownloadTimeoutCoroutine(string requestId, string url) { int num; if (songInfo.ContainsKey(url)) { _ = songInfo[url].duration; num = 0; } else { num = 1; } if (num != 0) { yield return (object)new WaitForSeconds(11f); } while (YoutubeDL.isInitializing) { yield return (object)new WaitForSeconds(1f); } int num2; if (songInfo.ContainsKey(url)) { _ = songInfo[url].duration; num2 = 1; } else { num2 = 0; } if (num2 != 0) { int timeout = SongTimeout(url); Logger.LogDebug((object)$"Using download timeout: {timeout}"); yield return (object)new WaitForSeconds((float)timeout); } if (currentRequestId == requestId && isProcessingQueue) { Logger.LogWarning((object)("Download timeout for url: " + url)); Logger.LogInfo((object)"Master client initiating timeout recovery"); isTimeoutRecovery = true; foreach (int player in Instance.baseListener.GetAllModUsers()) { if (!downloadsReady[url].Contains(player)) { if (!downloadErrors.ContainsKey(url)) { downloadErrors[url] = new HashSet(); } downloadErrors[url].Add(player); Logger.LogWarning((object)$"Player {player} timed out during download"); } } if (downloadsReady.ContainsKey(url) && downloadsReady[url].Count > 0) { string timeoutMessage = $"Some players timed out. Continuing playback for {downloadsReady[url].Count} players."; BaseListener.RPC(((MonoBehaviourPun)this).photonView, "NotifyPlayersOfErrors", (RpcTarget)0, timeoutMessage); if (boomboxParent.data.pendingPlaybackStart && boomboxParent.data.currentSong?.Url == url) { boomboxParent.FinalizePendingPlaybackStart(boomboxParent.startPlayBackOnDownload); } } else { BaseListener.RPC(((MonoBehaviourPun)this).photonView, "NotifyPlayersOfErrors", (RpcTarget)0, "Download timed out for all players."); } currentDownloadUrl = null; currentRequestId = null; isTimeoutRecovery = false; } timeoutCoroutines.Remove(requestId); } [PunRPC] public async void StartDownloadAndSync(string url, int requesterId) { if (url == null || requesterId != PhotonNetwork.MasterClient.ActorNumber) { return; } if (currentDownloadUrl != url) { currentDownloadUrl = url; if (!downloadsReady.ContainsKey(url)) { downloadsReady[url] = new HashSet(); } if (!downloadErrors.ContainsKey(url)) { downloadErrors[url] = new HashSet(); } } if (downloadedClips.ContainsKey(url)) { boomboxParent?.HandleDownloadedCurrentSong(); BaseListener.RPC(((MonoBehaviourPun)this).photonView, "ReportDownloadComplete", (RpcTarget)2, url, PhotonNetwork.LocalPlayer.ActorNumber); } else if (await StartAudioDownload(url) && (Object)(object)this != (Object)null) { boomboxParent?.HandleDownloadedCurrentSong(); BaseListener.RPC(((MonoBehaviourPun)this).photonView, "ReportDownloadComplete", (RpcTarget)2, url, PhotonNetwork.LocalPlayer.ActorNumber); } } [PunRPC] public void ReportDownloadComplete(string url, int actorNumber) { if (PhotonNetwork.IsMasterClient && url != null) { if (!downloadsReady.ContainsKey(url)) { downloadsReady[url] = new HashSet(); } if (downloadErrors.ContainsKey(url) && downloadErrors[url].Contains(actorNumber)) { downloadErrors[url].Remove(actorNumber); } if (!downloadsReady[url].Contains(actorNumber)) { downloadsReady[url].Add(actorNumber); } } } private IEnumerator WaitForPlayersReadyOrFailed(string url) { int totalPlayers = Instance.baseListener.GetAllModUsers().Count; int readyCount = 0; int errorCount = 0; float waitTime = 0.1f; float? partialConsensusStartTime = null; while ((Object)(object)this != (Object)null) { readyCount = (downloadsReady.ContainsKey(url) ? downloadsReady[url].Count : 0); errorCount = (downloadErrors.ContainsKey(url) ? downloadErrors[url].Count : 0); bool allAccountedFor = readyCount + errorCount >= totalPlayers; bool partialConsensus = readyCount > 0 && errorCount > 0; bool timeOutReached = false; if (!partialConsensus) { partialConsensusStartTime = null; } else if (!partialConsensusStartTime.HasValue) { partialConsensusStartTime = Time.time; } else { timeOutReached = Time.time - partialConsensusStartTime.Value >= 10f; } if (allAccountedFor || timeOutReached) { break; } yield return (object)new WaitForSeconds(waitTime); } if ((Object)(object)this != (Object)null) { Logger.LogInfo((object)$"Ready to proceed with playback. Ready: {readyCount}, Errors: {errorCount}, Total: {totalPlayers} for url: {url}"); } } public static async Task GetAudioClipAsync(string filePath, SongInfo info) { await Task.Yield(); if (!File.Exists(filePath)) { throw new Exception("Audio file not found at path: " + filePath); } Uri fileUri = new Uri(filePath); string uri = fileUri.AbsoluteUri; UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(uri, (AudioType)13); try { www.timeout = 11; ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = true; TaskCompletionSource tcs = new TaskCompletionSource(); 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) { Logger.LogError((object)("Web request failed: " + www.error + ", URI: " + uri)); tcs.SetException(new Exception("Failed to load audio file: " + www.error)); } else { tcs.SetResult(result: true); } }; await tcs.Task; AudioClip clip = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)clip == (Object)null) { throw new Exception("Failed to get AudioClip content."); } return clip; } finally { if (www != null) { ((IDisposable)www).Dispose(); } } } public async Task StartAudioDownload(string url) { if (downloadedClips.ContainsKey(url)) { return true; } try { SongInfo info = (songInfo.ContainsKey(url) ? songInfo[url] : null); if (info?.IsInvalid() ?? true) { SongInfo tempInfo = await YoutubeDL.DownloadAudioInfoAsync(url); if (!songInfo.ContainsKey(url) || songInfo[url].IsInvalid()) { songInfo[url] = tempInfo; info = tempInfo; if (!tempInfo.IsInvalid()) { BaseListener.RPC(((MonoBehaviourPun)this).photonView, "SetSongInfo", (RpcTarget)0, url, info.title, info.duration, PhotonNetwork.LocalPlayer.ActorNumber); } } } string filePath = await YoutubeDL.DownloadAudioAsync(url, info); if (boomboxParent?.data?.currentSong?.Url == url) { boomboxParent?.UpdateUIStatus("Processing audio: " + info.title); } AudioClip clip = await GetAudioClipAsync(filePath, info); downloadedClips[url] = clip; Logger.LogDebug((object)("Downloaded and cached clip for video: " + info.title)); if ((Object)(object)clip != (Object)null && info.IsInvalid()) { SongInfo newInfo = await YoutubeDL.DownloadAudioInfoAsync(url); if (!newInfo.IsInvalid() && !songInfo.ContainsKey(url)) { songInfo[url] = newInfo; BaseListener.RPC(((MonoBehaviourPun)this).photonView, "SetSongInfo", (RpcTarget)0, url, newInfo.title, newInfo.duration, PhotonNetwork.LocalPlayer.ActorNumber); } } } catch (Exception ex) { if (boomboxParent?.data?.currentSong?.Url == url) { boomboxParent?.UpdateUIStatus("Error: " + ex.Message); } BaseListener.RPC(((MonoBehaviourPun)this).photonView, "ReportDownloadError", (RpcTarget)0, PhotonNetwork.LocalPlayer.ActorNumber, url, ex.Message); return false; } return true; } 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 (songInfo.ContainsKey(url)) { songInfo.Remove(url); } if (downloadsReady.ContainsKey(url)) { downloadsReady.Remove(url); } if (downloadErrors.ContainsKey(url)) { downloadErrors.Remove(url); } } public override void OnPlayerLeftRoom(Player otherPlayer) { if (isProcessingQueue && !string.IsNullOrEmpty(currentDownloadUrl) && downloadsReady.ContainsKey(currentDownloadUrl) && !downloadsReady[currentDownloadUrl].Contains(otherPlayer.ActorNumber)) { if (!downloadErrors.ContainsKey(currentDownloadUrl)) { downloadErrors[currentDownloadUrl] = new HashSet(); } downloadErrors[currentDownloadUrl].Add(otherPlayer.ActorNumber); Logger.LogInfo((object)$"Player {otherPlayer.ActorNumber} left during download - marking as error"); } ((MonoBehaviourPunCallbacks)this).OnPlayerLeftRoom(otherPlayer); } public void ForceCancelDownload() { if (!isProcessingQueue) { return; } string text = currentDownloadUrl; if (text == null) { text = "Unknown"; } currentDownloadUrl = null; foreach (Coroutine value in timeoutCoroutines.Values) { if (value != null) { ((MonoBehaviour)this).StopCoroutine(value); } } timeoutCoroutines.Clear(); BaseListener.RPC(((MonoBehaviourPun)this).photonView, "ReportDownloadError", (RpcTarget)0, PhotonNetwork.LocalPlayer.ActorNumber, text, "Download cancelled."); Logger.LogInfo((object)"Download was force cancelled by user."); } } public class VisualEffects : MonoBehaviour { private Light frontLight; private Light backLight; private AudioSource audioSource; private const float BaseRgbSpeed = 0.18f; private const float BaseLightRange = 6f; private const float MaxLightRangeMultiplier = 1.3f; private const float BaseIntensity = 1f; private const float MaxLightIntensityMultiplier = 2f; private const float MinSpectrumNormalizationVolume = 0.1f; private const float MaxBassSpeedBoost = 0.85f; private const float BassResponse = 10f; private const float BassRelease = 4f; private const int SpectrumSize = 512; private const float BassMaxFrequency = 220f; private const float LowEndMaxFrequency = 900f; private const float NonBassMaxFrequency = 2500f; private const float PeakEmphasis = 0.75f; private const float AnalysisBarMin = 0.15f; private const float AnalysisBarMax = 1.5f; private const float AnalysisHeightMultiplier = 24f; private const float NonBassLeakRejection = 0.8f; private readonly float[] spectrum = new float[512]; private float hueProgress = 0f; private float bassIntensity = 0f; private bool lightsOn = false; private static BoomBoxCartMod Instance => BoomBoxCartMod.instance; private void Start() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) audioSource = ((Component)this).GetComponent(); GameObject val = new GameObject("BoomboxFrontLight"); val.transform.SetParent(((Component)this).transform); val.transform.localPosition = new Vector3(0f, 0f, 1f); frontLight = val.AddComponent(); frontLight.type = (LightType)2; frontLight.range = 6f; frontLight.intensity = 1f; ((Behaviour)frontLight).enabled = lightsOn; GameObject val2 = new GameObject("BoomboxBackLight"); val2.transform.SetParent(((Component)this).transform); val2.transform.localPosition = new Vector3(0f, 0f, -1f); backLight = val2.AddComponent(); backLight.type = (LightType)2; backLight.range = 6f; backLight.intensity = 1f; ((Behaviour)backLight).enabled = lightsOn; } private void Update() { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) if (lightsOn) { float num = 0.18f + GetBassSpeedBoost(); float range = 6f * Mathf.Lerp(1f, 1.3f, bassIntensity); float intensity = 1f * Mathf.Lerp(1f, 2f, bassIntensity); hueProgress = Mathf.Repeat(hueProgress + Time.deltaTime * num, 1f); Color color = Color.HSVToRGB(hueProgress, 1f, 1f); if ((Object)(object)frontLight != (Object)null) { frontLight.color = color; frontLight.range = range; frontLight.intensity = intensity; } if ((Object)(object)backLight != (Object)null) { backLight.color = color; backLight.range = range; backLight.intensity = intensity; } } } private float GetBassSpeedBoost() { if ((Object)(object)audioSource == (Object)null) { audioSource = ((Component)this).GetComponent(); } float num = (((Object)(object)audioSource != (Object)null) ? Mathf.Clamp01(audioSource.volume) : 0f); float num2 = 0f; if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying && num > 0f) { audioSource.GetSpectrumData(spectrum, 0, (FFTWindow)4); float num3 = Mathf.Max(num, 0.1f); float spectrumNormalizationScale = 1f / num3; float frequencyRangeIntensity = GetFrequencyRangeIntensity(20f, 900f, spectrumNormalizationScale, 0.2625f); float frequencyRangeIntensity2 = GetFrequencyRangeIntensity(20f, 220f, spectrumNormalizationScale, 0.75f); float frequencyRangeIntensity3 = GetFrequencyRangeIntensity(220f, 2500f, spectrumNormalizationScale, 0.112500004f); float num4 = Mathf.Clamp01(frequencyRangeIntensity2 - frequencyRangeIntensity3 * 0.8f); num2 = Mathf.Lerp(frequencyRangeIntensity, num4, Instance.UnderglowBassBias.Value); bassIntensity = Mathf.Lerp(bassIntensity, num2, Time.deltaTime * 10f); } else { bassIntensity = Mathf.Lerp(bassIntensity, 0f, Time.deltaTime * 4f); } return bassIntensity * 0.85f * Instance.UnderglowBeatSpeed.Value; } private float GetFrequencyRangeIntensity(float minFrequency, float maxFrequency, float spectrumNormalizationScale, float peakBlend) { float num = (float)AudioSettings.outputSampleRate * 0.5f / 512f; int num2 = Mathf.Clamp(Mathf.FloorToInt(minFrequency / num), 1, 511); int num3 = Mathf.Clamp(Mathf.CeilToInt(maxFrequency / num), num2 + 1, 512); float num4 = 0f; float num5 = 0f; int num6 = 0; for (int i = num2; i < num3; i++) { float normalizedSpectrumSample = GetNormalizedSpectrumSample(spectrum[i], spectrumNormalizationScale); num4 += normalizedSpectrumSample; num5 = Mathf.Max(num5, normalizedSpectrumSample); num6++; } if (num6 == 0) { return 0f; } float num7 = num4 / (float)num6; return Mathf.Lerp(num7, num5, peakBlend); } private float GetNormalizedSpectrumSample(float sample, float spectrumNormalizationScale) { float num = sample * spectrumNormalizationScale; float num2 = Mathf.Clamp(Mathf.Pow(num * 24f, 0.5f), 0.15f, 1.5f); return Mathf.InverseLerp(0.15f, 1.5f, num2); } public void SetLights(bool on) { if (lightsOn == on) { return; } lightsOn = on; if ((Object)(object)frontLight != (Object)null) { ((Behaviour)frontLight).enabled = on; if (!on) { frontLight.range = 6f; frontLight.intensity = 1f; } } if ((Object)(object)backLight != (Object)null) { ((Behaviour)backLight).enabled = on; if (!on) { backLight.range = 6f; backLight.intensity = 1f; } } } public bool AreLightsOn() { return lightsOn; } } public class Visualizer : MonoBehaviour { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; public AudioSource audioSource; private bool visible = true; public int numBars = 16; public int spectrumSize = 128; public float radius = 0.7f; public float barWidth = 0.1f; public float barMaxHeight = 1.5f; public float barMinHeight = 0.15f; public float heightMultiplier = 24f; private float[] spectrum; private Transform[] bars; private bool lastPlaying = false; public bool IsVisible { get { return visible; } set { visible = value; for (int i = 0; i < numBars; i++) { ((Component)bars[i]).GetComponent().enabled = value || Instance.VisualizerBehaviourPaused.Value != BoomBoxCartMod.VisualizerPaused.Hide; } } } private void Start() { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)audioSource == (Object)null) { audioSource = ((Component)this).GetComponent(); } spectrum = new float[spectrumSize]; bars = (Transform[])(object)new Transform[numBars]; Vector3 localPosition = default(Vector3); for (int i = 0; i < numBars; i++) { GameObject val = GameObject.CreatePrimitive((PrimitiveType)3); val.transform.SetParent(((Component)this).transform); float num = Mathf.Lerp(-(float)Math.PI / 2f, (float)Math.PI / 2f, (float)i / (float)(numBars - 1)); ((Vector3)(ref localPosition))..ctor(Mathf.Sin(num) * radius, 0.5f, Mathf.Cos(num) * radius + 0.7f); val.transform.localPosition = localPosition; val.transform.localScale = new Vector3(barWidth, barMinHeight, barWidth); val.GetComponent().material.color = Color.HSVToRGB((float)i / (float)numBars, 1f, 1f); Object.Destroy((Object)(object)val.GetComponent()); bars[i] = val.transform; } IsVisible = audioSource.isPlaying || Instance.VisualizerBehaviourPaused.Value != BoomBoxCartMod.VisualizerPaused.Hide; lastPlaying = audioSource.isPlaying; } private void Update() { //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) AudioSource obj = audioSource; bool flag = obj != null && obj.isPlaying; if (lastPlaying != flag) { IsVisible = flag || Instance.VisualizerBehaviourPaused.Value != BoomBoxCartMod.VisualizerPaused.Hide; lastPlaying = flag; } if (!visible) { return; } if (flag) { audioSource.GetSpectrumData(spectrum, 0, (FFTWindow)4); for (int i = 0; i < numBars; i++) { int num = (int)Mathf.Pow((float)spectrumSize, (float)i / (float)numBars); int num2 = (int)Mathf.Pow((float)spectrumSize, (float)(i + 1) / (float)numBars); num2 = Mathf.Clamp(num2, num + 1, spectrumSize); float num3 = 0f; for (int j = num; j < num2; j++) { num3 += spectrum[j]; } num3 /= (float)(num2 - num); float y = Mathf.Clamp(Mathf.Pow(num3 * heightMultiplier, 0.5f), barMinHeight, barMaxHeight); Vector3 localScale = bars[i].localScale; localScale.y = y; bars[i].localScale = localScale; } } else if (Instance.VisualizerBehaviourPaused.Value == BoomBoxCartMod.VisualizerPaused.Show) { for (int k = 0; k < numBars; k++) { Vector3 localScale2 = bars[k].localScale; localScale2.y = barMinHeight; bars[k].localScale = localScale2; } } } private void OnDestroy() { if (bars == null) { return; } Transform[] array = bars; foreach (Transform val in array) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)((Component)val).gameObject); } } } } [BepInPlugin("Doppelclick.BoomboxCartUpgrade", "BoomboxCartUpgrade", "1.3.4")] public class BoomBoxCartMod : BaseUnityPlugin { public enum VisualizerPaused { Show, PausePosition, Hide } public enum CookieUsage { OFF, FILE, BROWSER } public enum BrowserType { chrome, firefox, edge, brave, safari } public const string modGUID = "Doppelclick.BoomboxCartUpgrade"; public const string modName = "BoomboxCartUpgrade"; public const string modVersion = "1.3.4"; private readonly Harmony harmony = new Harmony("Doppelclick.BoomboxCartUpgrade"); public BaseListener baseListener = null; internal static BoomBoxCartMod instance; internal ManualLogSource logger; public PersistentData data; private bool _modDisabled = false; private bool resourcesReinstalling = false; public bool initialized { get; private set; } = false; public bool modDisabled { get { return _modDisabled; } set { if (_modDisabled != value) { logger.LogInfo((object)("Mod " + (value ? "Disabled" : "Enabled"))); } _modDisabled = value; if (!value || data == null) { return; } foreach (Boombox allBoombox in data.GetAllBoomboxes()) { PhotonView photonView = allBoombox.photonView; Object.Destroy((Object)(object)allBoombox); if (photonView != null) { photonView.RefreshRpcMonoBehaviourCache(); } } data.GetAllBoomboxes().Clear(); data.GetBoomboxData().Clear(); } } public ConfigEntry CookiePassthrough { get; private set; } public ConfigEntry CookiePath { get; private set; } public ConfigEntry Browser { get; private set; } public ConfigEntry DownloadSpeed { get; private set; } public ConfigEntry OpenUIKey { get; private set; } public ConfigEntry GlobalMuteKey { get; private set; } public ConfigEntry MasterClientDismissQueue { get; private set; } public ConfigEntry UseTimeStampOnce { get; private set; } public ConfigEntry RestoreBoomboxes { get; private set; } public ConfigEntry AutoResume { get; private set; } public ConfigEntry UnderglowBeatSpeed { get; private set; } public ConfigEntry UnderglowBassBias { get; private set; } public ConfigEntry VisualizerBehaviourPaused { get; private set; } public ConfigEntry SyncVisuals { get; private set; } public ConfigEntry ReinstallResources { get; private set; } private void Awake() { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Expected O, but got Unknown //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Expected O, but got Unknown if ((Object)(object)instance == (Object)null) { instance = this; } data = new PersistentData(); logger = Logger.CreateLogSource("Doppelclick.BoomboxCartUpgrade"); logger.LogInfo((object)"BoomBoxCartMod loaded!"); harmony.PatchAll(); CookiePassthrough = ((BaseUnityPlugin)this).Config.Bind("Downloader", "CookiePassthrough", CookieUsage.OFF, "Pass cookies from either your browser or an exported file. (OFF, FILE, BROWSER)"); CookiePath = ((BaseUnityPlugin)this).Config.Bind("Downloader", "CustomCookiePath", "", "Path to where the browser cookies are located, if the browser selection does not work."); Browser = ((BaseUnityPlugin)this).Config.Bind("Downloader", "BrowserCookies", BrowserType.firefox, "Select the browser you are using for cookies (chrome, firefox, edge, brave, safari). This is unlikely to work."); DownloadSpeed = ((BaseUnityPlugin)this).Config.Bind("Downloader", "DownloadSpeed", 10, new ConfigDescription("Estimated download speed in Mbps (not MBps). Used for download timeout estimation.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 100), Array.Empty())); OpenUIKey = ((BaseUnityPlugin)this).Config.Bind("Binds", "OpenUIKey", (Key)39, "Key to open the Boombox UI when grabbing a cart."); GlobalMuteKey = ((BaseUnityPlugin)this).Config.Bind("Binds", "GlobalMuteKey", (Key)27, "Key to mute all playback."); MasterClientDismissQueue = ((BaseUnityPlugin)this).Config.Bind("Queue", "MasterClientDismissQueue", true, "Allow only the master client to dismiss the queue."); UseTimeStampOnce = ((BaseUnityPlugin)this).Config.Bind("Queue", "UseTimeStampOnce", false, "Only use the timestamp provided with a Url the first time the song is played."); RestoreBoomboxes = ((BaseUnityPlugin)this).Config.Bind("Queue", "RestoreBoomboxes", true, "Restore BoomBoxes and their Queues when you load back into a level."); AutoResume = ((BaseUnityPlugin)this).Config.Bind("Queue", "AutoResume", false, "Automatically resume playback when entering a new lobby."); UnderglowBeatSpeed = ((BaseUnityPlugin)this).Config.Bind("Visual", "UnderglowBeatSpeed", 1.2f, new ConfigDescription("Scales how much detected beat energy speeds up the RGB underglow cycle. 0 = disabled, 6 = more aggressive.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 6f), Array.Empty())); UnderglowBassBias = ((BaseUnityPlugin)this).Config.Bind("Visual", "UnderglowBassBias", 0.8f, new ConfigDescription("Blends between broad beat response and isolated low-band response. 0 = broad response, 1 = strongest bass isolation.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); VisualizerBehaviourPaused = ((BaseUnityPlugin)this).Config.Bind("Visual", "VisualizerOnPaused", VisualizerPaused.Hide, "Visualizer behaviour when music is paused."); SyncVisuals = ((BaseUnityPlugin)this).Config.Bind("Visual", "SyncVisuals", true, "Sync if underglow/visualizer are enabled with the lobby, or only apply it to yourself."); ReinstallResources = ((BaseUnityPlugin)this).Config.Bind("Debug", "ReinstallResources", false, "Reinstall resources on startup."); ReinstallResources.SettingChanged += delegate(object s, EventArgs _) { if (initialized) { ConfigEntry val = (ConfigEntry)s; if (resourcesReinstalling) { if (!val.Value) { val.Value = true; } } else if (YoutubeDL.IsUpdatingResources) { logger.LogInfo((object)"Already updating resources..."); val.Value = false; } else { if (val.Value) { logger.LogInfo((object)"Reinstalling resources..."); resourcesReinstalling = true; } Task.Run(async delegate { await YoutubeDL.Reinstall(); logger.LogInfo((object)"Finished reinstalling resources."); ReinstallResources.Value = false; resourcesReinstalling = false; }); } } }; logger.LogInfo((object)"BoomBoxCartMod initialization finished!"); initialized = true; } private void Start() { if (ReinstallResources.Value) { ReinstallResources.Value = true; } else { YoutubeDL.InitializeAsync(); } } private void OnDestroy() { YoutubeDL.CleanUp(); } } } namespace BoomBoxCartMod.Util { public class BaseListener : MonoBehaviourPunCallbacks { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; public int lastUserAmount = 0; private List modList = new List(); public bool audioMuted = false; private bool mutePressed = false; public static bool downloaderAvailable = true; public List GetAllModUsers() { return modList; } public void AddModUser(int id) { if (!modList.Contains(id)) { modList.Add(id); SendModListUpdate(); } } public void RemoveModUser(int id) { modList.Remove(id); SendModListUpdate(); } private void SendModListUpdate() { if (PhotonNetwork.IsMasterClient && PhotonNetwork.IsMasterClient) { RPC(((MonoBehaviourPun)this).photonView, "UpdateModUsers", (RpcTarget)1, modList.ToArray(), PhotonNetwork.LocalPlayer.ActorNumber); } } private void Update() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!mutePressed && Keyboard.current != null && ((ButtonControl)Keyboard.current[Instance.GlobalMuteKey.Value]).wasPressedThisFrame) { mutePressed = true; audioMuted = !audioMuted; } else if (mutePressed && (Keyboard.current == null || ((ButtonControl)Keyboard.current[Instance.GlobalMuteKey.Value]).wasReleasedThisFrame)) { mutePressed = false; } } public static void RPC(PhotonView view, string methodName, RpcTarget target, params object[] parameters) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Invalid comparison between Unknown and I4 //IL_00ba: 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) if ((Object)(object)view == (Object)null) { return; } if ((int)target == 0 || (int)target == 1) { List allModUsers = Instance.baseListener.GetAllModUsers(); Dictionary players = PhotonNetwork.CurrentRoom.Players; { foreach (KeyValuePair item in players) { if (allModUsers.Contains(item.Key) && ((int)target == 0 || item.Key != PhotonNetwork.LocalPlayer.ActorNumber)) { view.RPC(methodName, item.Value, parameters); } } return; } } view.RPC(methodName, target, parameters); } public static void RPC(PhotonView view, string methodName, Player target, params object[] parameters) { if ((Object)(object)view != (Object)null && Instance.baseListener.GetAllModUsers().Contains(target.ActorNumber)) { view.RPC(methodName, PhotonNetwork.MasterClient, parameters); } } public static void ReportDownloaderStatus(bool available) { if (available == downloaderAvailable) { return; } Instance.logger.LogInfo((object)("Downloader status: " + (available ? "Available" : "Unavailable"))); downloaderAvailable = available; if (!available) { Instance.modDisabled = true; } if (PhotonNetwork.IsMasterClient) { if (available) { Instance.modDisabled = false; Instance.baseListener.AddModUser(PhotonNetwork.LocalPlayer.ActorNumber); } else { Instance.baseListener.RemoveModUser(PhotonNetwork.LocalPlayer.ActorNumber); } } else if (PhotonNetwork.IsConnected) { PhotonView photonView = ((MonoBehaviourPun)Instance.baseListener).photonView; if (photonView != null) { photonView.RPC("ModFeedbackCheck", (RpcTarget)2, new object[2] { available ? "1.3.4" : "-1", PhotonNetwork.LocalPlayer.ActorNumber }); } } else if (available) { Instance.modDisabled = false; } } public override void OnPlayerLeftRoom(Player otherPlayer) { ((MonoBehaviourPunCallbacks)this).OnPlayerLeftRoom(otherPlayer); RemoveModUser(otherPlayer.ActorNumber); Instance.logger.LogInfo((object)$"Player {otherPlayer.ActorNumber} left the room."); } [PunRPC] public void ModFeedbackCheck(string modVersion, int actorNumber) { if ((Object)(object)Instance.baseListener == (Object)null) { return; } if (PhotonNetwork.IsMasterClient) { if (Instance.modDisabled) { return; } if (modVersion == "1.3.4") { Instance.baseListener.AddModUser(actorNumber); ManualLogSource logger = Instance.logger; if (logger != null) { logger.LogInfo((object)$"Player {actorNumber} is using a compatible version of the mod."); } } else { Instance.baseListener.RemoveModUser(actorNumber); ManualLogSource logger2 = Instance.logger; if (logger2 != null) { logger2.LogInfo((object)$"Player {actorNumber} will not be joining the jam session."); } } } else { Instance.modDisabled = !downloaderAvailable || modVersion != "1.3.4"; Instance.logger.LogInfo((object)("Mod " + (Instance.modDisabled ? "DISABLED" : "ENABLED") + ". Current version: 1.3.4, requested: " + modVersion)); PhotonView photonView = ((MonoBehaviourPun)Instance.baseListener).photonView; if (photonView != null) { photonView.RPC("ModFeedbackCheck", (RpcTarget)2, new object[2] { downloaderAvailable ? "1.3.4" : "-1", PhotonNetwork.LocalPlayer.ActorNumber }); } } } [PunRPC] public void UpdateModUsers(int[] modUsers, int actorNumber) { if (!PhotonNetwork.IsMasterClient) { modList.Clear(); modList.AddRange(modUsers); } } } public class PersistentData { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; private List initializedBoomBoxes = new List(); private List boomboxData = new List(); public static bool GetBoomboxViewStatus(Player player, int viewID) { if (!PhotonNetwork.IsConnected) { return false; } string key = "boomboxView" + viewID; if (((Dictionary)(object)player.CustomProperties).TryGetValue((object)key, out object value)) { if (value is bool result) { return result; } return false; } return false; } public static void SetBoomboxViewInitialized(int viewID) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown string key = "boomboxView" + viewID; Hashtable val = new Hashtable(); ((Dictionary)(object)val).Add((object)key, (object)true); PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null); } public static void RemoveBoomboxViewInitialized(int viewID) { if (PhotonNetwork.IsConnected) { string key = "boomboxView" + viewID; ((Dictionary)(object)PhotonNetwork.LocalPlayer.CustomProperties).Remove((object)key); } } public List GetAllBoomboxes() { return initializedBoomBoxes; } public List GetBoomboxData() { return boomboxData; } private void ApplyRestoreSettings(Boombox.BoomboxData data) { if (data != null && !Instance.AutoResume.Value) { data.isPlaying = false; data.pendingPlaybackStart = false; } } public void InitializeBoomboxData(Boombox boombox) { int num = initializedBoomBoxes.IndexOf(boombox); if (num == -1) { num = initializedBoomBoxes.Count; initializedBoomBoxes.Add(boombox); } Boombox.BoomboxData boomboxData; if (PhotonNetwork.IsMasterClient && Instance.RestoreBoomboxes.Value) { if (num < this.boomboxData.Count) { boomboxData = this.boomboxData[num]; ApplyRestoreSettings(boomboxData); } else { boomboxData = new Boombox.BoomboxData(); this.boomboxData.Add(boomboxData); } } else { boomboxData = new Boombox.BoomboxData(); } boombox.data = boomboxData; } } public class SongInfo { [JsonProperty("title")] public string title { get; set; } = "Unknown Title"; [JsonProperty("duration")] public double duration { get; set; } = -1.0; public SongInfo() { } public SongInfo(string title, double duration) { this.title = title; this.duration = duration; } public bool IsInvalid() { return duration == -1.0 && (Utility.IsNullOrWhiteSpace(title) || title.Trim().StartsWith("Unknown Title")); } } public static class YoutubeDL { [Serializable] private class GitHubRelease { public string published_at; } private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; private static readonly string baseFolder = Path.Combine(Directory.GetCurrentDirectory(), "BoomboxedCart"); private static readonly string tempFolder = Path.Combine(baseFolder, "temp"); private static readonly string jsFolder = Path.Combine(Directory.GetCurrentDirectory(), "JavaScript"); private const string JS_RELEASE_URL = "https://nodejs.org/dist/v26.1.0/node-v26.1.0-win-x64.zip"; private const string YTDLP_RELEASE_URL = "https://api.github.com/repos/yt-dlp/yt-dlp-Builds/releases/latest"; private const string YTDLP_URL = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe"; private const string FFMPEG_RELEASE_URL = "https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest"; private const string FFMPEG_URL = "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip"; private static readonly string jsRuntimePath = Path.Combine(jsFolder, "node-v26.1.0-win-x64/node.exe"); private static readonly string ytDlpPath = Path.Combine(baseFolder, "yt-dlp.exe"); private static readonly string ffmpegFolder = Path.Combine(baseFolder, "ffmpeg"); private static string ffmpegBinPath = Path.Combine(ffmpegFolder, "ffmpeg-master-latest-win64-gpl", "bin", "ffmpeg.exe"); private static readonly object initializationLock = new object(); private static Task initializeTask = Task.CompletedTask; public static bool isInitializing = false; private static bool jsInstalled = false; private static bool ffmpegUpdateChecked = false; private static bool ytDLPUpdateChecked = false; private static bool isUpdatingResources = false; private static string resourceUpdateStatus = string.Empty; private static ManualLogSource Logger => Instance.logger; public static bool IsUpdatingResources => isUpdatingResources; public static string ResourceUpdateStatus => string.IsNullOrWhiteSpace(resourceUpdateStatus) ? "Updating resources..." : resourceUpdateStatus; public static Task InitializeAsync() { lock (initializationLock) { bool flag = File.Exists(ytDlpPath) && File.Exists(ffmpegBinPath); if (initializeTask != null && !initializeTask.IsCompleted) { return initializeTask; } if (flag && ytDLPUpdateChecked && ffmpegUpdateChecked) { return Task.CompletedTask; } initializeTask = InitializeInternalAsync(); return initializeTask; } } private static async Task InitializeInternalAsync() { SetResourceUpdateStatus("Checking downloader resources...", updating: true); isInitializing = true; try { Directory.CreateDirectory(baseFolder); Directory.CreateDirectory(tempFolder); Directory.CreateDirectory(jsFolder); if (!File.Exists(jsRuntimePath)) { SetResourceUpdateStatus("Updating resources: downloading nodeJS...", updating: true); Logger.LogInfo((object)"nodeJS not found. Downloading..."); await DownloadAndExtractArchiveAsync("https://nodejs.org/dist/v26.1.0/node-v26.1.0-win-x64.zip", jsFolder, "node.exe", 6); Logger.LogInfo((object)"nodeJS download finished."); jsInstalled = true; } else { jsInstalled = true; } if (!File.Exists(ytDlpPath)) { SetResourceUpdateStatus("Updating resources: downloading yt-dlp...", updating: true); Logger.LogInfo((object)"yt-dlp not found. Downloading..."); await DownloadFileAsync("https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", ytDlpPath, 1); Logger.LogInfo((object)"yt-dlp download finished."); } else if (!ytDLPUpdateChecked) { SetResourceUpdateStatus("Updating resources: checking yt-dlp...", updating: true); Logger.LogInfo((object)"yt-dlp found. Checking for updates..."); DateTime localBuildDate = File.GetLastWriteTimeUtc(ytDlpPath); DateTime? latestReleaseDate = await GetLatestGithubReleaseDate("https://api.github.com/repos/yt-dlp/yt-dlp-Builds/releases/latest"); int num; if (latestReleaseDate.HasValue) { DateTime value = localBuildDate.AddHours(24.0); DateTime? dateTime = latestReleaseDate; num = ((value < dateTime) ? 1 : 0); } else { num = 0; } if (num != 0) { SetResourceUpdateStatus("Updating resources: updating yt-dlp...", updating: true); Logger.LogInfo((object)"yt-dlp update found. Downloading..."); await DownloadFileAsync("https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe", ytDlpPath, 1); } else { Logger.LogInfo((object)((!latestReleaseDate.HasValue) ? "yt-dlp release date failed to parse." : "yt-dlp up to date.")); } } ytDLPUpdateChecked = true; if (!File.Exists(ffmpegBinPath) || !Directory.Exists(Path.GetDirectoryName(ffmpegBinPath))) { SetResourceUpdateStatus("Updating resources: downloading FFmpeg...", updating: true); Logger.LogInfo((object)"ffmpeg not found. Downloading and extracting..."); await DownloadAndExtractArchiveAsync("https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", ffmpegFolder, "ffmpeg.exe", 6); } else if (!ffmpegUpdateChecked) { SetResourceUpdateStatus("Updating resources: checking FFmpeg...", updating: true); Logger.LogInfo((object)"ffmpeg found. Checking for updates..."); DateTime localBuildDate2 = File.GetLastWriteTimeUtc(ffmpegBinPath); DateTime? latestReleaseDate2 = await GetLatestGithubReleaseDate("https://api.github.com/repos/BtbN/FFmpeg-Builds/releases/latest"); int num2; if (latestReleaseDate2.HasValue) { DateTime value = localBuildDate2.AddHours(24.0); DateTime? dateTime = latestReleaseDate2; num2 = ((value < dateTime) ? 1 : 0); } else { num2 = 0; } if (num2 != 0) { SetResourceUpdateStatus("Updating resources: updating FFmpeg...", updating: true); Logger.LogInfo((object)"ffmpeg update found. Downloading and extracting..."); await DownloadAndExtractArchiveAsync("https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip", ffmpegFolder, "ffmpeg.exe", 6); } else { Logger.LogInfo((object)((!latestReleaseDate2.HasValue) ? "ffmpeg release date failed to parse." : "ffmpeg up to date.")); } } ffmpegUpdateChecked = true; if (!File.Exists(ytDlpPath)) { BaseListener.ReportDownloaderStatus(available: false); Logger.LogError((object)("yt-dlp executable was not found at " + ytDlpPath + ". Internet problem?")); SetResourceUpdateStatus("Required media tools are unavailable.", updating: false); } else if (!File.Exists(ffmpegBinPath)) { BaseListener.ReportDownloaderStatus(available: false); Logger.LogError((object)("ffmpeg executable was not found at " + ffmpegBinPath + " after extraction. Internet problem? Not on Windows problem?")); SetResourceUpdateStatus("Required media tools are unavailable.", updating: false); } else { Logger.LogInfo((object)"Yt-DL initialization complete."); BaseListener.ReportDownloaderStatus(available: true); SetResourceUpdateStatus(string.Empty, updating: false); } } catch (Exception ex2) { Exception ex = ex2; Logger.LogError((object)("Downloader initialization failed: " + ex.Message)); BaseListener.ReportDownloaderStatus(available: false); SetResourceUpdateStatus("Required media tools are unavailable.", updating: false); } finally { isInitializing = false; } } private static void SetResourceUpdateStatus(string message, bool updating) { resourceUpdateStatus = message; isUpdatingResources = updating; } private static async Task DownloadFileAsync(string url, string destinationPath, int timeout) { HttpClient client = new HttpClient(); try { client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("BoomboxCartUpgrade", "1.3.4")); client.Timeout = TimeSpan.FromMinutes(timeout); string tempDownloadPath = destinationPath + ".download"; try { byte[] data = await client.GetByteArrayAsync(url); Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); if (File.Exists(tempDownloadPath)) { File.Delete(tempDownloadPath); } File.WriteAllBytes(tempDownloadPath, data); ReplaceFile(tempDownloadPath, destinationPath); } catch (Exception ex) { Exception e = ex; Logger.LogError((object)e.Message); if (File.Exists(tempDownloadPath)) { File.Delete(tempDownloadPath); } throw; } } finally { ((IDisposable)client)?.Dispose(); } } private static void ReplaceFile(string sourcePath, string destinationPath) { if (File.Exists(destinationPath)) { string text = destinationPath + ".bak"; if (File.Exists(text)) { File.Delete(text); } File.Replace(sourcePath, destinationPath, text, ignoreMetadataErrors: true); if (File.Exists(text)) { File.Delete(text); } } else { File.Move(sourcePath, destinationPath); } } private static async Task DownloadAndExtractArchiveAsync(string url, string installFolder, string executableName, int retries = 3) { string archivePath = Path.Combine(tempFolder, Path.GetFileName(url)); string stagingFolder = Path.Combine(tempFolder, "staging-" + Guid.NewGuid().ToString("N")); try { if (File.Exists(archivePath)) { File.Delete(archivePath); } if (Directory.Exists(stagingFolder)) { Directory.Delete(stagingFolder, recursive: true); } Directory.CreateDirectory(stagingFolder); Logger.LogDebug((object)("Downloading: " + url)); await DownloadFileAsync(url, archivePath, retries); if (!File.Exists(archivePath)) { throw new Exception("Archive download failed."); } Logger.LogDebug((object)"Extracting archive..."); string extension = Path.GetExtension(archivePath).ToLowerInvariant(); if (extension == ".zip") { ZipFile.ExtractToDirectory(archivePath, stagingFolder); File.Delete(archivePath); string executablePath2 = Directory.GetFiles(stagingFolder, executableName, SearchOption.AllDirectories).FirstOrDefault(); if (string.IsNullOrWhiteSpace(executablePath2)) { throw new Exception(executableName + " not found after extraction."); } if (Directory.Exists(installFolder)) { Directory.Delete(installFolder, recursive: true); } Directory.Move(stagingFolder, installFolder); executablePath2 = Directory.GetFiles(installFolder, executableName, SearchOption.AllDirectories).First(); Logger.LogDebug((object)(executableName + " installed successfully.")); return executablePath2; } throw new Exception("Unsupported archive type: " + extension); } finally { if (File.Exists(archivePath)) { File.Delete(archivePath); } if (Directory.Exists(stagingFolder)) { Directory.Delete(stagingFolder, recursive: true); } } } public static async Task DownloadAudioInfoAsync(string videoUrl) { await InitializeAsync(); return await Task.Run(async delegate { try { SongInfo info = (DownloadHelper.songInfo.ContainsKey(videoUrl) ? DownloadHelper.songInfo[videoUrl] : null); if (info?.IsInvalid() ?? true) { info = await GetVideoTitleInternalAsync(videoUrl); if (info == null || string.IsNullOrEmpty(info.title)) { info = new SongInfo(); } } info.title = info.title.Replace("\n", "").Replace("\r", ""); return info; } catch (Exception ex2) { Exception ex = ex2; Logger.LogError((object)$"Download Error: {ex}"); Logger.LogError((object)("Stack Trace: " + ex.StackTrace)); throw; } }); } public static async Task DownloadAudioAsync(string videoUrl, SongInfo info) { string folder = Path.Combine(tempFolder, Guid.NewGuid().ToString()); Directory.CreateDirectory(folder); Logger.LogDebug((object)("Downloading audio for " + videoUrl + "...")); return await Task.Run(async delegate { try { string text; if (!Boombox.ApplyQualityToDownloads) { text = "192K"; } else { int quality2 = AudioPlayer.GetQuality(); if (1 == 0) { } string text2 = quality2 switch { 0 => "32K", 1 => "64K", 2 => "96K", 3 => "128K", _ => "192K", }; if (1 == 0) { } text = text2; } string quality = text; string noIckySpecialCharsFileName = $"audio_{DateTime.Now.Ticks}.%(ext)s"; string options = ""; if (jsInstalled) { options = options + " --js-runtimes node:\"" + jsRuntimePath + "\""; } if (Instance.CookiePassthrough.Value == BoomBoxCartMod.CookieUsage.FILE && !string.IsNullOrWhiteSpace(Instance.CookiePath.Value)) { options = options + " --cookies \"" + Instance.CookiePath.Value + "\""; Logger.LogDebug((object)"Attempting to use cookies from file."); } else if (Instance.CookiePassthrough.Value == BoomBoxCartMod.CookieUsage.BROWSER) { options += $" --cookies-from-browser \"{Instance.Browser.Value}\""; Logger.LogDebug((object)"Attempting to use cookies from browser."); } string command = "-x --audio-format mp3 --audio-quality " + quality + options + " --ffmpeg-location \"" + ffmpegBinPath + "\" --output \"" + Path.Combine(folder, noIckySpecialCharsFileName) + "\" " + videoUrl; ProcessStartInfo processInfo = new ProcessStartInfo { FileName = ytDlpPath, Arguments = command, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8 }; using (Process process = Process.Start(processInfo)) { if (process == null) { throw new Exception("Failed to start yt-dlp process."); } await process.StandardOutput.ReadToEndAsync(); string error = await process.StandardError.ReadToEndAsync(); process.WaitForExit(); if (!string.IsNullOrEmpty(error)) { Logger.LogInfo((object)"An error recorded in yt-dlp download, error is probably not fatal though"); } if (process.ExitCode != 0) { throw new Exception($"yt-dlp download failed. Exit Code: {process.ExitCode}. Error: {error}"); } } await Task.Delay(1000); string audioFilePath = Directory.GetFiles(folder, "*.mp3").FirstOrDefault(); if (audioFilePath == null) { string[] allFiles = Directory.GetFiles(folder); Logger.LogError((object)$"No MP3 files found. Total files: {allFiles.Length}"); string[] array = allFiles; foreach (string file in array) { Logger.LogError((object)("Found file: " + file)); } throw new Exception("Audio download failed. No MP3 file created."); } return audioFilePath; } catch (Exception ex2) { Exception ex = ex2; Logger.LogError((object)$"Download Error: {ex}"); Logger.LogError((object)("Stack Trace: " + ex.StackTrace)); if (Directory.Exists(folder)) { try { Directory.Delete(folder, recursive: true); } catch (Exception ex2) { Exception cleanupEx = ex2; Logger.LogError((object)$"Failed to clean up temp folder: {cleanupEx}"); } } throw; } }); } private static async Task GetVideoTitleInternalAsync(string url) { try { ProcessStartInfo psi = new ProcessStartInfo { FileName = ytDlpPath, Arguments = "--skip-download --no-playlist --no-warnings --encoding utf-8 --print \"{\\\"title\\\":\\\"%(title)s\\\",\\\"duration\\\":%(duration)s}\" \"" + url + "\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, StandardOutputEncoding = Encoding.UTF8, StandardErrorEncoding = Encoding.UTF8 }; Process process = new Process { StartInfo = psi }; try { process.Start(); string json = await process.StandardOutput.ReadToEndAsync(); string error = await process.StandardError.ReadToEndAsync(); Task timeoutTask = Task.Delay(11000); 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 new SongInfo(); } if (process.ExitCode != 0) { Logger.LogError((object)$"yt-dlp error code: {process.ExitCode}"); if (!string.IsNullOrWhiteSpace(error)) { Logger.LogWarning((object)("yt-dlp title fetch error: " + error.Trim())); } return new SongInfo(); } SongInfo info; try { info = JsonConvert.DeserializeObject(json); int num; if (info != null && !string.IsNullOrEmpty(info.title)) { _ = info.duration; num = 1; } else { num = 0; } if (num != 0) { try { info.title = new string(info.title.Where((char c) => !char.IsControl(c) || c == '\n' || c == '\r' || c == '\t').ToArray()); } catch (Exception ex4) { Exception ex3 = ex4; Logger.LogWarning((object)("Error sanitizing title: " + ex3.Message)); } } } catch (Exception ex2) { Logger.LogWarning((object)("Error converting audio info: " + ex2.Message)); return new SongInfo(); } return (info == null) ? new SongInfo() : info; } finally { if (process != null) { ((IDisposable)process).Dispose(); } } } catch (Exception ex4) { Exception ex = ex4; Logger.LogError((object)("Error getting video title: " + ex.Message)); return new SongInfo(); } } private static async Task GetLatestGithubReleaseDate(string url) { HttpClient client = new HttpClient(); try { client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("BoomboxCartUpgrade", "1.3.4")); client.Timeout = TimeSpan.FromSeconds(20.0); try { GitHubRelease release = JsonUtility.FromJson(await client.GetStringAsync(url)); if (release != null && !string.IsNullOrEmpty(release.published_at)) { return DateTime.Parse(release.published_at).ToUniversalTime(); } } catch (Exception ex) { Exception e = ex; Logger.LogError((object)e.Message); Logger.LogInfo((object)("Update check failed for url: " + url)); } return null; } finally { ((IDisposable)client)?.Dispose(); } } public static bool CleanUp() { if (Directory.Exists(Path.GetDirectoryName(tempFolder))) { Directory.Delete(tempFolder, recursive: true); } return false; } public static bool Uninstall() { if (IsUpdatingResources) { Logger.LogWarning((object)"Attempted to uninstall downloader while resource update in progress. Please wait until the update is complete and try again if you need to."); return false; } CleanUp(); try { if (Directory.Exists(baseFolder)) { Directory.Delete(baseFolder, recursive: true); } } catch (Exception ex) { Logger.LogError((object)("Error during uninstallation of dependencies: " + ex.Message)); return false; } Logger.LogInfo((object)"Downloader dependencies uninstalled successfully."); return true; } public static Task Reinstall() { if (!Uninstall()) { return Task.FromResult(result: false); } Logger.LogInfo((object)"Starting downloader dependencies reinstall."); initializeTask = InitializeInternalAsync(); return initializeTask; } } } namespace BoomBoxCartMod.Patches { [HarmonyPatch(typeof(CameraAim))] internal class CamerAimPatch { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; private static ManualLogSource Logger => Instance.logger; [HarmonyPatch("Update")] [HarmonyPrefix] private static bool PatchPlayerAim(CameraAim __instance) { return !BoomboxUI.anyUISHown || Instance.modDisabled; } } [HarmonyPatch(typeof(LevelGenerator))] internal class LevelGeneratorPatch { private static BoomBoxCartMod Instance = BoomBoxCartMod.instance; private static ManualLogSource Logger => Instance.logger; [HarmonyPatch("Start")] [HarmonyPostfix] private static void StartPatch(LevelGenerator __instance) { if ((Object)(object)((Component)__instance).gameObject == (Object)null) { return; } if ((Object)(object)Instance.baseListener == (Object)null) { Instance.baseListener = ((Component)__instance).gameObject.AddComponent(); } if ((Object)(object)Instance.baseListener == (Object)null || !PhotonNetwork.IsConnected || Instance.modDisabled) { return; } Logger.LogDebug((object)"Level started, checking if other players are using the mod."); Instance.baseListener.lastUserAmount = Instance.baseListener.GetAllModUsers().Count; Instance.baseListener.GetAllModUsers().Clear(); if (PhotonNetwork.IsMasterClient && !Instance.modDisabled) { PhotonView photonView = ((MonoBehaviourPun)Instance.baseListener).photonView; if (photonView != null) { photonView.RPC("ModFeedbackCheck", (RpcTarget)1, new object[2] { "1.3.4", PhotonNetwork.LocalPlayer.ActorNumber }); } Instance.baseListener.AddModUser(PhotonNetwork.LocalPlayer.ActorNumber); } } } [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 (!Instance.modDisabled && !RunManager.instance.levelShop.Contains(RunManager.instance.levelCurrent)) { if ((Object)(object)((Component)__instance).GetComponent() == (Object)null) { Boombox boombox = ((Component)__instance).gameObject.AddComponent(); Instance.data.InitializeBoomboxData(boombox); Logger.LogInfo((object)("Boombox component added to " + ((Object)__instance).name)); } if ((Object)(object)((Component)__instance).GetComponent() == (Object)null) { ((Component)__instance).gameObject.AddComponent(); } } } } public static class PlayerGrabbingTracker { public static Dictionary playerGrabbingMap = new Dictionary(); 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) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) if (Instance.modDisabled) { return; } if ((Object)(object)__instance.physGrabObject != (Object)null) { PlayerGrabbingTracker.SetLocalPlayerGrabbing(__instance.physGrabObject.gameObject); if (Keyboard.current != null && ((ButtonControl)Keyboard.current[Instance.OpenUIKey.Value]).wasPressedThisFrame && (Object)(object)__instance.physGrabObject.GetComponent() != (Object)null) { BoomboxController component = __instance.physGrabObject.GetComponent(); if ((Object)(object)component != (Object)null) { component.RequestBoomboxControl(); } } } else { PlayerGrabbingTracker.SetLocalPlayerGrabbing(null); } } } }