using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace LethalMusicSync; [BepInPlugin("com.foxwood.lethalmusicsync", "LethalMusicSync", "1.0.2")] public class LethalMusicSyncPlugin : BaseUnityPlugin { public const string MOD_GUID = "com.foxwood.lethalmusicsync"; public const string MOD_NAME = "LethalMusicSync"; public const string MOD_VERSION = "1.0.2"; internal static ManualLogSource LoggerInstance; private static LethalMusicSyncPlugin Instance; public static AudioSource CustomShipSpeaker; public static AudioSource CustomShipMusic; public static string CurrentPlayingUrl = ""; public static bool IsCustomBoomboxPlaying = false; public static AudioClip[] OriginalBoomboxClips; public static List SpeakerQueue = new List(); public static List SpeakerQueueTitles = new List(); public static int SpeakerQueueIndex = -1; public static string SpeakerLoopMode = "off"; public static List MusicQueue = new List(); public static List MusicQueueTitles = new List(); public static int MusicQueueIndex = -1; public static string MusicLoopMode = "off"; public static List BoomboxQueue = new List(); public static List BoomboxQueueTitles = new List(); public static int BoomboxQueueIndex = -1; public static string BoomboxLoopMode = "off"; public static bool IsProgrammaticActivation = false; public static bool IsSpeakerPaused = false; public static bool IsMusicPaused = false; public static bool IsBoomboxPaused = false; public static float SpeakerVolumeSetting = 0.8f; public static float MusicVolumeSetting = 0.6f; public static ConfigEntry HudHotkey; public static ConfigEntry MusicControlPermission; public static bool showBoomboxGui = false; private string guiUrlInput = ""; private float lastVolumeBroadcastTime = 0f; private int lastBroadcastedVolumePercent = -1; public static readonly Queue pendingChatBroadcasts = new Queue(); private void Awake() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown Instance = this; LoggerInstance = ((BaseUnityPlugin)this).Logger; LoggerInstance.LogInfo((object)"LethalMusicSync is loading..."); HudHotkey = ((BaseUnityPlugin)this).Config.Bind("General", "HUDHotkey", "F8", "The hotkey used to toggle the Boombox HUD Console dashboard. Can be F8, F9, F10, F11, Tab, etc."); MusicControlPermission = ((BaseUnityPlugin)this).Config.Bind("General", "MusicControlPermission", "everyone", "Who can control the music (speaker, music, boombox). Options: everyone, host."); Harmony val = new Harmony("com.foxwood.lethalmusicsync"); val.PatchAll(); LoggerInstance.LogInfo((object)"LethalMusicSync patches applied successfully!"); } public static Key ParseKey(string keyStr) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) try { return (Key)Enum.Parse(typeof(Key), keyStr, ignoreCase: true); } catch { LoggerInstance.LogWarning((object)("[LMS] Invalid HUDHotkey config value: '" + keyStr + "'. Falling back to F8.")); return (Key)101; } } public static bool CanLocalPlayerControl() { if (MusicControlPermission == null) { return true; } string text = MusicControlPermission.Value.ToLower(); if (text == "host" || text == "hostonly") { bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } return flag; } return true; } private void Update() { //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) while (pendingChatBroadcasts.Count > 0) { string text = pendingChatBroadcasts.Dequeue(); if ((Object)(object)HUDManager.Instance != (Object)null) { try { HUDManager.Instance.AddTextToChatOnServer(text, -1); Log("[LMS] Safely sent GUI command: " + text); } catch (Exception ex) { LoggerInstance.LogError((object)("[LMS] Failed to send GUI command: " + ex.Message)); } } } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && val.currentlyHeldObjectServer is BoomboxItem) { bool flag = false; try { string keyStr = ((HudHotkey != null && !string.IsNullOrEmpty(HudHotkey.Value)) ? HudHotkey.Value : "F8"); Key val2 = ParseKey(keyStr); if (Keyboard.current != null && ((ButtonControl)Keyboard.current[val2]).wasPressedThisFrame) { flag = true; } } catch (Exception ex) { LoggerInstance.LogError((object)("Error reading InputSystem keyboard: " + ex.Message)); } if (flag) { showBoomboxGui = !showBoomboxGui; if (showBoomboxGui) { Cursor.visible = true; Cursor.lockState = (CursorLockMode)0; val.disableLookInput = true; } else { Cursor.visible = false; Cursor.lockState = (CursorLockMode)1; val.disableLookInput = false; } } } else if (showBoomboxGui) { showBoomboxGui = false; Cursor.visible = false; Cursor.lockState = (CursorLockMode)1; if ((Object)(object)val != (Object)null) { val.disableLookInput = false; } } if (!((Object)(object)val != (Object)null)) { return; } bool flag2 = false; if ((!val.isPlayerDead || !((Object)(object)val.spectatedPlayerScript != (Object)null)) ? val.isInsideFactory : val.spectatedPlayerScript.isInsideFactory) { if ((Object)(object)CustomShipSpeaker != (Object)null && CustomShipSpeaker.volume > 0f) { CustomShipSpeaker.volume = 0f; } if ((Object)(object)CustomShipMusic != (Object)null && CustomShipMusic.volume > 0f) { CustomShipMusic.volume = 0f; } } else { if ((Object)(object)CustomShipSpeaker != (Object)null && CustomShipSpeaker.volume != SpeakerVolumeSetting) { CustomShipSpeaker.volume = SpeakerVolumeSetting; } if ((Object)(object)CustomShipMusic != (Object)null && CustomShipMusic.volume != MusicVolumeSetting) { CustomShipMusic.volume = MusicVolumeSetting; } } } private void OnGUI() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Expected O, but got Unknown //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_034c: Expected O, but got Unknown //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Unknown result type (might be due to invalid IL or missing references) //IL_03eb: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Unknown result type (might be due to invalid IL or missing references) //IL_06ad: Unknown result type (might be due to invalid IL or missing references) //IL_0504: Unknown result type (might be due to invalid IL or missing references) //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_06eb: Unknown result type (might be due to invalid IL or missing references) //IL_079e: Unknown result type (might be due to invalid IL or missing references) //IL_07d0: Unknown result type (might be due to invalid IL or missing references) //IL_0808: Unknown result type (might be due to invalid IL or missing references) //IL_062c: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Invalid comparison between Unknown and I4 //IL_08cb: Unknown result type (might be due to invalid IL or missing references) //IL_099f: Unknown result type (might be due to invalid IL or missing references) PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val == (Object)null) { return; } GrabbableObject currentlyHeldObjectServer = val.currentlyHeldObjectServer; BoomboxItem val2 = (BoomboxItem)(object)((currentlyHeldObjectServer is BoomboxItem) ? currentlyHeldObjectServer : null); if ((Object)(object)val2 == (Object)null) { return; } GUIStyle val3 = new GUIStyle(GUI.skin.label); val3.normal.textColor = Color.cyan; val3.fontSize = 13; val3.fontStyle = (FontStyle)1; GUI.backgroundColor = new Color(0f, 0.1f, 0.15f, 0.85f); GUI.Box(new Rect(15f, 15f, 250f, 40f), ""); string text = ((HudHotkey != null && !string.IsNullOrEmpty(HudHotkey.Value)) ? HudHotkey.Value : "F8"); GUI.Label(new Rect(25f, 23f, 230f, 25f), "\ud83d\udcfb [LMS HUD] Press " + text + " to Toggle Dashboard", val3); if (!showBoomboxGui) { return; } Rect val4 = default(Rect); ((Rect)(ref val4))..ctor((float)Screen.width / 2f - 200f, (float)Screen.height / 2f - 190f, 400f, 380f); GUI.backgroundColor = new Color(0.08f, 0.12f, 0.08f, 0.98f); GUI.Box(val4, ""); GUIStyle val5 = new GUIStyle(GUI.skin.label); val5.alignment = (TextAnchor)4; val5.fontSize = 15; val5.fontStyle = (FontStyle)1; val5.normal.textColor = Color.green; GUI.Label(new Rect(((Rect)(ref val4)).x, ((Rect)(ref val4)).y + 12f, ((Rect)(ref val4)).width, 25f), "=== \ud83d\udcfb LMS BOOMBOX CONSOLE ===", val5); string text2 = "Idle (Queue Empty)"; if (BoomboxQueueIndex >= 0 && BoomboxQueueIndex < BoomboxQueueTitles.Count) { text2 = BoomboxQueueTitles[BoomboxQueueIndex]; } GUIStyle val6 = new GUIStyle(GUI.skin.label); val6.alignment = (TextAnchor)4; val6.normal.textColor = Color.white; val6.fontSize = 12; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, ((Rect)(ref val4)).y + 45f, ((Rect)(ref val4)).width - 40f, 20f), "Playing: " + text2, val6); GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, ((Rect)(ref val4)).y + 65f, ((Rect)(ref val4)).width - 40f, 20f), "Loop Mode: " + BoomboxLoopMode.ToUpper(), val6); float num = ((Rect)(ref val4)).y + 95f; float num2 = 110f; float num3 = 30f; bool flag = CanLocalPlayerControl(); if (!flag) { GUIStyle val7 = new GUIStyle(GUI.skin.label); val7.alignment = (TextAnchor)4; val7.normal.textColor = Color.red; val7.fontSize = 11; val7.fontStyle = (FontStyle)1; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, ((Rect)(ref val4)).y + 35f, ((Rect)(ref val4)).width - 40f, 15f), "⚠\ufe0f LOBBY OWNER ONLY PERMISSIONS ENABLED", val7); } GUI.enabled = flag; string text3 = (val2.isPlayingMusic ? "PAUSE" : "RESUME"); if (GUI.Button(new Rect(((Rect)(ref val4)).x + 20f, num, num2, num3), text3)) { if (val2.isPlayingMusic) { string item = "[LMS_SYNC] BOOMBOX PAUSE"; pendingChatBroadcasts.Enqueue(item); } else { string item = "[LMS_SYNC] BOOMBOX RESUME"; pendingChatBroadcasts.Enqueue(item); } } if (GUI.Button(new Rect(((Rect)(ref val4)).x + 145f, num, num2, num3), "SKIP")) { string item = "[LMS_SYNC] BOOMBOX SKIP"; pendingChatBroadcasts.Enqueue(item); } if (GUI.Button(new Rect(((Rect)(ref val4)).x + 270f, num, num2, num3), "CLEAR")) { string item = "[LMS_SYNC] BOOMBOX CLEAR"; pendingChatBroadcasts.Enqueue(item); } GUI.enabled = true; float num4 = num + 45f; if ((Object)(object)val2.boomboxAudio != (Object)null) { GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num4, 120f, 20f), "Volume: " + Mathf.RoundToInt(val2.boomboxAudio.volume * 100f) + "%", val6); float num5 = GUI.HorizontalSlider(new Rect(((Rect)(ref val4)).x + 140f, num4 + 5f, 240f, 20f), val2.boomboxAudio.volume, 0f, 1f); int num6 = Mathf.RoundToInt(num5 * 100f); if (Mathf.Abs(num5 - val2.boomboxAudio.volume) > 0.01f) { val2.boomboxAudio.volume = num5; if (flag && num6 != lastBroadcastedVolumePercent && Time.time - lastVolumeBroadcastTime > 0.25f) { lastVolumeBroadcastTime = Time.time; lastBroadcastedVolumePercent = num6; string item = "[LMS_SYNC] BOOMBOX VOLUME " + num6; pendingChatBroadcasts.Enqueue(item); } } if (Event.current != null && (int)Event.current.type == 1 && flag && num6 != lastBroadcastedVolumePercent) { lastVolumeBroadcastTime = Time.time; lastBroadcastedVolumePercent = num6; string item = "[LMS_SYNC] BOOMBOX VOLUME " + num6; pendingChatBroadcasts.Enqueue(item); } } else { GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num4, 360f, 20f), "Volume: [Audio Source Missing]", val6); } GUI.enabled = flag; float num7 = num4 + 30f; if (GUI.Button(new Rect(((Rect)(ref val4)).x + 20f, num7, 360f, 30f), "CYCLE LOOP (Current: " + BoomboxLoopMode.ToUpper() + ")")) { string text4 = "off"; if (BoomboxLoopMode == "off") { text4 = "track"; } else if (BoomboxLoopMode == "track") { text4 = "queue"; } string item = "[LMS_SYNC] BOOMBOX LOOP " + text4; pendingChatBroadcasts.Enqueue(item); } float num8 = num7 + 45f; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num8, 360f, 20f), "Paste Link or type YouTube Search query:", val6); guiUrlInput = GUI.TextField(new Rect(((Rect)(ref val4)).x + 20f, num8 + 20f, 360f, 25f), guiUrlInput); float num9 = num8 + 55f; if (GUI.Button(new Rect(((Rect)(ref val4)).x + 20f, num9, 170f, 35f), "PLAY NOW") && !string.IsNullOrEmpty(guiUrlInput)) { string text5 = guiUrlInput.Trim(); if (!text5.StartsWith("http://") && !text5.StartsWith("https://")) { SearchAndQueueYoutube(text5, "BOOMBOX", "PLAY", viaGui: true); } else { string item = "[LMS_SYNC] BOOMBOX PLAY " + text5; pendingChatBroadcasts.Enqueue(item); } guiUrlInput = ""; } if (GUI.Button(new Rect(((Rect)(ref val4)).x + 210f, num9, 170f, 35f), "ADD TO QUEUE") && !string.IsNullOrEmpty(guiUrlInput)) { string text5 = guiUrlInput.Trim(); if (!text5.StartsWith("http://") && !text5.StartsWith("https://")) { SearchAndQueueYoutube(text5, "BOOMBOX", "QUEUE", viaGui: true); } else { string item = "[LMS_SYNC] BOOMBOX QUEUE " + text5; pendingChatBroadcasts.Enqueue(item); } guiUrlInput = ""; } GUI.enabled = true; float num10 = num9 + 45f; GUI.Label(new Rect(((Rect)(ref val4)).x + 20f, num10, 360f, 20f), "Total Tracks in Queue: " + BoomboxQueue.Count, val6); } public static void Log(string message) { LoggerInstance.LogInfo((object)message); } public static string ResolveSunoUrl(string inputUrl) { if (string.IsNullOrEmpty(inputUrl)) { return ""; } if (inputUrl.Contains("cdn1.suno.ai") && inputUrl.EndsWith(".mp3")) { return inputUrl; } Match match = Regex.Match(inputUrl, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (match.Success) { string value = match.Groups[1].Value; string text = "https://cdn1.suno.ai/" + value + ".mp3"; Log("Resolved Suno AI URL: " + inputUrl + " -> " + text); return text; } return inputUrl; } public static AudioSource GetOrCreateShipAudioSource(string device) { //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030a: Expected O, but got Unknown //IL_0324: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find("HangarShip"); if ((Object)(object)val == (Object)null) { val = GameObject.Find("HangarShip(Clone)"); } if ((Object)(object)val == (Object)null && (Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.shipAnimatorObject != (Object)null) { val = ((Component)StartOfRound.Instance.shipAnimatorObject).gameObject; } if ((Object)(object)val == (Object)null) { Log("HangarShip not found in scene. Cannot spawn audio source."); return null; } PlayerControllerB val2 = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); bool flag = false; if ((Object)(object)val2 != (Object)null) { flag = ((!val2.isPlayerDead || !((Object)(object)val2.spectatedPlayerScript != (Object)null)) ? val2.isInsideFactory : val2.spectatedPlayerScript.isInsideFactory); } if (device == "SPEAKER") { if ((Object)(object)CustomShipSpeaker != (Object)null) { return CustomShipSpeaker; } GameObject val3 = new GameObject("LethalMusicSyncSpeaker3D"); val3.transform.SetParent(val.transform); val3.transform.localPosition = new Vector3(0f, 2.2f, -1f); AudioSource val4 = val3.AddComponent(); val4.playOnAwake = false; val4.spatialBlend = 1f; val4.rolloffMode = (AudioRolloffMode)0; val4.minDistance = 3f; val4.maxDistance = 18f; val4.volume = (flag ? 0f : SpeakerVolumeSetting); AudioSource componentInChildren = val.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = componentInChildren.outputAudioMixerGroup; Log("[LMS] Routed Speaker through Ship AudioMixerGroup: " + ((Object)componentInChildren.outputAudioMixerGroup).name); } else { AudioSource[] array = Object.FindObjectsOfType(); AudioSource[] array2 = array; foreach (AudioSource val5 in array2) { if ((Object)(object)val5 != (Object)null && (Object)(object)val5.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = val5.outputAudioMixerGroup; Log("[LMS] Routed Speaker through Fallback AudioMixerGroup: " + ((Object)val5.outputAudioMixerGroup).name); break; } } } CustomShipSpeaker = val4; Log("Custom 3D Spatial Hangar Ship Speaker spawned successfully!"); return CustomShipSpeaker; } if (device == "MUSIC") { if ((Object)(object)CustomShipMusic != (Object)null) { return CustomShipMusic; } GameObject val6 = new GameObject("LethalMusicSyncMusic2D"); val6.transform.SetParent(val.transform); val6.transform.localPosition = Vector3.zero; AudioSource val4 = val6.AddComponent(); val4.playOnAwake = false; val4.spatialBlend = 0f; val4.volume = (flag ? 0f : MusicVolumeSetting); AudioSource componentInChildren = val.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null && (Object)(object)componentInChildren.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = componentInChildren.outputAudioMixerGroup; Log("[LMS] Routed Music through Ship AudioMixerGroup: " + ((Object)componentInChildren.outputAudioMixerGroup).name); } else { AudioSource[] array = Object.FindObjectsOfType(); AudioSource[] array2 = array; foreach (AudioSource val5 in array2) { if ((Object)(object)val5 != (Object)null && (Object)(object)val5.outputAudioMixerGroup != (Object)null) { val4.outputAudioMixerGroup = val5.outputAudioMixerGroup; Log("[LMS] Routed Music through Fallback AudioMixerGroup: " + ((Object)val5.outputAudioMixerGroup).name); break; } } } CustomShipMusic = val4; Log("Custom 2D Global Ship Background Music spawned successfully!"); return CustomShipMusic; } return null; } public static void PlayAudioFromUrl(AudioSource source, string url, string device, Action onSuccess = null) { if (!((Object)(object)source == (Object)null) && !string.IsNullOrEmpty(url)) { ((MonoBehaviour)Instance).StartCoroutine(StreamAudioCoroutine(source, url, device, onSuccess)); } } private static IEnumerator StreamAudioCoroutine(AudioSource source, string url, string device, Action onSuccess) { switch (device) { case "SPEAKER": IsSpeakerPaused = false; break; case "MUSIC": IsMusicPaused = false; break; case "BOOMBOX": IsBoomboxPaused = false; break; } string targetUrl = url; if (url.Contains("youtube.com") || url.Contains("youtu.be")) { Log("[LMS] Resolving YouTube URL via Cobalt API..."); string requestJson = "{\"url\":\"" + url + "\",\"downloadMode\":\"audio\"}"; byte[] requestBytes = Encoding.UTF8.GetBytes(requestJson); string[] cobaltEndpoints = new string[2] { "https://apicobalt.mgytr.top/", "https://api.cobalt.tools/" }; bool resolvedSuccessfully = false; try { string[] array = cobaltEndpoints; foreach (string endpoint in array) { Log("[LMS] Sending request to Cobalt endpoint: " + endpoint); UnityWebRequest resolveRequest = new UnityWebRequest(endpoint, "POST"); try { resolveRequest.uploadHandler = (UploadHandler)new UploadHandlerRaw(requestBytes); resolveRequest.downloadHandler = (DownloadHandler)new DownloadHandlerBuffer(); resolveRequest.SetRequestHeader("Content-Type", "application/json"); resolveRequest.SetRequestHeader("Accept", "application/json"); yield return resolveRequest.SendWebRequest(); if ((int)resolveRequest.result == 2 || (int)resolveRequest.result == 3) { Log("[LMS] Cobalt endpoint error (" + endpoint + "): " + resolveRequest.error); if (resolveRequest.downloadHandler != null && !string.IsNullOrEmpty(resolveRequest.downloadHandler.text)) { Log("[LMS] Server response details: " + resolveRequest.downloadHandler.text); } continue; } string responseText = resolveRequest.downloadHandler.text; if (responseText.Contains("error.api.auth")) { Log("[LMS] Cobalt endpoint (" + endpoint + ") failed due to auth lock. Response: " + responseText); continue; } Match match = Regex.Match(responseText, "\"url\":\"(.*?)\""); if (match.Success) { targetUrl = match.Groups[1].Value; Log("[LMS] Successfully resolved YouTube direct stream URL via " + endpoint); Match match2 = Regex.Match(responseText, "\"title\":\"(.*?)\""); if (match2.Success) { string value = match2.Groups[1].Value; UpdateQueueTitle(url, value, device); } resolvedSuccessfully = true; break; } Log("[LMS] Cobalt response parsing failed for endpoint " + endpoint + ". Response: " + responseText); } finally { ((IDisposable)resolveRequest)?.Dispose(); } } } finally { } if (!resolvedSuccessfully) { Log("[LMS] Failed to resolve YouTube URL through any Cobalt endpoints. Aborting stream."); yield break; } } else if (url.Contains("suno.com/playlist") || url.Contains("suno.com/p/")) { Log("[LMS] Suno Playlist URL detected. Fetching playlist page to extract tracks..."); string playlistId = ""; Match idMatch = Regex.Match(url, "/(?:playlist|p)/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (idMatch.Success) { playlistId = idMatch.Groups[1].Value.ToLower(); } UnityWebRequest pageRequest = UnityWebRequest.Get(url); try { pageRequest.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); pageRequest.SetRequestHeader("Referer", "https://suno.com/"); yield return pageRequest.SendWebRequest(); if ((int)pageRequest.result == 2 || (int)pageRequest.result == 3) { Log("[LMS] Failed to fetch Suno playlist page: " + pageRequest.error); } else { string text = pageRequest.downloadHandler.text; string text2 = ""; try { int num = text.IndexOf("playlist_clips"); if (num != -1) { int num2 = text.LastIndexOf("", num); if (num2 != -1 && num3 != -1) { string input = text.Substring(num2, num3 - num2); Match match3 = Regex.Match(input, "self\\.__next_f\\.push\\(\\[1,\\s*\"(.*?)\"\\s*\\]\\)", RegexOptions.Singleline); if (match3.Success) { string value2 = match3.Groups[1].Value; string text3 = JsonConvert.DeserializeObject("\"" + value2 + "\""); int num4 = text3.IndexOf("["); if (num4 != -1) { JToken token = JToken.Parse(text3.Substring(num4)); JToken val = FindPlaylistClips(token); if (val != null && (int)val.Type == 2 && val.HasValues) { JToken val2 = val[(object)0][(object)"clip"]; if (val2 != null) { JToken val3 = val2[(object)"id"]; if (val3 != null) { text2 = ((object)val3).ToString(); } } } } } } } } catch { } if (string.IsNullOrEmpty(text2)) { MatchCollection matchCollection = Regex.Matches(text, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); foreach (Match item in matchCollection) { string text4 = item.Value.ToLower(); if (text4 != playlistId && text4 != "019c23e4-1dd3-7d05-8d4f-341d10bb7c55") { text2 = text4; break; } } } if (!string.IsNullOrEmpty(text2)) { targetUrl = "https://cdn1.suno.ai/" + text2 + ".mp3"; Log("[LMS] Successfully resolved Suno Playlist to track direct URL: " + targetUrl); } else { Log("[LMS] Could not find any song UUIDs in Suno playlist page."); } } } finally { ((IDisposable)pageRequest)?.Dispose(); } } else if (url.Contains("suno.com/song") || url.Contains("suno.com/track")) { Match match5 = Regex.Match(url, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (match5.Success) { string value3 = match5.Groups[1].Value; targetUrl = "https://cdn1.suno.ai/" + value3 + ".mp3"; Log("[LMS] Resolved Suno song to direct URL: " + targetUrl); } } else { targetUrl = ResolveSunoUrl(url); } Log("[LMS] Starting stream of URL: " + targetUrl); UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(targetUrl, (AudioType)13); try { if (targetUrl.Contains("suno.ai") || targetUrl.Contains("suno.com")) { www.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); www.SetRequestHeader("Referer", "https://suno.com/"); } yield return www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Log("[LMS] Stream Error: " + www.error); yield break; } AudioClip content = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)content != (Object)null) { source.clip = content; source.loop = false; source.Play(); Log("[LMS] Playing audio track successfully!"); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { string text5 = "Audio Stream"; if (device == "SPEAKER" && SpeakerQueueIndex >= 0 && SpeakerQueueIndex < SpeakerQueueTitles.Count) { text5 = SpeakerQueueTitles[SpeakerQueueIndex]; } else if (device == "MUSIC" && MusicQueueIndex >= 0 && MusicQueueIndex < MusicQueueTitles.Count) { text5 = MusicQueueTitles[MusicQueueIndex]; } else if (device == "BOOMBOX" && BoomboxQueueIndex >= 0 && BoomboxQueueIndex < BoomboxQueueTitles.Count) { text5 = BoomboxQueueTitles[BoomboxQueueIndex]; } HUDManager.Instance.AddTextToChatOnServer("[LMS] ▶ Now Playing: " + text5 + " (" + device + ")", -1); } ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, content, device)); onSuccess?.Invoke(content); } else { Log("[LMS] Download succeeded, but clip is null."); } } finally { ((IDisposable)www)?.Dispose(); } } private static JToken FindPlaylistClips(JToken token) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Invalid comparison between Unknown and I4 //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (token == null) { return null; } if ((int)token.Type == 1) { JObject val = (JObject)token; if (val.ContainsKey("playlist_clips")) { return val["playlist_clips"]; } foreach (KeyValuePair item in val) { JToken val2 = FindPlaylistClips(item.Value); if (val2 != null) { return val2; } } } else if ((int)token.Type == 2) { foreach (JToken item2 in (IEnumerable)token) { JToken val2 = FindPlaylistClips(item2); if (val2 != null) { return val2; } } } return null; } public static void SearchAndQueueYoutube(string query, string device, string action, bool viaGui) { ((MonoBehaviour)Instance).StartCoroutine(SearchYoutubeCoroutine(device, action, query, viaGui)); } private static IEnumerator SearchYoutubeCoroutine(string device, string action, string query, bool viaGui) { string escapedQuery = Uri.EscapeDataString(query); string resolvedUrl = null; string resolvedTitle = null; string ytSearchUrl = "https://www.youtube.com/results?search_query=" + escapedQuery; Log("[LMS] Searching YouTube directly via: " + ytSearchUrl); UnityWebRequest ytRequest = UnityWebRequest.Get(ytSearchUrl); try { ytRequest.timeout = 6; ytRequest.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); yield return ytRequest.SendWebRequest(); if ((int)ytRequest.result == 1) { string text = ytRequest.downloadHandler.text; Match match = Regex.Match(text, "\\\"videoRenderer\\\":\\{\\\"videoId\\\":\\\"([a-zA-Z0-9_-]{11})\\\""); if (match.Success) { resolvedUrl = "https://www.youtube.com/watch?v=" + match.Groups[1].Value; Match match2 = Regex.Match(text, "\\\"videoRenderer\\\":\\{\\\"videoId\\\":\\\"([a-zA-Z0-9_-]{11})\\\".*?\\\"title\\\":\\{\\\"runs\\\":\\[\\{\\\"text\\\":\\\"([^\\\"]+)\\\""); if (match2.Success && match2.Groups[1].Value == match.Groups[1].Value) { resolvedTitle = match2.Groups[2].Value; try { resolvedTitle = Regex.Unescape(resolvedTitle); } catch { } } else { resolvedTitle = query; } Log("[LMS] Direct YouTube search succeeded!"); } } } finally { ((IDisposable)ytRequest)?.Dispose(); } if (string.IsNullOrEmpty(resolvedUrl)) { string[] instances = new string[6] { "https://inv.nadeko.net", "https://invidious.nerdvpn.de", "https://iv.melmac.space", "https://invidious.no-logs.com", "https://invidious.flokinet.to", "https://yewtu.be" }; try { string[] array = instances; foreach (string instance in array) { string searchUrl = instance + "/api/v1/search?q=" + escapedQuery; Log("[LMS] Direct search failed. Trying Invidious fallback: " + searchUrl); UnityWebRequest request = UnityWebRequest.Get(searchUrl); try { request.timeout = 5; yield return request.SendWebRequest(); if ((int)request.result == 1) { try { string text2 = request.downloadHandler.text; JArray val = JArray.Parse(text2); foreach (JToken item in val) { if (item[(object)"type"] != null && ((object)item[(object)"type"]).ToString() == "video") { string text3 = ((object)item[(object)"videoId"]).ToString(); resolvedTitle = ((item[(object)"title"] != null) ? ((object)item[(object)"title"]).ToString() : query); resolvedUrl = "https://www.youtube.com/watch?v=" + text3; break; } } } catch (Exception ex) { Log("[LMS] Error parsing search JSON: " + ex.Message); } } } finally { ((IDisposable)request)?.Dispose(); } if (!string.IsNullOrEmpty(resolvedUrl)) { break; } } } finally { } } if (!string.IsNullOrEmpty(resolvedUrl)) { Log("[LMS] Search resolved to: " + resolvedUrl + " (" + resolvedTitle + ")"); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] \ud83d\udd0d Found: " + resolvedTitle + " (" + device.ToUpper() + ")", -1); } string text4 = "[LMS_SYNC] " + device.ToUpper() + " " + action.ToUpper() + " " + resolvedUrl; if (viaGui) { pendingChatBroadcasts.Enqueue(text4); } else if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer(text4, -1); } } else { string text5 = "YouTube search failed for: \"" + query + "\""; Log("[LMS] " + text5); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] Search failed. No video found for search query.", -1); } } } public static void ResolveAndQueueSunoPlaylist(string url, string device, bool clearQueue) { ((MonoBehaviour)Instance).StartCoroutine(ResolveSunoPlaylistCoroutine(url, device, clearQueue)); } private static IEnumerator ResolveSunoPlaylistCoroutine(string url, string device, bool clearQueue) { Log("[LMS] Scraping Suno Playlist tracks: " + url); string playlistId = ""; Match idMatch = Regex.Match(url, "/(?:playlist|p)/([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); if (idMatch.Success) { playlistId = idMatch.Groups[1].Value.ToLower(); } UnityWebRequest pageRequest = UnityWebRequest.Get(url); try { pageRequest.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"); pageRequest.SetRequestHeader("Referer", "https://suno.com/"); yield return pageRequest.SendWebRequest(); if ((int)pageRequest.result == 2 || (int)pageRequest.result == 3) { Log("[LMS] Failed to scrape playlist: " + pageRequest.error); yield break; } string text = pageRequest.downloadHandler.text; List list = new List(); List list2 = new List(); try { int num = text.IndexOf("playlist_clips"); if (num != -1) { int num2 = text.LastIndexOf("", num); if (num2 != -1 && num3 != -1) { string input = text.Substring(num2, num3 - num2); Match match = Regex.Match(input, "self\\.__next_f\\.push\\(\\[1,\\s*\"(.*?)\"\\s*\\]\\)", RegexOptions.Singleline); if (match.Success) { string value = match.Groups[1].Value; string text2 = JsonConvert.DeserializeObject("\"" + value + "\""); int num4 = text2.IndexOf("["); if (num4 != -1) { JToken token = JToken.Parse(text2.Substring(num4)); JToken val = FindPlaylistClips(token); if (val != null && (int)val.Type == 2) { JArray val2 = (JArray)val; foreach (JToken item2 in val2) { JToken val3 = item2[(object)"clip"]; if (val3 == null) { continue; } string text3 = ((object)val3[(object)"id"])?.ToString(); string text4 = ((object)val3[(object)"title"])?.ToString(); if (!string.IsNullOrEmpty(text3)) { string item = "https://cdn1.suno.ai/" + text3 + ".mp3"; if (string.IsNullOrEmpty(text4)) { text4 = "Suno Track " + text3.Substring(0, 8); } if (!list.Contains(item)) { list.Add(item); list2.Add(text4); } } } Log("[LMS] Scraped " + list.Count + " tracks via Strategy 1 (RSC Parser) with real titles!"); } } } } } } catch (Exception ex) { Log("[LMS] Strategy 1 (RSC Parser) error: " + ex.Message + ". Falling back to Strategy 2..."); } if (list.Count == 0) { Log("[LMS] Strategy 1 failed or found no clips. Running Strategy 2 broad UUID scan..."); MatchCollection matchCollection = Regex.Matches(text, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); foreach (Match item3 in matchCollection) { string text5 = item3.Value.ToLower(); if (text5 != playlistId && text5 != "019c23e4-1dd3-7d05-8d4f-341d10bb7c55") { string item = "https://cdn1.suno.ai/" + text5 + ".mp3"; if (!list.Contains(item)) { list.Add(item); list2.Add("Suno Track " + text5.Substring(0, 8)); } } } Log("[LMS] Scraped " + list.Count + " tracks via Strategy 2 (Broad UUID Scan)."); } if (list.Count <= 0) { yield break; } switch (device) { case "SPEAKER": { if (clearQueue) { SpeakerQueue.Clear(); SpeakerQueueTitles.Clear(); SpeakerQueueIndex = 0; } int count = SpeakerQueue.Count; for (int i = 0; i < list.Count; i++) { SpeakerQueue.Add(list[i]); SpeakerQueueTitles.Add(list2[i]); } Log("[LMS] Queued " + list.Count + " tracks for SPEAKER."); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ➕ Queued Playlist: Scraped " + list.Count + " Suno tracks (SPEAKER)", -1); } if (clearQueue || SpeakerQueueIndex == -1) { SpeakerQueueIndex = count; PlayAudioFromUrl(GetOrCreateShipAudioSource("SPEAKER"), SpeakerQueue[SpeakerQueueIndex], "SPEAKER"); } break; } case "MUSIC": { if (clearQueue) { MusicQueue.Clear(); MusicQueueTitles.Clear(); MusicQueueIndex = 0; } int count = MusicQueue.Count; for (int i = 0; i < list.Count; i++) { MusicQueue.Add(list[i]); MusicQueueTitles.Add(list2[i]); } Log("[LMS] Queued " + list.Count + " tracks for MUSIC."); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ➕ Queued Playlist: Scraped " + list.Count + " Suno tracks (MUSIC)", -1); } if (clearQueue || MusicQueueIndex == -1) { MusicQueueIndex = count; PlayAudioFromUrl(GetOrCreateShipAudioSource("MUSIC"), MusicQueue[MusicQueueIndex], "MUSIC"); } break; } case "BOOMBOX": { BoomboxItem nearest = ChatSyncPatch.GetNearestBoombox(); if (!((Object)(object)nearest != (Object)null)) { break; } if (clearQueue) { BoomboxQueue.Clear(); BoomboxQueueTitles.Clear(); BoomboxQueueIndex = 0; } int count = BoomboxQueue.Count; for (int i = 0; i < list.Count; i++) { BoomboxQueue.Add(list[i]); BoomboxQueueTitles.Add(list2[i]); } Log("[LMS] Queued " + list.Count + " tracks for BOOMBOX."); bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ➕ Queued Playlist: Scraped " + list.Count + " Suno tracks (BOOMBOX)", -1); } if (!clearQueue && BoomboxQueueIndex != -1) { break; } BoomboxQueueIndex = count; PlayAudioFromUrl(nearest.boomboxAudio, BoomboxQueue[BoomboxQueueIndex], "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { IsProgrammaticActivation = false; } }); break; } } } finally { ((IDisposable)pageRequest)?.Dispose(); } } public static void QueueSingleTrack(string url, string device, bool clearQueue) { string text = url; if (url.Contains("youtube.com") || url.Contains("youtu.be")) { text = "YouTube Video"; } else if (url.Contains("suno.ai") || url.Contains("suno.com")) { Match match = Regex.Match(url, "([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})"); text = ((!match.Success) ? "Suno Track" : ("Suno Song " + match.Groups[1].Value.Substring(0, 8))); } else { text = "Audio Stream"; } switch (device) { case "SPEAKER": { if (clearQueue) { SpeakerQueue.Clear(); SpeakerQueueTitles.Clear(); } SpeakerQueue.Add(url); SpeakerQueueTitles.Add(text); if (clearQueue || SpeakerQueueIndex == -1) { SpeakerQueueIndex = SpeakerQueue.Count - 1; PlayAudioFromUrl(GetOrCreateShipAudioSource("SPEAKER"), url, "SPEAKER"); break; } bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ➕ Queued: " + text + " (SPEAKER)", -1); } break; } case "MUSIC": { if (clearQueue) { MusicQueue.Clear(); MusicQueueTitles.Clear(); } MusicQueue.Add(url); MusicQueueTitles.Add(text); if (clearQueue || MusicQueueIndex == -1) { MusicQueueIndex = MusicQueue.Count - 1; PlayAudioFromUrl(GetOrCreateShipAudioSource("MUSIC"), url, "MUSIC"); break; } bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ➕ Queued: " + text + " (MUSIC)", -1); } break; } case "BOOMBOX": { BoomboxItem nearest = ChatSyncPatch.GetNearestBoombox(); if (!((Object)(object)nearest != (Object)null)) { break; } if (clearQueue) { BoomboxQueue.Clear(); BoomboxQueueTitles.Clear(); } BoomboxQueue.Add(url); BoomboxQueueTitles.Add(text); if (clearQueue || BoomboxQueueIndex == -1) { BoomboxQueueIndex = BoomboxQueue.Count - 1; PlayAudioFromUrl(nearest.boomboxAudio, url, "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { IsProgrammaticActivation = false; } }); } else { bool flag = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag && (Object)(object)StartOfRound.Instance != (Object)null) { flag = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ➕ Queued: " + text + " (BOOMBOX)", -1); } } break; } } } private static void UpdateQueueTitle(string url, string newTitle, string device) { switch (device) { case "SPEAKER": { for (int i = 0; i < SpeakerQueue.Count; i++) { if (SpeakerQueue[i] == url) { SpeakerQueueTitles[i] = newTitle; } } break; } case "MUSIC": { for (int i = 0; i < MusicQueue.Count; i++) { if (MusicQueue[i] == url) { MusicQueueTitles[i] = newTitle; } } break; } case "BOOMBOX": { for (int i = 0; i < BoomboxQueue.Count; i++) { if (BoomboxQueue[i] == url) { BoomboxQueueTitles[i] = newTitle; } } break; } } } private static IEnumerator TrackEndTrigger(AudioSource source, AudioClip clip, string device) { float startWaitTime = 0f; bool isPaused = false; while ((Object)(object)source != (Object)null && (Object)(object)source.clip == (Object)(object)clip && !source.isPlaying && startWaitTime < 3f) { switch (device) { case "SPEAKER": isPaused = IsSpeakerPaused; break; case "MUSIC": isPaused = IsMusicPaused; break; case "BOOMBOX": isPaused = IsBoomboxPaused; break; } if (isPaused) { break; } startWaitTime += 0.2f; yield return (object)new WaitForSeconds(0.2f); } while ((Object)(object)source != (Object)null && (Object)(object)source.clip == (Object)(object)clip) { switch (device) { case "SPEAKER": isPaused = IsSpeakerPaused; break; case "MUSIC": isPaused = IsMusicPaused; break; case "BOOMBOX": isPaused = IsBoomboxPaused; break; } if (source.isPlaying || isPaused) { yield return (object)new WaitForSeconds(1f); continue; } break; } if (!((Object)(object)source != (Object)null) || !((Object)(object)source.clip == (Object)(object)clip)) { yield break; } switch (device) { case "SPEAKER": isPaused = IsSpeakerPaused; break; case "MUSIC": isPaused = IsMusicPaused; break; case "BOOMBOX": isPaused = IsBoomboxPaused; break; } if (source.isPlaying || isPaused) { yield break; } if (device == "SPEAKER" && SpeakerQueueIndex != -1) { if (SpeakerLoopMode == "track") { source.Play(); ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, clip, device)); } else { Log("[LMS] Speaker track finished naturally. Advancing queue..."); PlayNextSpeakerTrack(); } } else if (device == "MUSIC" && MusicQueueIndex != -1) { if (MusicLoopMode == "track") { source.Play(); ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, clip, device)); } else { Log("[LMS] Music track finished naturally. Advancing queue..."); PlayNextMusicTrack(); } } else if (device == "BOOMBOX" && BoomboxQueueIndex != -1) { if (BoomboxLoopMode == "track") { source.Play(); ((MonoBehaviour)Instance).StartCoroutine(TrackEndTrigger(source, clip, device)); } else { Log("[LMS] Boombox track finished naturally. Advancing queue..."); PlayNextBoomboxTrack(); } } } public static void PlayNextSpeakerTrack() { if (SpeakerQueue.Count == 0) { return; } AudioSource orCreateShipAudioSource = GetOrCreateShipAudioSource("SPEAKER"); if ((Object)(object)orCreateShipAudioSource != (Object)null) { orCreateShipAudioSource.Stop(); orCreateShipAudioSource.clip = null; } SpeakerQueueIndex++; if (SpeakerQueueIndex >= SpeakerQueue.Count) { if (!(SpeakerLoopMode == "queue")) { SpeakerQueueIndex = -1; Log("[LMS] Speaker reached end of queue."); return; } SpeakerQueueIndex = 0; } string text = SpeakerQueue[SpeakerQueueIndex]; Log("[LMS] Playing next speaker track in queue (Index " + SpeakerQueueIndex + "): " + text); PlayAudioFromUrl(orCreateShipAudioSource, text, "SPEAKER"); } public static void PlayNextMusicTrack() { if (MusicQueue.Count == 0) { return; } AudioSource orCreateShipAudioSource = GetOrCreateShipAudioSource("MUSIC"); if ((Object)(object)orCreateShipAudioSource != (Object)null) { orCreateShipAudioSource.Stop(); orCreateShipAudioSource.clip = null; } MusicQueueIndex++; if (MusicQueueIndex >= MusicQueue.Count) { if (!(MusicLoopMode == "queue")) { MusicQueueIndex = -1; Log("[LMS] Global Music reached end of queue."); return; } MusicQueueIndex = 0; } string text = MusicQueue[MusicQueueIndex]; Log("[LMS] Playing next music track in queue (Index " + MusicQueueIndex + "): " + text); PlayAudioFromUrl(orCreateShipAudioSource, text, "MUSIC"); } public static void PlayNextBoomboxTrack() { BoomboxItem nearest = ChatSyncPatch.GetNearestBoombox(); if ((Object)(object)nearest == (Object)null || BoomboxQueue.Count == 0) { return; } if ((Object)(object)nearest.boomboxAudio != (Object)null) { nearest.boomboxAudio.Stop(); nearest.boomboxAudio.clip = null; } BoomboxQueueIndex++; if (BoomboxQueueIndex >= BoomboxQueue.Count) { if (!(BoomboxLoopMode == "queue")) { BoomboxQueueIndex = -1; ((GrabbableObject)nearest).isBeingUsed = false; nearest.isPlayingMusic = false; if (OriginalBoomboxClips != null) { nearest.musicAudios = OriginalBoomboxClips; } Log("[LMS] Boombox reached end of queue."); return; } BoomboxQueueIndex = 0; } string text = BoomboxQueue[BoomboxQueueIndex]; Log("[LMS] Playing next boombox track in queue (Index " + BoomboxQueueIndex + "): " + text); PlayAudioFromUrl(nearest.boomboxAudio, text, "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { IsProgrammaticActivation = false; } }); } public static string GetQueueDisplayString(string device) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("\n========================================="); stringBuilder.AppendLine(" LETHAL MUSIC SYNC QUEUE "); stringBuilder.AppendLine("=========================================\n"); List list = ((device == "SPEAKER") ? SpeakerQueue : ((device == "MUSIC") ? MusicQueue : BoomboxQueue)); List list2 = ((device == "SPEAKER") ? SpeakerQueueTitles : ((device == "MUSIC") ? MusicQueueTitles : BoomboxQueueTitles)); int num = ((device == "SPEAKER") ? SpeakerQueueIndex : ((device == "MUSIC") ? MusicQueueIndex : BoomboxQueueIndex)); string text = ((device == "SPEAKER") ? SpeakerLoopMode : ((device == "MUSIC") ? MusicLoopMode : BoomboxLoopMode)); stringBuilder.AppendLine("DEVICE: " + device); stringBuilder.AppendLine("LOOP MODE: " + text.ToUpper()); stringBuilder.AppendLine("TRACKS: " + list.Count + "\n"); if (list.Count == 0) { stringBuilder.AppendLine("[Queue is currently empty.]"); stringBuilder.AppendLine("Use: play [URL] or queue [URL] to add songs!"); } else { for (int i = 0; i < list.Count; i++) { string text2 = " "; string text3 = ""; if (i == num) { text2 = "> "; text3 = " [PLAYING]"; } stringBuilder.AppendLine(text2 + (i + 1) + ". " + list2[i] + text3); } } stringBuilder.AppendLine("\n=========================================\n"); return stringBuilder.ToString(); } } [HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")] public static class TerminalPatch { [HarmonyPrefix] public static bool Prefix(Terminal __instance, ref TerminalNode __result) { string text = ""; try { if ((Object)(object)__instance.screenText != (Object)null && !string.IsNullOrEmpty(__instance.screenText.text)) { string text2 = __instance.screenText.text; text = ((text2.Length < __instance.textAdded) ? text2.Trim() : text2.Substring(text2.Length - __instance.textAdded).Trim()); if (text.StartsWith(">")) { text = text.Substring(1).Trim(); } } } catch (Exception ex) { LethalMusicSyncPlugin.Log("Error getting terminal text: " + ex.Message); } if (string.IsNullOrEmpty(text)) { return true; } string text3 = text.Trim().ToLower(); if (text3.StartsWith("boombox ") || text3.StartsWith("speaker ") || text3.StartsWith("music ")) { __result = HandleTerminalCommand(__instance, text3); return false; } return true; } private static TerminalNode HandleTerminalCommand(Terminal terminal, string command) { TerminalNode val = ScriptableObject.CreateInstance(); val.clearPreviousText = true; string[] array = command.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { val.displayText = "LethalMusicSync - Invalid Command Format.\nUse: boombox/speaker/music play [URL], queue [URL], skip, loop, list, clear, stop.\n\n"; return val; } string text = array[0]; string text2 = array[1]; switch (text2) { case "queue": if (array.Length < 3) { break; } goto default; default: if (!LethalMusicSyncPlugin.CanLocalPlayerControl()) { val.displayText = "\n=========================================\n LETHAL MUSIC SYNC (v1.0) \n=========================================\n\nDEVICE: " + text.ToUpper() + "\n[ERROR] ONLY THE HOST IS PERMITTED TO CONTROL MUSIC IN THIS LOBBY.\n=========================================\n\n"; return val; } break; case "list": case "qlist": break; } int result; if (text2 == "play" || text2 == "queue" || text2 == "q") { if (array.Length < 3) { val.displayText = "Usage: " + text + " " + text2 + " [URL/Search Query]\n\n"; return val; } string text3 = command.Substring(command.IndexOf(text2) + text2.Length).Trim(); if (!text3.StartsWith("http://") && !text3.StartsWith("https://")) { LethalMusicSyncPlugin.SearchAndQueueYoutube(text3, text, text2, viaGui: false); if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] \ud83d\udd0d Searching YouTube for: \"" + text3 + "\" (" + text.ToUpper() + ")...", -1); } val.displayText = "\n=========================================\n LETHAL MUSIC SYNC (v1.0) \n=========================================\n\nDEVICE: " + text.ToUpper() + "\nACTION: " + text2.ToUpper() + "\nSEARCH: " + text3 + "\n\n[Searching YouTube asynchronously...]\n=========================================\n\n"; } else { string text4 = "[LMS_SYNC] " + text.ToUpper() + " " + text2.ToUpper() + " " + text3; HUDManager.Instance.AddTextToChatOnServer(text4, -1); string text5 = ((text2 == "play") ? "Initiating Stream..." : "Queueing Track..."); val.displayText = "\n=========================================\n LETHAL MUSIC SYNC (v1.0) \n=========================================\n\nDEVICE: " + text.ToUpper() + "\nACTION: " + text5 + "\nURL: " + text3 + "\n\n[Broadcasting network sync...]\n=========================================\n\n"; } } else if (text2 == "stop") { string text4 = "[LMS_SYNC] " + text.ToUpper() + " STOP"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Stopping stream...\n\n"; } else if (text2 == "skip" || text2 == "s") { if (array.Length >= 3 && array[2] == "to") { if (array.Length < 4) { val.displayText = "Usage: " + text + " skip to [index]\n\n"; return val; } string text6 = array[3]; if (!int.TryParse(text6, out result)) { val.displayText = "[ERROR] Index '" + text6 + "' must be a valid integer.\n\n"; return val; } int num = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerQueue.Count : ((text == "music") ? LethalMusicSyncPlugin.MusicQueue.Count : LethalMusicSyncPlugin.BoomboxQueue.Count)); if (result < 1 || result > num) { val.displayText = "[ERROR] Index " + text6 + " is out of bounds. The " + text.ToUpper() + " queue has " + num + " track(s).\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIPTO " + text6; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping to track index " + text6 + "...\n\n"; } else if (array.Length >= 3) { string text6 = array[2]; if (int.TryParse(text6, out result)) { int num = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerQueue.Count : ((text == "music") ? LethalMusicSyncPlugin.MusicQueue.Count : LethalMusicSyncPlugin.BoomboxQueue.Count)); if (result < 1 || result > num) { val.displayText = "[ERROR] Index " + text6 + " is out of bounds. The " + text.ToUpper() + " queue has " + num + " track(s).\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIPTO " + text6; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping to track index " + text6 + "...\n\n"; } else { string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIP"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping current track...\n\n"; } } else { string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIP"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping current track...\n\n"; } } else if (text2 == "skipto" || text2 == "goto") { if (array.Length < 3) { val.displayText = "Usage: " + text + " skipto [index]\n\n"; return val; } string text6 = array[2]; if (!int.TryParse(text6, out result)) { val.displayText = "[ERROR] Index '" + text6 + "' must be a valid integer.\n\n"; return val; } int num = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerQueue.Count : ((text == "music") ? LethalMusicSyncPlugin.MusicQueue.Count : LethalMusicSyncPlugin.BoomboxQueue.Count)); if (result < 1 || result > num) { val.displayText = "[ERROR] Index " + text6 + " is out of bounds. The " + text.ToUpper() + " queue has " + num + " track(s).\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " SKIPTO " + text6; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Skipping to track index " + text6 + "...\n\n"; } else { switch (text2) { case "loop": { string text8 = "off"; if (array.Length >= 3) { text8 = array[2].ToLower(); } else { string text9 = "off"; text9 = ((text == "speaker") ? LethalMusicSyncPlugin.SpeakerLoopMode : ((!(text == "music")) ? LethalMusicSyncPlugin.BoomboxLoopMode : LethalMusicSyncPlugin.MusicLoopMode)); text8 = ((text9 == "off") ? "track" : ((!(text9 == "track")) ? "off" : "queue")); } if (text8 != "off" && text8 != "track" && text8 != "queue") { val.displayText = "Invalid loop mode. Choose: track, queue, off.\n\n"; return val; } string text4 = "[LMS_SYNC] " + text.ToUpper() + " LOOP " + text8; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Setting loop mode to '" + text8.ToUpper() + "'...\n\n"; break; } case "clear": { string text4 = "[LMS_SYNC] " + text.ToUpper() + " CLEAR"; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Clearing current queue...\n\n"; break; } default: if (!(text2 == "queue")) { if (text2 == "volume" || text2 == "vol") { if (array.Length < 3) { val.displayText = "Usage: " + text + " volume [0-100]\n\n"; return val; } string text7 = array[2]; string text4 = "[LMS_SYNC] " + text.ToUpper() + " VOLUME " + text7; HUDManager.Instance.AddTextToChatOnServer(text4, -1); val.displayText = "Adjusting volume to " + text7 + "%\n\n"; } else { val.displayText = "Unknown command. Options: play, queue, list, skip, skipto, loop, clear, stop, volume.\n\n"; } break; } goto case "list"; case "list": case "qlist": val.displayText = LethalMusicSyncPlugin.GetQueueDisplayString(text.ToUpper()); break; } } return val; } } [HarmonyPatch(typeof(HUDManager), "AddTextToChatOnServer")] public static class ChatSyncPatch { [HarmonyPrefix] public static bool Prefix(ref string chatMessage, int playerId) { if (string.IsNullOrEmpty(chatMessage)) { return true; } if (chatMessage.StartsWith("[LMS_SYNC]")) { if (LethalMusicSyncPlugin.MusicControlPermission != null && LethalMusicSyncPlugin.MusicControlPermission.Value.ToLower() == "host" && playerId != 0 && playerId != -1) { LethalMusicSyncPlugin.Log("[LMS] Ignored sync command from non-host player (ID: " + playerId + "). Permissions locked to Host."); return false; } try { string cmd = chatMessage.Substring(10).Trim(); ExecuteSyncCommand(cmd); } catch (Exception ex) { LethalMusicSyncPlugin.Log("Error executing sync command: " + ex.Message); } return false; } return true; } private static void ExecuteSyncCommand(string cmd) { string[] array = cmd.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 2) { return; } string text = array[0]; string text2 = array[1]; if (text2 == "PLAY" || text2 == "QUEUE") { if (array.Length < 3) { return; } string text3 = cmd.Substring(cmd.IndexOf(text2) + text2.Length).Trim(); bool clearQueue = text2 == "PLAY"; if (text == "BOOMBOX") { LethalMusicSyncPlugin.IsCustomBoomboxPlaying = true; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null && LethalMusicSyncPlugin.OriginalBoomboxClips == null) { LethalMusicSyncPlugin.OriginalBoomboxClips = nearestBoombox.musicAudios; } } if (text3.Contains("suno.com/playlist") || text3.Contains("suno.com/p/")) { LethalMusicSyncPlugin.ResolveAndQueueSunoPlaylist(text3, text, clearQueue); } else { LethalMusicSyncPlugin.QueueSingleTrack(text3, text, clearQueue); } return; } switch (text2) { case "STOP": { bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] \ud83d\uded1 Stopping playback (" + text + ")...", -1); } switch (text) { case "SPEAKER": { AudioSource orCreateShipAudioSource = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("SPEAKER"); if ((Object)(object)orCreateShipAudioSource != (Object)null) { orCreateShipAudioSource.Stop(); } LethalMusicSyncPlugin.SpeakerQueueIndex = -1; break; } case "MUSIC": { AudioSource orCreateShipAudioSource2 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("MUSIC"); if ((Object)(object)orCreateShipAudioSource2 != (Object)null) { orCreateShipAudioSource2.Stop(); } LethalMusicSyncPlugin.MusicQueueIndex = -1; break; } case "BOOMBOX": { LethalMusicSyncPlugin.IsCustomBoomboxPlaying = false; LethalMusicSyncPlugin.IsBoomboxPaused = false; LethalMusicSyncPlugin.BoomboxQueueIndex = -1; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { ((GrabbableObject)nearestBoombox).isBeingUsed = false; nearestBoombox.isPlayingMusic = false; if ((Object)(object)nearestBoombox.boomboxAudio != (Object)null) { nearestBoombox.boomboxAudio.Stop(); } LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearestBoombox).ItemActivate(false, false); } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } if (LethalMusicSyncPlugin.OriginalBoomboxClips != null) { nearestBoombox.musicAudios = LethalMusicSyncPlugin.OriginalBoomboxClips; } } break; } } break; } case "PAUSE": if (text == "BOOMBOX") { BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { nearestBoombox.isPlayingMusic = false; nearestBoombox.boomboxAudio.Pause(); LethalMusicSyncPlugin.IsCustomBoomboxPlaying = false; LethalMusicSyncPlugin.IsBoomboxPaused = true; } } break; case "RESUME": { if (!(text == "BOOMBOX")) { break; } BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { nearestBoombox.isPlayingMusic = true; nearestBoombox.boomboxAudio.UnPause(); LethalMusicSyncPlugin.IsCustomBoomboxPlaying = true; LethalMusicSyncPlugin.IsBoomboxPaused = false; LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearestBoombox).ItemActivate(true, true); break; } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } } break; } case "SKIP": { bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ⏭\ufe0f Skipping current track (" + text + ")...", -1); } switch (text) { case "SPEAKER": LethalMusicSyncPlugin.PlayNextSpeakerTrack(); break; case "MUSIC": LethalMusicSyncPlugin.PlayNextMusicTrack(); break; case "BOOMBOX": LethalMusicSyncPlugin.PlayNextBoomboxTrack(); break; } break; } case "SKIPTO": { if (array.Length < 3 || !int.TryParse(array[2], out var result2)) { break; } bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] ⏭\ufe0f Skipping to track #" + result2 + " (" + text + ")...", -1); } result2--; switch (text) { case "SPEAKER": if (result2 >= 0 && result2 < LethalMusicSyncPlugin.SpeakerQueue.Count) { LethalMusicSyncPlugin.Log("[LMS] Execute SKIPTO for SPEAKER index: " + (result2 + 1)); LethalMusicSyncPlugin.SpeakerQueueIndex = result2; AudioSource orCreateShipAudioSource3 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("SPEAKER"); if ((Object)(object)orCreateShipAudioSource3 != (Object)null) { orCreateShipAudioSource3.Stop(); orCreateShipAudioSource3.clip = null; } LethalMusicSyncPlugin.PlayAudioFromUrl(orCreateShipAudioSource3, LethalMusicSyncPlugin.SpeakerQueue[LethalMusicSyncPlugin.SpeakerQueueIndex], "SPEAKER"); } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO SPEAKER failed: Index " + (result2 + 1) + " out of bounds (Count: " + LethalMusicSyncPlugin.SpeakerQueue.Count + ")"); } break; case "MUSIC": if (result2 >= 0 && result2 < LethalMusicSyncPlugin.MusicQueue.Count) { LethalMusicSyncPlugin.Log("[LMS] Execute SKIPTO for MUSIC index: " + (result2 + 1)); LethalMusicSyncPlugin.MusicQueueIndex = result2; AudioSource orCreateShipAudioSource3 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("MUSIC"); if ((Object)(object)orCreateShipAudioSource3 != (Object)null) { orCreateShipAudioSource3.Stop(); orCreateShipAudioSource3.clip = null; } LethalMusicSyncPlugin.PlayAudioFromUrl(orCreateShipAudioSource3, LethalMusicSyncPlugin.MusicQueue[LethalMusicSyncPlugin.MusicQueueIndex], "MUSIC"); } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO MUSIC failed: Index " + (result2 + 1) + " out of bounds (Count: " + LethalMusicSyncPlugin.MusicQueue.Count + ")"); } break; case "BOOMBOX": if (result2 >= 0 && result2 < LethalMusicSyncPlugin.BoomboxQueue.Count) { LethalMusicSyncPlugin.Log("[LMS] Execute SKIPTO for BOOMBOX index: " + (result2 + 1)); LethalMusicSyncPlugin.BoomboxQueueIndex = result2; BoomboxItem nearest = GetNearestBoombox(); if ((Object)(object)nearest != (Object)null) { if ((Object)(object)nearest.boomboxAudio != (Object)null) { nearest.boomboxAudio.Stop(); nearest.boomboxAudio.clip = null; } LethalMusicSyncPlugin.PlayAudioFromUrl(nearest.boomboxAudio, LethalMusicSyncPlugin.BoomboxQueue[LethalMusicSyncPlugin.BoomboxQueueIndex], "BOOMBOX", delegate(AudioClip clip) { nearest.musicAudios = (AudioClip[])(object)new AudioClip[1] { clip }; ((GrabbableObject)nearest).isBeingUsed = true; nearest.isPlayingMusic = true; LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearest).ItemActivate(true, true); } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } }); } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO BOOMBOX failed: No boombox nearby found!"); } } else { LethalMusicSyncPlugin.Log("[LMS] SKIPTO BOOMBOX failed: Index " + (result2 + 1) + " out of bounds (Count: " + LethalMusicSyncPlugin.BoomboxQueue.Count + ")"); } break; } break; } case "LOOP": { if (array.Length < 3) { break; } string text4 = array[2].ToLower(); bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] \ud83d\udd01 Loop mode set to: " + text4.ToUpper() + " (" + text + ")", -1); } switch (text) { case "SPEAKER": LethalMusicSyncPlugin.SpeakerLoopMode = text4; if ((Object)(object)LethalMusicSyncPlugin.CustomShipSpeaker != (Object)null) { LethalMusicSyncPlugin.CustomShipSpeaker.loop = text4 == "track"; } break; case "MUSIC": LethalMusicSyncPlugin.MusicLoopMode = text4; if ((Object)(object)LethalMusicSyncPlugin.CustomShipMusic != (Object)null) { LethalMusicSyncPlugin.CustomShipMusic.loop = text4 == "track"; } break; case "BOOMBOX": { LethalMusicSyncPlugin.BoomboxLoopMode = text4; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null && (Object)(object)nearestBoombox.boomboxAudio != (Object)null) { nearestBoombox.boomboxAudio.loop = text4 == "track"; } break; } } break; } case "CLEAR": { bool flag2 = (Object)(object)GameNetworkManager.Instance != (Object)null && GameNetworkManager.Instance.isHostingGame; if (!flag2 && (Object)(object)StartOfRound.Instance != (Object)null) { flag2 = ((NetworkBehaviour)StartOfRound.Instance).IsServer; } if (flag2 && (Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.AddTextToChatOnServer("[LMS] \ud83e\uddf9 Queue cleared (" + text + ")...", -1); } switch (text) { case "SPEAKER": LethalMusicSyncPlugin.SpeakerQueue.Clear(); LethalMusicSyncPlugin.SpeakerQueueTitles.Clear(); LethalMusicSyncPlugin.SpeakerQueueIndex = -1; if ((Object)(object)LethalMusicSyncPlugin.CustomShipSpeaker != (Object)null) { LethalMusicSyncPlugin.CustomShipSpeaker.Stop(); } break; case "MUSIC": LethalMusicSyncPlugin.MusicQueue.Clear(); LethalMusicSyncPlugin.MusicQueueTitles.Clear(); LethalMusicSyncPlugin.MusicQueueIndex = -1; if ((Object)(object)LethalMusicSyncPlugin.CustomShipMusic != (Object)null) { LethalMusicSyncPlugin.CustomShipMusic.Stop(); } break; case "BOOMBOX": { LethalMusicSyncPlugin.BoomboxQueue.Clear(); LethalMusicSyncPlugin.BoomboxQueueTitles.Clear(); LethalMusicSyncPlugin.IsBoomboxPaused = false; LethalMusicSyncPlugin.BoomboxQueueIndex = -1; BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { ((GrabbableObject)nearestBoombox).isBeingUsed = false; nearestBoombox.isPlayingMusic = false; if ((Object)(object)nearestBoombox.boomboxAudio != (Object)null) { nearestBoombox.boomboxAudio.Stop(); } LethalMusicSyncPlugin.IsProgrammaticActivation = true; try { ((GrabbableObject)nearestBoombox).ItemActivate(false, false); } finally { LethalMusicSyncPlugin.IsProgrammaticActivation = false; } if (LethalMusicSyncPlugin.OriginalBoomboxClips != null) { nearestBoombox.musicAudios = LethalMusicSyncPlugin.OriginalBoomboxClips; } } break; } } break; } case "VOLUME": { if (array.Length < 3 || !float.TryParse(array[2], out var result)) { break; } switch (text) { case "SPEAKER": { LethalMusicSyncPlugin.SpeakerVolumeSetting = result / 100f; AudioSource orCreateShipAudioSource = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("SPEAKER"); PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); bool flag = false; if ((Object)(object)val != (Object)null) { flag = ((!val.isPlayerDead || !((Object)(object)val.spectatedPlayerScript != (Object)null)) ? val.isInsideFactory : val.spectatedPlayerScript.isInsideFactory); } if ((Object)(object)orCreateShipAudioSource != (Object)null && !flag) { orCreateShipAudioSource.volume = LethalMusicSyncPlugin.SpeakerVolumeSetting; } break; } case "MUSIC": { LethalMusicSyncPlugin.MusicVolumeSetting = result / 100f; AudioSource orCreateShipAudioSource2 = LethalMusicSyncPlugin.GetOrCreateShipAudioSource("MUSIC"); PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); bool flag = false; if ((Object)(object)val != (Object)null) { flag = ((!val.isPlayerDead || !((Object)(object)val.spectatedPlayerScript != (Object)null)) ? val.isInsideFactory : val.spectatedPlayerScript.isInsideFactory); } if ((Object)(object)orCreateShipAudioSource2 != (Object)null && !flag) { orCreateShipAudioSource2.volume = LethalMusicSyncPlugin.MusicVolumeSetting; } break; } case "BOOMBOX": { BoomboxItem nearestBoombox = GetNearestBoombox(); if ((Object)(object)nearestBoombox != (Object)null) { nearestBoombox.boomboxAudio.volume = result / 100f; } break; } } break; } } } public static BoomboxItem GetNearestBoombox() { //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) BoomboxItem[] array = Object.FindObjectsOfType(); if (array == null || array.Length == 0) { return null; } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); BoomboxItem[] array2; if ((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform != (Object)null) { GrabbableObject currentlyHeldObjectServer = val.currentlyHeldObjectServer; BoomboxItem val2 = (BoomboxItem)(object)((currentlyHeldObjectServer is BoomboxItem) ? currentlyHeldObjectServer : null); if ((Object)(object)val2 != (Object)null && (Object)(object)((Component)val2).transform != (Object)null) { return val2; } BoomboxItem result = null; float num = float.MaxValue; array2 = array; foreach (BoomboxItem val3 in array2) { if (!((Object)(object)val3 == (Object)null) && !((Object)(object)((Component)val3).gameObject == (Object)null) && !((Object)(object)((Component)val3).transform == (Object)null)) { float num2 = Vector3.Distance(((Component)val).transform.position, ((Component)val3).transform.position); if (num2 < num) { num = num2; result = val3; } } } return result; } array2 = array; foreach (BoomboxItem val3 in array2) { if ((Object)(object)val3 != (Object)null && (Object)(object)((Component)val3).gameObject != (Object)null && (Object)(object)((Component)val3).transform != (Object)null) { return val3; } } return null; } } public static class BoomboxPocketHelper { public static void ProcessPocket(GrabbableObject item) { BoomboxItem val = (BoomboxItem)(object)((item is BoomboxItem) ? item : null); if ((Object)(object)val != (Object)null && LethalMusicSyncPlugin.BoomboxQueueIndex != -1) { LethalMusicSyncPlugin.Log("[LMS] Intercepted Boombox PocketItem (" + ((object)item).GetType().Name + "). Pausing stream to preserve track time..."); ((GrabbableObject)val).isPocketed = true; ((GrabbableObject)val).EnableItemMeshes(false); if ((Object)(object)((GrabbableObject)val).playerHeldBy != (Object)null) { ((GrabbableObject)val).parentObject = ((GrabbableObject)val).playerHeldBy.localItemHolder; } ((GrabbableObject)val).isBeingUsed = false; val.isPlayingMusic = false; val.boomboxAudio.Pause(); LethalMusicSyncPlugin.IsCustomBoomboxPlaying = false; LethalMusicSyncPlugin.IsBoomboxPaused = true; PlayerControllerB val2 = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val2 != (Object)null && (Object)(object)((GrabbableObject)val).playerHeldBy == (Object)(object)val2) { LethalMusicSyncPlugin.pendingChatBroadcasts.Enqueue("[LMS_SYNC] BOOMBOX PAUSE"); } } } } [HarmonyPatch(typeof(GrabbableObject), "PocketItem")] public static class GrabbablePocketPatch { [HarmonyPrefix] public static bool Prefix(GrabbableObject __instance) { if (__instance is BoomboxItem) { BoomboxPocketHelper.ProcessPocket(__instance); return false; } return true; } } [HarmonyPatch(typeof(GrabbableObject), "DiscardItem")] public static class BoomboxDiscardPatch { [HarmonyPrefix] public static void Prefix(GrabbableObject __instance) { BoomboxItem val = (BoomboxItem)(object)((__instance is BoomboxItem) ? __instance : null); if ((Object)(object)val != (Object)null && LethalMusicSyncPlugin.BoomboxQueueIndex != -1) { LethalMusicSyncPlugin.IsProgrammaticActivation = true; LethalMusicSyncPlugin.Log("[LMS] Boombox dropped — blocking discard-triggered ItemActivate."); } } [HarmonyPostfix] public static void Postfix(GrabbableObject __instance) { BoomboxItem val = (BoomboxItem)(object)((__instance is BoomboxItem) ? __instance : null); if (!((Object)(object)val != (Object)null) || LethalMusicSyncPlugin.BoomboxQueueIndex == -1) { return; } LethalMusicSyncPlugin.IsProgrammaticActivation = false; if (LethalMusicSyncPlugin.IsBoomboxPaused) { ((GrabbableObject)val).isBeingUsed = false; val.isPlayingMusic = false; if ((Object)(object)val.boomboxAudio != (Object)null) { val.boomboxAudio.Pause(); } LethalMusicSyncPlugin.Log("[LMS] Boombox dropped while paused — ensuring it remains paused."); } else { ((GrabbableObject)val).isBeingUsed = true; val.isPlayingMusic = true; if ((Object)(object)val.boomboxAudio != (Object)null && !val.boomboxAudio.isPlaying) { val.boomboxAudio.Play(); } LethalMusicSyncPlugin.Log("[LMS] Boombox dropped while playing — ensuring it keeps playing."); } } } [HarmonyPatch(typeof(BoomboxItem), "ItemActivate")] public static class BoomboxOverridePatch { [HarmonyPrefix] public static bool Prefix(BoomboxItem __instance, bool used, bool buttonDown) { if (LethalMusicSyncPlugin.showBoomboxGui) { return false; } if (LethalMusicSyncPlugin.BoomboxQueueIndex != -1) { if (!LethalMusicSyncPlugin.IsProgrammaticActivation) { PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); if ((Object)(object)val != (Object)null && (Object)(object)((GrabbableObject)__instance).playerHeldBy == (Object)(object)val) { if (!used) { LethalMusicSyncPlugin.pendingChatBroadcasts.Enqueue("[LMS_SYNC] BOOMBOX PAUSE"); } else { LethalMusicSyncPlugin.pendingChatBroadcasts.Enqueue("[LMS_SYNC] BOOMBOX RESUME"); } } } return false; } return true; } } [HarmonyPatch(typeof(Terminal), "Start")] public static class TerminalHelpPatch { [HarmonyPostfix] public static void Postfix(Terminal __instance) { if (!((Object)(object)__instance != (Object)null)) { return; } if ((Object)(object)__instance.screenText != (Object)null) { __instance.screenText.characterLimit = 9999; LethalMusicSyncPlugin.Log("[LMS] Increased Terminal input characterLimit to 9999!"); } if (!((Object)(object)__instance.terminalNodes != (Object)null) || __instance.terminalNodes.allKeywords == null) { return; } TerminalKeyword[] allKeywords = __instance.terminalNodes.allKeywords; foreach (TerminalKeyword val in allKeywords) { if ((Object)(object)val != (Object)null && val.word == "help" && (Object)(object)val.specialKeywordResult != (Object)null) { TerminalNode specialKeywordResult = val.specialKeywordResult; specialKeywordResult.displayText += "\n\n>MUSIC CONTROLS\nUse speaker/music/boombox: play [URL], queue [URL], list, skip, skip to [index], loop [track/queue/off], stop, clear, vol [0-100].\n* Speaker: 3D Spatial directional Audio from ship ceiling!\n* Music: 2D Global Ambient flat Background Music!\n* Boombox: Local Portable 3D Audio!\n\n"; LethalMusicSyncPlugin.Log("[LMS] Injected music commands into the Terminal Help menu!"); break; } } } } [HarmonyPatch(typeof(Terminal), "TextChanged")] public static class TerminalTextChangedPatch { [HarmonyPrefix] public static void Prefix(Terminal __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.currentNode != (Object)null && __instance.currentNode.maxCharactersToType < 9999) { __instance.currentNode.maxCharactersToType = 9999; } } }