The BepInEx console will not appear when launching like it does for other games on Thunderstore (you can turn it back on in your BepInEx.cfg file). If your PEAK crashes on startup, add -dx12 to your launch parameters.
Decompiled source of sPEAKer v1.8.7
plugins/onlystar.sPEAKer.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Web; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using PEAKLib.Core; using Photon.Pun; using Photon.Realtime; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; using Zorro.ControllerSupport; using sPEAKer.Scripts; using ytModule; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("onlystar")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Some guy had it aboard. Does it still work?")] [assembly: AssemblyFileVersion("1.8.7.0")] [assembly: AssemblyInformationalVersion("1.8.7+a3cec52dc89754e1046023faf8065e564c25dbb9")] [assembly: AssemblyProduct("onlystar.sPEAKer")] [assembly: AssemblyTitle("sPEAKer")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.8.7.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.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [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 BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace sPEAKer { [BepInPlugin("onlystar.sPEAKer", "sPEAKer", "1.8.7")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <DelayedSpawn>d__100 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private Player <masterClient>5__1; private List<Character>.Enumerator <>s__2; private Character <character>5__3; private Vector3 <spawnPos>5__4; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedSpawn>d__100(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <masterClient>5__1 = null; <>s__2 = default(List<Character>.Enumerator); <character>5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //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) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(2f); <>1__state = 1; return true; case 1: <>1__state = -1; <masterClient>5__1 = PhotonNetwork.MasterClient; <>s__2 = Character.AllCharacters.GetEnumerator(); try { while (<>s__2.MoveNext()) { <character>5__3 = <>s__2.Current; Character obj = <character>5__3; if (!object.Equals((obj != null) ? ((MonoBehaviourPun)obj).photonView.Owner : null, <masterClient>5__1)) { continue; } <spawnPos>5__4 = new Vector3(<character>5__3.Center.x + Random.insideUnitSphere.z * 1.5f, <character>5__3.Center.y + 5f, <character>5__3.Center.z + Random.insideUnitSphere.z * 1.5f); PhotonNetwork.InstantiateItemRoom(((Object)_prefab).name, <spawnPos>5__4, Quaternion.identity); break; } } finally { ((IDisposable)<>s__2).Dispose(); } <>s__2 = default(List<Character>.Enumerator); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static GameObject _prefab = null; public static PeakBundle Bundle = null; public static bool ytModuleAvailable = false; private ConfigEntry<bool> debugModeConfig = null; private ConfigEntry<bool> loadDemoSongConfig = null; private ConfigEntry<string> externalAudioFolderPathConfig = null; private ConfigEntry<bool> spawnAtGameStartConfig = null; private ConfigEntry<bool> spawnWithF4Config = null; private ConfigEntry<bool> recallEnabledConfig = null; private ConfigEntry<bool> extendedFormatSupportConfig = null; private ConfigEntry<bool> shuffleOnStartConfig = null; private ConfigEntry<bool> turnOnAndPlayConfig = null; private ConfigEntry<bool> showTrackDurationConfig = null; private ConfigEntry<bool> startupLoadingIndicatorConfig = null; private ConfigEntry<bool> displayAuthorInfoConfig = null; private ConfigEntry<KeyCode> droneToggleKeyConfig = null; private ConfigEntry<KeyCode> recallCommandKeyConfig = null; private ConfigEntry<KeyCode> danceKeyConfig = null; public static Plugin Instance { get; private set; } = null; internal static ManualLogSource Log { get; private set; } = null; public static string DefaultMusicPath { get; private set; } = ""; public static bool DebugMode { get; private set; } public static bool LoadDemoSong { get; private set; } public static string ExternalAudioFolderPath { get; private set; } = ""; private static bool SpawnAtGameStart { get; set; } private static bool SpawnWithF4 { get; set; } private static bool RecallEnabled { get; set; } public static bool ExtendedFormatSupport { get; private set; } public static bool ShuffleOnStart { get; private set; } public static bool TurnOnAndPlay { get; private set; } public static bool ShowTrackDuration { get; private set; } public static bool StartupLoadingIndicator { get; private set; } public static bool DisplayAuthorInfo { get; private set; } public static KeyCode DroneToggleKey { get; private set; } public static KeyCode RecallCommandKey { get; private set; } public static KeyCode DanceKey { get; private set; } private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; SetUserConfigs(); LocalizationFix(); ApplyPatches(); CheckYtModule(); Log.LogInfo((object)"[Plugin] Plugin awakening..."); ModDefinition.GetOrCreate(((BaseUnityPlugin)this).Info.Metadata); AudioMaster.EnsureInitialized(); Netcode.EnsureInitialized(); if (ytModuleAvailable) { Type type = Type.GetType("sPEAKer.Scripts.YouTubePlayer, sPEAKer"); if (type != null) { type.GetMethod("EnsureInitialized")?.Invoke(null, null); } else { Log.LogWarning((object)"[Plugin] ytModule is available, but sPEAKer.Scripts.YouTubePlayer class could not be found!"); } } try { Log.LogInfo((object)"[Plugin] Attempting to load bundle..."); BundleLoader.LoadBundleWithName((BaseUnityPlugin)(object)this, "ps.peakbundle", (Action<PeakBundle>)InitSpeaker); Log.LogInfo((object)"[Plugin] Bundle load request submitted"); } catch (Exception ex) { Log.LogError((object)("[Plugin] Failed to load bundle: " + ex.Message)); } } private void InitSpeaker(PeakBundle bundle) { Log.LogInfo((object)"[Plugin] Initializing sPEAKer with bundle..."); Bundle = bundle; GameObject val = Bundle.LoadAsset<GameObject>("assets/sPEAKer.prefab"); if ((Object)(object)val == (Object)null) { Log.LogError((object)"[Plugin] Failed to load sPEAKer prefab!"); return; } _prefab = val; Log.LogInfo((object)"[Plugin] Successfully loaded sPEAKer prefab."); SceneManager.sceneLoaded += OnSceneLoaded; val.AddComponent<Controller>(); Bundle.Mod.RegisterContent(); Log.LogInfo((object)"[Plugin] Plugin sPEAKer is loaded!"); } private static void CheckYtModule() { try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); if (directoryName == null) { ytModuleAvailable = false; Log.LogError((object)"[Plugin] YT Module is not available (plugins folder not found)."); return; } string text = Path.Combine(directoryName, "..\\onlystar-ytModule"); if (!Directory.Exists(text)) { ytModuleAvailable = false; Log.LogError((object)"[Plugin] YT Module is not available (onlystar-ytModule not found)."); Log.LogInfo((object)("[Plugin] Checked path: " + text)); return; } string[] source = new string[3] { Path.Combine(text, "ffmpeg.exe"), Path.Combine(text, "yt-dlp.exe"), Path.Combine(text, "onlystar-ytModule.dll") }; ytModuleAvailable = source.All(File.Exists); if (ytModuleAvailable) { Log.LogInfo((object)"[Plugin] YT Module is available!"); } else { Log.LogError((object)"[Plugin] YT Module is not available (required files were not found)."); } } catch (Exception ex) { ytModuleAvailable = false; Log.LogError((object)ex); } } private void SetUserConfigs() { //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) DefaultMusicPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new InvalidOperationException(), "..\\onlystar-sPEAKer Music"); externalAudioFolderPathConfig = ((BaseUnityPlugin)this).Config.Bind<string>("Audio Loading", "ExternalAudioFolderPath", DefaultMusicPath, "Folder where the mod will first try to load mixtapes and external audio from.\nIt will default to \"/plugins/onlystar-sPEAKer Music\" in case of error."); ExternalAudioFolderPath = externalAudioFolderPathConfig.Value; loadDemoSongConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio Loading", "LoadDemoSong", true, "Include a demo song in your track list."); LoadDemoSong = loadDemoSongConfig.Value; extendedFormatSupportConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio Loading", "ExtendedFormatSupport", true, "Enables loading of .mp3 and .wav files. May impact performance."); ExtendedFormatSupport = extendedFormatSupportConfig.Value; startupLoadingIndicatorConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio Loading", "StartupLoadingIndicator", true, "Shows a loading indicator when audio loading is occuring."); StartupLoadingIndicator = startupLoadingIndicatorConfig.Value; showTrackDurationConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "ShowTrackDuration", true, "Shows song's length and current time."); ShowTrackDuration = showTrackDurationConfig.Value; shuffleOnStartConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "ShuffleOnStart", false, "Start with Shuffle enabled by default. Saves you a toggle if you only want shuffled playback."); ShuffleOnStart = shuffleOnStartConfig.Value; turnOnAndPlayConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "TurnOnAndPlay", false, "Make sPEAKer start playing immediately after turning it on."); TurnOnAndPlay = turnOnAndPlayConfig.Value; displayAuthorInfoConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "DisplayAuthorInfo", true, "Displays mixtape author under the stamina bar."); DisplayAuthorInfo = displayAuthorInfoConfig.Value; spawnAtGameStartConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawning", "SpawnAtGameStart", true, "Automatically spawn a sPEAKer around the crash site when a game starts."); SpawnAtGameStart = spawnAtGameStartConfig.Value; spawnWithF4Config = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawning", "SpawnWithF4", true, "Allows the host to spawn a sPEAKer in their hands when pressing F4.\nNotes:\n - Only one sPEAKer may exist at a time (for now).\n - Only the host can spawn a sPEAKer (for now)."); SpawnWithF4 = spawnWithF4Config.Value; droneToggleKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "DroneToggle", (KeyCode)112, "Drone Mode toggle."); DroneToggleKey = droneToggleKeyConfig.Value; recallCommandKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "RecallCommand", (KeyCode)306, "Hold this while pressing DroneToggle key to make sPEAKer \"recall\" to you instead of looking for people nearby."); RecallCommandKey = recallCommandKeyConfig.Value; danceKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "DanceEmote", (KeyCode)111, "Makes sPEAKer dance when Drone Mode is active."); DanceKey = danceKeyConfig.Value; recallEnabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawning", "RecallEnabled", true, "Enables recalling existing sPEAKer when pressing F4.\nNotes:\n - Only works if SpawnWithF4 is enabled.\n - sPEAKer will only be recalled if it's in the ground and not following any player."); RecallEnabled = recallEnabledConfig.Value; debugModeConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("zZzZz", "Debug", false, "Enable debug mode AKA verbose logging. If you want to report a bug, this is the way."); DebugMode = debugModeConfig.Value; } private void Update() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_012e: 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) //IL_0104: Invalid comparison between Unknown and I4 //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Invalid comparison between Unknown and I4 //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown((KeyCode)285) && PhotonNetwork.IsMasterClient) { if (RecallEnabled) { Controller controller = Object.FindAnyObjectByType<Controller>(); if ((Object)(object)controller == (Object)null) { Instance.SpawnAtHand(); return; } if ((int)controller.item.itemState == 0 && !controller.droneBehaviour.IsFollowingTarget()) { controller.KillYourself(); Instance.SpawnAtHand(); return; } } if (SpawnWithF4) { if (!sPEAKerExists()) { Instance.SpawnAtHand(); } else if (DebugMode) { Log.LogInfo((object)"[Plugin] sPEAKer already exists - not spawning another one"); } } } if (Input.GetKeyDown(DanceKey)) { Controller controller2 = Object.FindAnyObjectByType<Controller>(); if ((Object)(object)controller2 != (Object)null && (int)controller2.item.itemState == 0) { controller2.StartDance(); } else { Log.LogWarning((object)"[Plugin] Could not find controller to initiate Dance."); } } if (!Input.GetKeyDown(DroneToggleKey)) { return; } Controller controller3 = Object.FindAnyObjectByType<Controller>(); if ((Object)(object)controller3 != (Object)null && (int)controller3.item.itemState == 0) { if (Input.GetKey(RecallCommandKey)) { controller3.RecallToPlayer(Character.localCharacter); } else { controller3.ToggleFloatMode(); } } else { Log.LogWarning((object)"[Plugin] Could not find controller to initiate Drone/Recall mode."); } } private void OnDestroy() { if (DebugMode) { Log.LogInfo((object)"[Plugin] Plugin destroying..."); } SceneManager.sceneLoaded -= OnSceneLoaded; Instance = null; Log = null; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (DebugMode) { Log.LogInfo((object)("[Plugin] Scene loaded: " + ((Scene)(ref scene)).name)); } if (((Scene)(ref scene)).name == "Airport") { sPEAKerUI.Instance.AttemptFontReload(); } if (SpawnAtGameStart && ((Scene)(ref scene)).name.StartsWith("Level_") && PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient) { ((MonoBehaviour)this).StartCoroutine(DelayedSpawn()); } } private void LocalizationFix() { LocalizedText.mainTable["NAME_SPEAKER"] = new List<string>(1) { "sPEAKer" }; LocalizedText.mainTable["..."] = new List<string>(1) { "..." }; LocalizedText.mainTable["VOLUME"] = new List<string>(13) { "Volume", "Volume", "Volume", "Lautstärke", "Volumen", "Volumen", "Volume", "Громкость", "Гучність", "音量", "音量", "音量", "볼륨" }; LocalizedText.mainTable["TURN ON"] = new List<string>(13) { "Turn On", "Allumer", "Accendere", "Einschalten", "Encender", "Encender", "Ligar", "Включить", "Увімкнути", "打开", "打開", "オンにする", "켜기" }; LocalizedText.mainTable["PAUSE"] = new List<string>(13) { "Pause", "Pause", "Pausa", "Pause", "Pausa", "Pausa", "Pausar", "Пауза", "Пауза", "暂停", "暫停", "一時停止", "일시정지" }; LocalizedText.mainTable["SHUFFLE"] = new List<string>(13) { "Shuffle", "Aléatoire", "Casuale", "Zufallswiedergabe", "Aleatorio", "Aleatorio", "Aleatório", "Перемешать", "Перемішати", "随机播放", "隨機播放", "シャッフル", "셔플" }; LocalizedText.mainTable["PREVIOUS"] = new List<string>(13) { "Previous", "Précédent", "Precedente", "Vorheriger", "Anterior", "Anterior", "Anterior", "Предыдущий", "Попередній", "上一首", "上一首", "前へ", "이전" }; LocalizedText.mainTable["LOOP"] = new List<string>(13) { "Loop", "Répéter", "Ripeti", "Wiederholen", "Repetir", "Repetir", "Repetir", "Повторить", "Повторити", "循环", "循環", "リピート", "반복" }; LocalizedText.mainTable["START RADIO"] = new List<string>(13) { "Start Radio", "Lancer la Radio", "Avvia Radio", "Radio starten", "Iniciar Radio", "Iniciar Radio", "Iniciar Rádio", "Запустить радио", "Запустити радіо", "播放电台", "播放電台", "ラジオを開始", "라디오 시작" }; LocalizedText.mainTable["BUFFERING"] = new List<string>(13) { "Buffering...", "Chargement...", "Caricamento...", "Wird geladen...", "Cargando...", "Cargando...", "Carregando...", "Загрузка...", "Завантаження...", "缓冲中...", "正在緩衝...", "バッファリング中...", "버퍼링..." }; LocalizedText.mainTable["SHUFFLE_ON"] = new List<string>(13) { "Shuffle is ON", "Lecture aléatoire activée", "Riproduzione casuale ON", "Zufallswiedergabe EIN", "Aleatorio activado", "Aleatorio activado", "Ordem aleatória LIGADA", "Вперемешку ВКЛ", "Перемішування УВІМК", "随机播放已开启", "隨機播放已開啟", "シャッフルオン", "셔플 켜짐" }; LocalizedText.mainTable["SHUFFLE_OFF"] = new List<string>(13) { "Shuffle is OFF", "Lecture aléatoire désactivée", "Riproduzione casuale OFF", "Zufallswiedergabe AUS", "Aleatorio desactivado", "Aleatorio desactivado", "Ordem aleatória DESLIGADA", "Вперемешку ВЫКЛ", "Перемішування ВИМК", "随机播放已关闭", "隨機播放已關閉", "シャッフルオフ", "셔플 꺼짐" }; LocalizedText.mainTable["LOOP_OFF"] = new List<string>(13) { "Loop is OFF", "Répétition désactivée", "Ripetizione OFF", "Wiederholung AUS", "Repetir desactivado", "Repetir desactivado", "Repetição DESLIGADA", "Повтор ВЫКЛ", "Повтор ВИМК", "循环已关闭", "循環已關閉", "ループオフ", "반복 꺼짐" }; LocalizedText.mainTable["LOOPING_ALL"] = new List<string>(13) { "Looping ALL songs", "Répétition de TOUS les titres", "Ripetizione di TUTTI i brani", "Wiederhole ALLE Titel", "Repitiendo TODAS las canciones", "Repitiendo TODAS las canciones", "Repetindo TODAS as músicas", "Повтор ВСЕХ песен", "Повтор УСІХ пісень", "循环播放全部歌曲", "循環播放全部歌曲", "全曲をループ再生", "모든 곡 반복" }; LocalizedText.mainTable["LOOPING_CURRENT"] = new List<string>(13) { "Looping CURRENT song", "Répétition du titre ACTUEL", "Ripetizione del brano ATTUALE", "Wiederhole AKTUELLEN Titel", "Repitiendo canción ACTUAL", "Repitiendo canción ACTUAL", "Repetindo música ATUAL", "Повтор ТЕКУЩЕЙ песни", "Повтор ПОТОЧНОЇ пісні", "单曲循环", "單曲循環", "この曲をループ再生", "현재 곡 반복" }; LocalizedText.mainTable["NO_PREVIOUS_SONGS"] = new List<string>(13) { "No previous songs available", "Aucun titre précédent disponible", "Nessun brano precedente disponibile", "Keine vorherigen Titel verfügbar", "No hay canciones anteriores", "No hay canciones anteriores", "Nenhuma música anterior disponível", "Нет предыдущих песен", "Немає попередніх пісень", "没有上一首歌曲", "沒有上一首歌曲", "前の曲はありません", "이전 곡 없음" }; LocalizedText.mainTable["PREVIOUS_SONG_NOT_CACHED"] = new List<string>(13) { "Previous song no longer cached", "Le titre précédent n'est plus en cache", "Brano precedente non più in cache", "Vorheriger Titel nicht mehr zwischengespeichert", "La canción anterior ya no está en caché", "La canción anterior ya no está en caché", "Música anterior não está mais em cache", "Предыдущая песня удалена из кэша", "Попередню пісню видалено з кешу", "上一首歌曲已不在缓存中", "上一首歌曲已不在快取中", "前の曲はキャッシュされていません", "이전 곡이 더 이상 캐시되지 않음" }; LocalizedText.mainTable["MIXTAPE_BY"] = new List<string>(13) { "Mixtape by", "Mixtape par", "Mixtape di", "Mixtape von", "Mixtape de", "Mixtape de", "Mixtape por", "Микстейп от", "Мікстейп від", "音乐合集,创作者:", "音樂合輯,創作者:", "ミックステープ作成者:", "믹스테이프 작성자:" }; } private void ApplyPatches() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown Harmony val = new Harmony("onlystar.sPEAKer"); val.PatchAll(Assembly.GetExecutingAssembly()); } private bool sPEAKerExists() { if ((Object)(object)Object.FindAnyObjectByType<Controller>() != (Object)null) { return true; } if (AudioMaster.Instance.IsSpeakerActive()) { return true; } return false; } [IteratorStateMachine(typeof(<DelayedSpawn>d__100))] private static IEnumerator DelayedSpawn() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedSpawn>d__100(0); } public void SpawnAtHand() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: 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) PhotonNetwork.Instantiate("0_Items/" + ((Object)_prefab).name, Character.localCharacter.Center + Vector3.up * 3f, Quaternion.identity, (byte)0, (object[])null).GetComponent<Item>().Interact(Character.localCharacter); } } } namespace sPEAKer.Scripts { public class AudioMaster : MonoBehaviourPun { [CompilerGenerated] private sealed class <MonitorForAutoSkip>d__112 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public AudioMaster <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <MonitorForAutoSkip>d__112(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Auto-skip monitor started."); } <>2__current = (object)new WaitUntil((Func<bool>)(() => (Object)(object)PersistentAudioSource != (Object)null && PersistentAudioSource.isPlaying)); <>1__state = 1; return true; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (CurrentPlaybackState == PlaybackState.Playing && PersistentAudioSource.isPlaying) { if (IsYouTubeMode) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Auto-skip monitor ended - YouTube mode active."); } return false; } <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 2; return true; } if (CurrentPlaybackState == PlaybackState.Playing && !IsYouTubeMode && PhotonNetwork.IsMasterClient) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Song finished. Automatically playing next track."); } <>4__this.AutoSkipToNext(); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Auto-skip monitor ended."); } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static AudioMaster _instance; private Transform? _targetTransform; private Character? _targetCharacter; public Character? LastHolder; private bool _isFollowing; private const int HASH_ALGORITHM_VERSION = 1; private static FileHashCache _fileHashCache = new FileHashCache(); private static string _cachePath = ""; private static readonly List<string> _processedFilePaths = new List<string>(); private static int _cacheHits = 0; private static int _cacheMisses = 0; private Coroutine _autoSkipCoroutine; public static List<AudioClip> LoadedAudioClips { get; private set; } = new List<AudioClip>(); private static List<MixtapeInfo> AvailableMixtapes { get; set; } = new List<MixtapeInfo>(); public static AudioSource PersistentAudioSource { get; private set; } = null; private static AudioSource EffectsAudioSource { get; set; } = null; public static AudioSource YouTubeAudioSource { get; private set; } = null; public static bool IsInitialized { get; private set; } private static bool IsLoading { get; set; } public static bool IsYouTubeMode { get; private set; } public static Dictionary<string, int> SongHashToIndex { get; private set; } = new Dictionary<string, int>(); public static Dictionary<int, string> IndexToSongHash { get; private set; } = new Dictionary<int, string>(); public static PlaybackState CurrentPlaybackState { get; private set; } = PlaybackState.Stopped; public static int CurrentSongIndex { get; private set; } public static float CurrentPausedTime { get; private set; } private static MixtapeInfo CurrentMixtape { get; set; } private static bool IsShuffleEnabled { get; set; } private static List<int> ShuffledIndices { get; set; } = new List<int>(); private static int ShuffledPosition { get; set; } public static LoopState CurrentLoopState { get; private set; } = LoopState.None; public static float NetworkVolume { get; private set; } = 0.35f; private static float LocalVolumeMultiplier { get; set; } = 1f; public static AudioMaster Instance { get { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { _instance = Object.FindAnyObjectByType<AudioMaster>(); if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("sPEAKerAudioMaster"); _instance = val.AddComponent<AudioMaster>(); Object.DontDestroyOnLoad((Object)(object)val); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Created new instance and marked as DontDestroyOnLoad."); } } } return _instance; } } public static AudioSource ActiveSource { get { if (IsYouTubeMode) { return YouTubeAudioSource; } return PersistentAudioSource; } } public static void EnsureInitialized() { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] EnsureInitialized called."); } AudioMaster instance = Instance; if (!IsInitialized && !IsLoading) { Plugin.Log.LogInfo((object)"[AudioMaster] Not initialized, starting initialization..."); instance.InitializeAsync(); } } private void Awake() { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Awake called."); } if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Duplicate instance detected, destroying."); } Object.Destroy((Object)(object)((Component)this).gameObject); return; } _instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Instance set and marked as DontDestroyOnLoad."); } InitializeAudioSources(); SceneManager.sceneLoaded += OnSceneLoaded; if (!IsInitialized && !IsLoading) { InitializeAsync(); } } private void InitializeAudioSources() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown AnimationCurve val = new AnimationCurve(); val.AddKey(0f, 1f); val.AddKey(0.25f, 0.85f); val.AddKey(0.6f, 0.2f); val.AddKey(1f, 0f); for (int i = 0; i < val.length; i++) { val.SmoothTangents(i, 0f); } if ((Object)(object)PersistentAudioSource == (Object)null) { PersistentAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); PersistentAudioSource.loop = false; PersistentAudioSource.playOnAwake = false; PersistentAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; PersistentAudioSource.minDistance = 2f; PersistentAudioSource.maxDistance = 50f; PersistentAudioSource.spatialBlend = 1f; PersistentAudioSource.dopplerLevel = 0.1f; PersistentAudioSource.spatialize = true; PersistentAudioSource.rolloffMode = (AudioRolloffMode)2; PersistentAudioSource.SetCustomCurve((AudioSourceCurveType)0, val); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Created persistent audio source."); } } if ((Object)(object)EffectsAudioSource == (Object)null) { EffectsAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); EffectsAudioSource.playOnAwake = false; EffectsAudioSource.loop = false; EffectsAudioSource.spatialBlend = 1f; EffectsAudioSource.volume = 1f; EffectsAudioSource.minDistance = 2f; EffectsAudioSource.maxDistance = 25f; EffectsAudioSource.rolloffMode = (AudioRolloffMode)0; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Created effects audio source."); } } if ((Object)(object)YouTubeAudioSource == (Object)null) { YouTubeAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); YouTubeAudioSource.playOnAwake = false; YouTubeAudioSource.loop = false; YouTubeAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; YouTubeAudioSource.minDistance = 2f; YouTubeAudioSource.maxDistance = 50f; YouTubeAudioSource.spatialBlend = 1f; YouTubeAudioSource.dopplerLevel = 0.1f; YouTubeAudioSource.spatialize = true; YouTubeAudioSource.rolloffMode = (AudioRolloffMode)2; YouTubeAudioSource.SetCustomCurve((AudioSourceCurveType)0, val); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Created YouTube audio source."); } } } private async Task InitializeAsync() { IsLoading = true; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Starting initialization... "); } LoadOrCreateHashCache(); sPEAKerUI.Instance.ShowLoading("Initializing sPEAKer..."); await LoadFolderAudio(); sPEAKerUI.Instance.ShowLoading("Loading bundle audio..."); await LoadBundleAudio(); if (Plugin.ShuffleOnStart && LoadedAudioClips.Count > 0) { IsShuffleEnabled = true; GenerateShuffledOrder(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Shuffle enabled on startup"); } } sPEAKerUI.Instance.ShowLoadingComplete(); SaveChangesToCache(); IsInitialized = true; IsLoading = false; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Initialization complete. Total clips loaded: " + LoadedAudioClips.Count + ", Mixtapes loaded: " + AvailableMixtapes.Count)); } } private async Task LoadBundleAudio() { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Starting bundle audio loading..."); } int waitCount = 0; while (Plugin.Bundle == null && waitCount < 100) { await Task.Delay(100); waitCount++; } if (Plugin.Bundle == null) { Plugin.Log.LogError((object)"[AudioMaster] Bundle not available after 10 seconds - skipping bundle audio loading"); return; } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Bundle is now available, proceeding with loading..."); } string[] allAssets = Plugin.Bundle.GetAllAssetNames(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Total assets in bundle: " + allAssets.Length)); } string[] jblFX = allAssets.Where((string asset) => asset.Contains("jbl_fx") && asset.EndsWith(".ogg")).ToArray(); string[] array = jblFX; foreach (string fx in array) { AudioClip clip = Plugin.Bundle.LoadAsset<AudioClip>(fx); string filename = Path.GetFileNameWithoutExtension(fx); SoundEffects.SetClip(filename, clip); } string whiteNoise = allAssets.FirstOrDefault((string asset) => asset.Contains("whitenoise")); if (!string.IsNullOrEmpty(whiteNoise)) { AudioClip whiteNoiseClip = Plugin.Bundle.LoadAsset<AudioClip>(whiteNoise); SoundEffects.SetClip("whitenoise", whiteNoiseClip); } if (Plugin.LoadDemoSong) { AudioClip demoClip = Plugin.Bundle.LoadAsset<AudioClip>("assets/speaker audio/portal.ogg"); if ((Object)(object)demoClip != (Object)null) { demoClip.LoadAudioData(); while ((int)demoClip.loadState == 1) { await Task.Yield(); } if ((int)demoClip.loadState == 2) { LoadedAudioClips.Add(demoClip); string songHash = "portalAsset"; int index = LoadedAudioClips.Count - 1; SongHashToIndex[songHash] = index; IndexToSongHash[index] = songHash; } else { Plugin.Log.LogError((object)"[AudioMaster] Failed to load demo song audio data"); } } } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Bundle loading complete."); } } private async Task LoadFolderAudio() { List<string> foldersToProcess = new List<string>(); string finalUserPath; try { string configuredPath = Plugin.ExternalAudioFolderPath; if (Directory.Exists(configuredPath) || (!Directory.Exists(configuredPath) && Directory.GetParent(configuredPath).Exists)) { finalUserPath = configuredPath; } else { Plugin.Log.LogWarning((object)("[AudioMaster] Configured path '" + configuredPath + "' is invalid. Falling back to the default path.")); finalUserPath = Plugin.DefaultMusicPath; } } catch (Exception ex) { Exception e = ex; Plugin.Log.LogError((object)("[AudioMaster] Error validating configured path '" + Plugin.ExternalAudioFolderPath + "'. Falling back to default. Error: " + e.Message)); finalUserPath = Plugin.DefaultMusicPath; } try { if (!Directory.Exists(finalUserPath)) { Directory.CreateDirectory(finalUserPath); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Created audio folder: " + finalUserPath)); } } foldersToProcess.Add(finalUserPath); } catch (Exception e2) { Plugin.Log.LogError((object)("[AudioMaster] Failed to create or access the final user path '" + finalUserPath + "'. Error: " + e2.Message)); } string pluginsPath = Paths.PluginPath; if (Directory.Exists(pluginsPath)) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Scanning for mixtape mods in: " + pluginsPath)); } string[] subDirectories = Directory.GetDirectories(pluginsPath, "*", SearchOption.AllDirectories); string[] array = subDirectories; foreach (string subDir in array) { if (File.Exists(Path.Combine(subDir, "sPEAKer.json")) && !foldersToProcess.Contains(subDir)) { foldersToProcess.Add(subDir); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Found mixtape mod folder: " + subDir)); } } } } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Total unique folders to process: " + foldersToProcess.Count)); } foreach (string folderPath in foldersToProcess) { await ProcessAudioFolder(folderPath); await LoadAudioClipsFromMixtapes(folderPath); } } private async Task ProcessAudioFolder(string folderPath) { string folderPath2 = folderPath; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Processing loose audio files in: " + folderPath2)); } List<string> searchPatterns = new List<string> { "*.ogg" }; if (Plugin.ExtendedFormatSupport) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Extended format support is ON. Searching for .mp3 and .wav files."); } searchPatterns.Add("*.mp3"); searchPatterns.Add("*.wav"); } List<string> audioFiles = searchPatterns.SelectMany((string pattern) => Directory.GetFiles(folderPath2, pattern, SearchOption.TopDirectoryOnly)).ToList(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Found " + audioFiles.Count + " loose audio files in " + folderPath2)); } if (audioFiles.Count == 0) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] No loose audio files found, skipping: " + folderPath2)); } return; } _processedFilePaths.AddRange(audioFiles.Select(NormalizePath)); int successCount = 0; int failCount = 0; foreach (string file in audioFiles) { string fileName = Path.GetFileName(file); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Loading file: " + fileName)); } string url = "file://" + file.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); AudioType audioType = GetAudioTypeFromExtension(file); if ((int)audioType == 0) { if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)("[AudioMaster] Skipping unsupported file type: " + fileName)); } continue; } UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, audioType); try { ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = false; await (AsyncOperation)(object)www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Plugin.Log.LogError((object)("[AudioMaster] Failed to load audio: " + fileName + " - Error: " + www.error)); failCount++; } else { AudioClip clip = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = Path.GetFileNameWithoutExtension(file); string songHash = await GenerateFileHashAsync(file); if (songHash == null) { continue; } clip.LoadAudioData(); while ((int)clip.loadState == 1) { await Task.Yield(); } if ((int)clip.loadState == 2) { LoadedAudioClips.Add(clip); int index = LoadedAudioClips.Count - 1; SongHashToIndex[songHash] = index; IndexToSongHash[index] = songHash; successCount++; } else { Plugin.Log.LogError((object)("[AudioMaster] Failed to preload audio data for: " + file)); } } } } finally { ((IDisposable)www)?.Dispose(); } await Task.Yield(); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Loose file processing complete for " + folderPath2 + ". Success: " + successCount + ", Failed: " + failCount)); } } private async Task LoadAudioClipsFromMixtapes(string basePath) { int mixtapeSuccessCount = 0; int mixtapeFailCount = 0; string[] subDirectories = Directory.GetDirectories(basePath); if (Plugin.DebugMode && subDirectories.Length != 0) { Plugin.Log.LogInfo((object)("[AudioMaster] Found " + subDirectories.Length + " potential mixtape folders in " + basePath)); } string[] array = subDirectories; foreach (string mixtapeDir in array) { if (await LoadMixtapeAsync(mixtapeDir)) { mixtapeSuccessCount++; } else { mixtapeFailCount++; } } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] All mixtape folder loading complete for " + basePath + ". Success: " + mixtapeSuccessCount + ", Failed: " + mixtapeFailCount)); } } private async Task<bool> LoadMixtapeAsync(string mixtapePath) { string mixtapePath2 = mixtapePath; string mixtapeName = new DirectoryInfo(mixtapePath2).Name; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Loading mixtape: " + mixtapeName)); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] From path: " + mixtapePath2)); } MixtapeInfo mixtapeInfo = new MixtapeInfo { Name = mixtapeName, Author = "Unknown", FirstSongIndex = LoadedAudioClips.Count, Path = mixtapePath2 }; string metadataPath = Path.Combine(mixtapePath2, "mixtape.json"); if (File.Exists(metadataPath)) { try { string jsonContent = File.ReadAllText(metadataPath); MixtapeMetadata metadata = JsonUtility.FromJson<MixtapeMetadata>(jsonContent); if (!string.IsNullOrEmpty(metadata.name)) { mixtapeInfo.Name = metadata.name; sPEAKerUI.Instance.UpdateLoadingPhase("Loading " + mixtapeInfo.Name + "..."); } else { sPEAKerUI.Instance.UpdateLoadingPhase("Loading " + mixtapeName + "..."); } if (!string.IsNullOrEmpty(metadata.author)) { mixtapeInfo.Author = metadata.author; } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Loaded mixtape metadata - Name: " + mixtapeInfo.Name + ", Author: " + mixtapeInfo.Author)); } } catch (Exception ex) { Exception e2 = ex; Plugin.Log.LogWarning((object)("[AudioMaster] Failed to parse mixtape metadata for " + mixtapeName + ": " + e2.Message)); } } string iconPath = Path.Combine(mixtapePath2, "..", "icon.png"); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Checking for icon at path: " + iconPath)); } string normalizedIconPath = Path.GetFullPath(iconPath); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Normalized icon path: " + normalizedIconPath)); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Icon file exists: {File.Exists(normalizedIconPath)}"); } if (File.Exists(normalizedIconPath)) { try { FileInfo fileInfo = new FileInfo(normalizedIconPath); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Icon file size: {fileInfo.Length} bytes"); } string iconUrl = "file://" + normalizedIconPath.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Converted icon URL: " + iconUrl)); } UnityWebRequest www2 = UnityWebRequest.Get(iconUrl); try { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Starting web request for icon: " + mixtapeName)); } await (AsyncOperation)(object)www2.SendWebRequest(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Web request result: {www2.result}"); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Response code: {www2.responseCode}"); } if ((int)www2.result == 2 || (int)www2.result == 3) { Plugin.Log.LogError((object)("[AudioMaster] Failed to load icon for mixtape " + mixtapeName + ": " + www2.error)); } else { byte[] iconBytes = www2.downloadHandler.data; Texture2D iconTexture = new Texture2D(2, 2); if (ImageConversion.LoadImage(iconTexture, iconBytes)) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Icon texture dimensions: {((Texture)iconTexture).width}x{((Texture)iconTexture).height}"); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Icon texture is readable: {((Texture)iconTexture).isReadable}"); } mixtapeInfo.IconTexture = iconTexture; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Successfully loaded and assigned icon for mixtape: " + mixtapeName)); } } else { Plugin.Log.LogError((object)("[AudioMaster] Failed to load image data from bytes for: " + mixtapeName)); } } } finally { ((IDisposable)www2)?.Dispose(); } } catch (Exception ex) { Exception e = ex; Plugin.Log.LogError((object)("[AudioMaster] Exception loading icon for mixtape " + mixtapeName + ": " + e.Message)); } } List<string> searchPatterns = new List<string> { "*.ogg" }; if (Plugin.ExtendedFormatSupport) { searchPatterns.Add("*.mp3"); searchPatterns.Add("*.wav"); } List<string> audioFiles = searchPatterns.SelectMany((string pattern) => Directory.GetFiles(mixtapePath2, pattern, SearchOption.TopDirectoryOnly)).ToList(); _processedFilePaths.AddRange(audioFiles.Select(NormalizePath)); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Found " + audioFiles.Count + " audio files in mixtape.")); } int songSuccessCount = 0; float totalDuration = 0f; foreach (string audioFile in audioFiles) { string fileName = Path.GetFileName(audioFile); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Loading mixtape song: " + fileName)); } string url = "file://" + audioFile.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); AudioType audioType = GetAudioTypeFromExtension(audioFile); if ((int)audioType == 0) { if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)("[AudioMaster] Skipping unsupported file type in mixtape: " + fileName)); } continue; } UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, audioType); try { ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = false; await (AsyncOperation)(object)www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Plugin.Log.LogError((object)("[AudioMaster] Failed to load mixtape audio: " + fileName + " - Error: " + www.error)); } else { AudioClip clip = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = Path.GetFileNameWithoutExtension(audioFile); string songHash = await GenerateFileHashAsync(audioFile); if (songHash == null) { continue; } clip.LoadAudioData(); while ((int)clip.loadState == 1) { await Task.Yield(); } if ((int)clip.loadState == 2) { LoadedAudioClips.Add(clip); int index = LoadedAudioClips.Count - 1; SongHashToIndex[songHash] = index; IndexToSongHash[index] = songHash; totalDuration += clip.length; songSuccessCount++; } else { Plugin.Log.LogError((object)("[AudioMaster] Failed to preload audio data for mixtape song: " + audioFile)); } } } } finally { ((IDisposable)www)?.Dispose(); } await Task.Yield(); } mixtapeInfo.SongCount = songSuccessCount; mixtapeInfo.LastSongIndex = mixtapeInfo.FirstSongIndex + songSuccessCount - 1; mixtapeInfo.TotalLength = FormatDuration(totalDuration); if (songSuccessCount > 0) { AvailableMixtapes.Add(mixtapeInfo); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Successfully loaded mixtape '" + mixtapeInfo.Name + "' by " + mixtapeInfo.Author + " - " + songSuccessCount + " songs, " + mixtapeInfo.TotalLength + " total")); } return true; } Plugin.Log.LogWarning((object)("[AudioMaster] Mixtape folder '" + mixtapeInfo.Name + "' contains no valid audio files.")); return false; } public void LoadAudioClipsFromBundle() { //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Invalid comparison between Unknown and I4 //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Invalid comparison between Unknown and I4 if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Starting bundle audio loading..."); } string[] allAssetNames = Plugin.Bundle.GetAllAssetNames(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Total assets in bundle: " + allAssetNames.Length)); Plugin.Log.LogInfo((object)"--- All Assets in Bundle ---"); string[] array = allAssetNames; foreach (string text in array) { Plugin.Log.LogInfo((object)(" -> " + text)); } Plugin.Log.LogInfo((object)"----------------------------"); } string[] array2 = allAssetNames.Where((string asset) => asset.Contains("jbl_fx") && asset.EndsWith(".ogg")).ToArray(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Found {array2.Length} JBL_FX assets:"); string[] array3 = array2; foreach (string text2 in array3) { Plugin.Log.LogInfo((object)(" -> " + text2)); } } string[] array4 = array2; foreach (string text3 in array4) { AudioClip clip2 = Plugin.Bundle.LoadAsset<AudioClip>(text3); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text3); SoundEffects.SetClip(fileNameWithoutExtension, clip2); } string text4 = allAssetNames.FirstOrDefault((string asset) => asset.Contains("whitenoise")); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Found whitenoise asset: " + (text4 ?? "NULL"))); } AudioClip clip3 = Plugin.Bundle.LoadAsset<AudioClip>(text4); SoundEffects.SetClip("whitenoise", clip3); string[] array5 = allAssetNames.Where((string asset) => asset.Contains("speaker audio") && asset.EndsWith(".ogg")).ToArray(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Found " + array5.Length + " demo songs in bundle.")); string[] array6 = array5; foreach (string text5 in array6) { Plugin.Log.LogInfo((object)(" -> " + text5)); } } int num = 0; int num2 = 0; string[] array7 = array5; foreach (string text6 in array7) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Loading bundle audio: " + text6)); } try { AudioClip clip = Plugin.Bundle.LoadAsset<AudioClip>(text6); if ((Object)(object)clip != (Object)null) { if ((int)clip.loadState == 2) { goto IL_0354; } clip.LoadAudioData(); if ((int)clip.loadState != 3) { goto IL_0354; } Plugin.Log.LogError((object)("[AudioMaster] Failed to preload audio data for bundle clip: " + ((Object)clip).name)); num2++; } else { Plugin.Log.LogError((object)("[AudioMaster] Failed to load audio clip from bundle: " + text6)); num2++; } goto end_IL_02b9; IL_0354: if (LoadedAudioClips.All((AudioClip c) => ((Object)c).name != ((Object)clip).name)) { LoadedAudioClips.Add(clip); string text7 = "portalAsset"; int num3 = LoadedAudioClips.Count - 1; SongHashToIndex[text7] = num3; IndexToSongHash[num3] = text7; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Successfully loaded bundle audio: " + ((Object)clip).name + " (Length: " + clip.length + "s)")); } num++; } else if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)("[AudioMaster] Audio clip already loaded: " + ((Object)clip).name)); } end_IL_02b9:; } catch (Exception ex) { Plugin.Log.LogError((object)("[AudioMaster] Exception loading " + text6 + ": " + ex.Message)); num2++; } } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Bundle loading complete. Success: " + num + ", Failed: " + num2 + ", Total clips now: " + LoadedAudioClips.Count)); } if (LoadedAudioClips.Count > 0 && Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] All loaded clips:"); for (int n = 0; n < LoadedAudioClips.Count; n++) { Plugin.Log.LogInfo((object)(" [" + (n + 1) + "] " + ((Object)LoadedAudioClips[n]).name)); } } } public static void UpdatePlaybackState(PlaybackState newState, int songIndex = -1, float pausedTime = 0f) { CurrentPlaybackState = newState; if (songIndex >= 0) { CurrentSongIndex = songIndex; } CurrentPausedTime = pausedTime; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Playback state updated - State: {newState}, Song: {CurrentSongIndex}, PausedTime: {pausedTime}"); } } private static void ResetPlaybackState() { CurrentPlaybackState = PlaybackState.Stopped; CurrentSongIndex = 0; CurrentPausedTime = 0f; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Playback state reset to defaults"); } } private string FormatDuration(float totalSeconds) { int num = Mathf.FloorToInt(totalSeconds / 60f); int num2 = Mathf.FloorToInt(totalSeconds % 60f); return $"{num}:{num2:00}"; } private string NormalizePath(string path) { return path.Replace('\\', '/'); } private async Task<string> GenerateFileHashAsync(string filePath) { string normalizedPath = NormalizePath(filePath); try { FileInfo fileInfo = new FileInfo(filePath); long size = fileInfo.Length; DateTime lastModified = fileInfo.LastWriteTimeUtc; if (_fileHashCache.Hashes.TryGetValue(normalizedPath, out CacheEntry cachedEntry) && cachedEntry.Size == size && cachedEntry.LastModified == lastModified) { if (Plugin.DebugMode) { _cacheHits++; } return cachedEntry.Hash; } if (Plugin.DebugMode) { _cacheMisses++; } string fileName = Path.GetFileName(filePath); string uniqueId = $"{fileName}|{size}"; using SHA256 sha256 = SHA256.Create(); byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(uniqueId)); string newHash = Convert.ToBase64String(hashBytes).Substring(0, 12); _fileHashCache.Hashes[normalizedPath] = new CacheEntry { Hash = newHash, LastModified = lastModified, Size = size }; return await Task.FromResult(newHash); } catch (Exception e) { Plugin.Log.LogError((object)("[AudioMaster] Failed to generate metadata hash for " + filePath + ": " + e.Message)); return await Task.FromResult<string>(null); } } public static MixtapeInfo GetMixtapeForSongIndex(int songIndex) { return AvailableMixtapes.FirstOrDefault((MixtapeInfo m) => m.ContainsSongIndex(songIndex)) ?? new MixtapeInfo { Author = "unknown" }; } private void LoadOrCreateHashCache() { try { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string text = Path.Combine(directoryName, "data"); _cachePath = Path.Combine(text, "file_hashes.json"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Cache] Created cache data folder at: " + text)); } } if (File.Exists(_cachePath)) { string text2 = File.ReadAllText(_cachePath); FileHashCache fileHashCache = JsonConvert.DeserializeObject<FileHashCache>(text2); if (fileHashCache != null && fileHashCache.Version == 1 && fileHashCache.AlgorithmVersion == 1) { _fileHashCache = fileHashCache; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Cache] Successfully loaded {_fileHashCache.Hashes.Count} hashes from cache (Algo v{1})."); } return; } if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)"[Cache] Cache is from an incompatible algorithm version. Regenerating."); } _fileHashCache = new FileHashCache { AlgorithmVersion = 1 }; } else { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Cache] No cache file found. A new one will be created."); } _fileHashCache = new FileHashCache { AlgorithmVersion = 1 }; } } catch (Exception ex) { Plugin.Log.LogError((object)("[Cache] Error loading hash cache, regenerating. Error: " + ex.Message)); _fileHashCache = new FileHashCache { AlgorithmVersion = 1 }; } } private void SaveChangesToCache() { try { List<string> list = _fileHashCache.Hashes.Keys.Except(_processedFilePaths).ToList(); foreach (string item in list) { _fileHashCache.Hashes.Remove(item); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Cache] Cleanup removed {list.Count} stale entries."); Plugin.Log.LogInfo((object)$"[Cache] Stats: {_cacheHits} hits, {_cacheMisses} misses."); } string contents = JsonConvert.SerializeObject((object)_fileHashCache, (Formatting)1); string text = _cachePath + ".tmp"; File.WriteAllText(text, contents); if (File.Exists(_cachePath)) { File.Replace(text, _cachePath, null); } else { File.Move(text, _cachePath); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Cache] Saved {_fileHashCache.Hashes.Count} hashes to cache at {_cachePath}"); } } catch (Exception ex) { Plugin.Log.LogError((object)("[Cache] Failed to save hash cache: " + ex.Message)); } } [IteratorStateMachine(typeof(<MonitorForAutoSkip>d__112))] private IEnumerator MonitorForAutoSkip() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <MonitorForAutoSkip>d__112(0) { <>4__this = this }; } private void AutoSkipToNext() { List<AudioClip> loadedAudioClips = LoadedAudioClips; if (loadedAudioClips != null && loadedAudioClips.Count <= 0) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Not enough songs to auto-skip"); } return; } int num; switch (CurrentLoopState) { case LoopState.One: num = CurrentSongIndex; break; case LoopState.All: num = GetNextSongIndex(CurrentSongIndex); break; default: if (!IsShuffleEnabled && CurrentSongIndex == LoadedAudioClips.Count - 1) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Reached end of playlist. Stopping."); } Netcode.Instance.SendStopPlayback(); return; } num = GetNextSongIndex(CurrentSongIndex); break; } string songHash = IndexToSongHash[num]; string name = ((Object)LoadedAudioClips[num]).name; Netcode.Instance.SendNextSong(songHash, name); } public void StartAutoSkipMonitoring() { if (_autoSkipCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_autoSkipCoroutine); } _autoSkipCoroutine = ((MonoBehaviour)this).StartCoroutine(MonitorForAutoSkip()); } public void StopAutoSkipMonitoring() { if (_autoSkipCoroutine != null) { ((MonoBehaviour)this).StopCoroutine(_autoSkipCoroutine); _autoSkipCoroutine = null; } } public void HandleNextSong(string songHash, string songName) { if (CurrentPlaybackState != PlaybackState.Playing) { return; } if (SongHashToIndex.TryGetValue(songHash, out var value)) { CurrentSongIndex = value; PlaySongAtIndex(value); } else { if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)"[AudioMaster] Song not available for next song, playing white noise"); } PersistentAudioSource.clip = SoundEffects.WhiteNoise; PersistentAudioSource.Play(); sPEAKerUI.Instance.ShowTrackName(songName); } UpdatePlaybackState(PlaybackState.Playing, CurrentSongIndex); if (PhotonNetwork.IsMasterClient && !IsYouTubeMode) { StartAutoSkipMonitoring(); } } private void PlaySongAtIndex(int index) { if (index >= 0 && index < LoadedAudioClips.Count && !((Object)(object)PersistentAudioSource == (Object)null)) { AudioClip val = LoadedAudioClips[index]; PersistentAudioSource.clip = val; PersistentAudioSource.Play(); CurrentMixtape = GetMixtapeForSongIndex(index); if (!string.IsNullOrEmpty(CurrentMixtape.Name)) { sPEAKerUI.Instance.ShowTrackName(CurrentMixtape.Name + " - " + ((Object)val).name); sPEAKerUI.Instance.ShowAuthorInfo(CurrentMixtape.Author); } else { sPEAKerUI.Instance.HideAuthorInfo(); sPEAKerUI.Instance.ShowTrackName(((Object)val).name); } sPEAKerUI.Instance.ShowDuration(val.length); } } public static Texture2D GetCurrentMixtapeIcon() { if (LoadedAudioClips.Count == 0) { return null; } return GetMixtapeForSongIndex(CurrentSongIndex)?.IconTexture; } private AudioType GetAudioTypeFromExtension(string filePath) { //IL_003c: 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) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) return (AudioType)(Path.GetExtension(filePath).ToLowerInvariant() switch { ".mp3" => 13, ".wav" => 20, ".ogg" => 14, _ => 0, }); } public static void PlayFX(AudioClip clip) { if (!((Object)(object)EffectsAudioSource == (Object)null) && !((Object)(object)clip == (Object)null)) { EffectsAudioSource.clip = clip; EffectsAudioSource.Play(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Playing sound effect: " + ((Object)clip).name)); } } } public void SetVolume(float newVolume) { float num = Mathf.Clamp(newVolume, 0f, 1f); if (num >= 1f && NetworkVolume < 1f) { PlayFX(SoundEffects.VolMax); } NetworkVolume = num; ApplyVolumeMultiplier(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[AudioMaster] Network volume set to " + NetworkVolume + " with local multiplier " + LocalVolumeMultiplier + " = final volume " + NetworkVolume * LocalVolumeMultiplier)); } } public void SetFollow(Transform? target) { _targetTransform = target; _targetCharacter = null; _isFollowing = (Object)(object)target != (Object)null; if (Plugin.DebugMode) { if (_isFollowing) { Plugin.Log.LogInfo((object)("[AudioMaster] Set to follow transform: " + ((Object)target).name)); } else { Plugin.Log.LogInfo((object)"[AudioMaster] Stopped following."); } } } public void SetFollow(Character? target) { _targetCharacter = target; LastHolder = target; _targetTransform = null; _isFollowing = (Object)(object)target != (Object)null; if (Plugin.DebugMode) { if (_isFollowing) { Plugin.Log.LogInfo((object)("[AudioMaster] Set to follow character: " + ((target != null) ? ((Object)target).name : null))); } else { Plugin.Log.LogInfo((object)"[AudioMaster] Stopped following."); } } } public static void SetYouTubeMode(bool enabled) { IsYouTubeMode = enabled; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] YouTube mode set to {enabled}"); } } public static void PauseLocalAudio() { if ((Object)(object)PersistentAudioSource != (Object)null && PersistentAudioSource.isPlaying) { PersistentAudioSource.Pause(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Local audio paused for YouTube mode"); } } } public static void ResumeLocalAudio() { if ((Object)(object)PersistentAudioSource != (Object)null && (Object)(object)PersistentAudioSource.clip != (Object)null) { PersistentAudioSource.UnPause(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Local audio resumed from YouTube mode"); } } } public static void ToggleShuffle() { if (PhotonNetwork.IsMasterClient) { IsShuffleEnabled = !IsShuffleEnabled; if (IsShuffleEnabled) { GenerateShuffledOrder(); } Netcode.Instance.SendShuffleStateChange(IsShuffleEnabled); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Shuffle toggled: {IsShuffleEnabled}"); } } } public void HandleNetworkShuffleStateChange(bool isEnabled) { IsShuffleEnabled = isEnabled; sPEAKerUI.Instance.ShowTrackName(IsShuffleEnabled ? "Shuffle is ON" : "Shuffle is OFF"); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Network updated shuffle state: {IsShuffleEnabled}"); } } public static void ToggleLoop() { CurrentLoopState = (LoopState)((int)(CurrentLoopState + 1) % 3); switch (CurrentLoopState) { case LoopState.None: sPEAKerUI.Instance.ShowTrackName("Loop is OFF"); break; case LoopState.All: sPEAKerUI.Instance.ShowTrackName("Looping ALL songs"); break; case LoopState.One: sPEAKerUI.Instance.ShowTrackName("Looping CURRENT song"); break; } if (PhotonNetwork.IsMasterClient) { Netcode.Instance.SendLoopStateChange((int)CurrentLoopState); } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Loop state toggled: {CurrentLoopState}"); } } public void HandleNetworkLoopStateChange(int loopState) { CurrentLoopState = (LoopState)loopState; if (!PhotonNetwork.IsMasterClient) { switch (CurrentLoopState) { case LoopState.None: sPEAKerUI.Instance.ShowTrackName("Loop is OFF"); break; case LoopState.All: sPEAKerUI.Instance.ShowTrackName("Looping ALL songs"); break; case LoopState.One: sPEAKerUI.Instance.ShowTrackName("Looping CURRENT song"); break; } } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Network updated loop state: {CurrentLoopState}"); } } private static void GenerateShuffledOrder() { ShuffledIndices.Clear(); for (int i = 0; i < LoadedAudioClips.Count; i++) { ShuffledIndices.Add(i); } for (int j = 0; j < ShuffledIndices.Count; j++) { int num = Random.Range(j, ShuffledIndices.Count); List<int> shuffledIndices = ShuffledIndices; int index = j; List<int> shuffledIndices2 = ShuffledIndices; int index2 = num; int value = ShuffledIndices[num]; int value2 = ShuffledIndices[j]; shuffledIndices[index] = value; shuffledIndices2[index2] = value2; } ShuffledPosition = 0; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Generated new shuffle order with {ShuffledIndices.Count} tracks"); } } public static int GetNextSongIndex(int currentIndex) { if (LoadedAudioClips.Count < 2) { return currentIndex; } if (!IsShuffleEnabled) { return (currentIndex + 1) % LoadedAudioClips.Count; } int num = ShuffledIndices.IndexOf(currentIndex); if (num >= 0) { ShuffledPosition = (num + 1) % ShuffledIndices.Count; } else { ShuffledPosition = (ShuffledPosition + 1) % ShuffledIndices.Count; } return ShuffledIndices[ShuffledPosition]; } public static int GetPreviousSongIndex(int currentIndex) { if (LoadedAudioClips.Count < 2) { return currentIndex; } if (!IsShuffleEnabled) { return (currentIndex - 1 + LoadedAudioClips.Count) % LoadedAudioClips.Count; } int num = ShuffledIndices.IndexOf(currentIndex); if (num >= 0) { ShuffledPosition = (num - 1 + ShuffledIndices.Count) % ShuffledIndices.Count; } else { ShuffledPosition = (ShuffledPosition - 1 + ShuffledIndices.Count) % ShuffledIndices.Count; } return ShuffledIndices[ShuffledPosition]; } public bool IsSpeakerActive() { if (_isFollowing && (Object)(object)_targetCharacter != (Object)null) { return true; } return false; } public static void SetLocalVolumeMultiplier(float volume) { LocalVolumeMultiplier = Mathf.Clamp01(volume); ApplyVolumeMultiplier(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[AudioMaster] Local volume multiplier set to {LocalVolumeMultiplier}"); } } private static void ApplyVolumeMultiplier() { if ((Object)(object)PersistentAudioSource != (Object)null) { PersistentAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; } if ((Object)(object)YouTubeAudioSource != (Object)null) { YouTubeAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; } } private void LateUpdate() { //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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) if (_isFollowing) { Vector3 val = Vector3.zero; if ((Object)(object)_targetTransform != (Object)null) { val = _targetTransform.position; } else if ((Object)(object)_targetCharacter != (Object)null) { val = _targetCharacter.Center; } if ((Object)(object)PersistentAudioSource != (Object)null && ((Component)PersistentAudioSource).transform.position != val) { ((Component)PersistentAudioSource).transform.position = val; } if ((Object)(object)EffectsAudioSource != (Object)null && ((Component)EffectsAudioSource).transform.position != val) { ((Component)EffectsAudioSource).transform.position = val; } if ((Object)(object)YouTubeAudioSource != (Object)null && ((Component)YouTubeAudioSource).transform.position != val) { ((Component)YouTubeAudioSource).transform.position = val; } } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "Airport") { StopAllPlayback(); sPEAKerUI.Instance.HideDuration(); sPEAKerUI.Instance.HideAuthorInfo(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] Airport scene detected - stopped audio playback"); } } } private void OnDestroy() { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[AudioMaster] OnDestroy called."); } SceneManager.sceneLoaded -= OnSceneLoaded; if ((Object)(object)_instance == (Object)(object)this) { IsInitialized = false; IsLoading = false; } } private void OnGUI() { if (!((Object)(object)GUIManager.instance != (Object)null)) { return; } if (GUIManager.instance.pauseMenu.activeSelf) { if (CurrentPlaybackState != 0) { sPEAKerUI.Instance.HideDuration(); sPEAKerUI.Instance.HideAuthorInfo(); } } else if (CurrentPlaybackState != 0 && (Object)(object)ActiveSource != (Object)null && (Object)(object)ActiveSource.clip != (Object)null) { sPEAKerUI.Instance.ShowDuration(ActiveSource.clip.length); MixtapeInfo mixtapeForSongIndex = GetMixtapeForSongIndex(CurrentSongIndex); if (!string.IsNullOrEmpty(mixtapeForSongIndex.Name)) { sPEAKerUI.Instance.ShowAuthorInfo(mixtapeForSongIndex.Author); } else { sPEAKerUI.Instance.HideAuthorInfo(); } } } public void StopAllPlayback() { ResetPlaybackState(); if ((Object)(object)PersistentAudioSource != (Object)null && PersistentAudioSource.isPlaying) { PersistentAudioSource.Stop(); } if ((Object)(object)YouTubeAudioSource != (Object)null && YouTubeAudioSource.isPlaying) { YouTubeAudioSource.Stop(); } } } public class FileHashCache { public const int CurrentVersion = 1; public int Version { get; set; } = 1; public int AlgorithmVersion { get; set; } public Dictionary<string, CacheEntry> Hashes { get; set; } = new Dictionary<string, CacheEntry>(); } public class CacheEntry { [JsonProperty("hash")] public string Hash { get; set; } [JsonProperty("lastModified")] public DateTime LastModified { get; set; } [JsonProperty("size")] public long Size { get; set; } } [Serializable] public class MixtapeMetadata { public string name; public string author; } public class MixtapeInfo { public string Name; public string Author; public string TotalLength; public int FirstSongIndex; public int LastSongIndex; public Texture2D IconTexture; public int SongCount; public string Path; public bool ContainsSongIndex(int index) { return index >= FirstSongIndex && index <= LastSongIndex; } } public enum PlaybackState { Stopped, Playing, Paused } public enum LoopState { None, All, One } public static class SoundEffects { public static AudioClip Paired { get; private set; } public static AudioClip Pairing { get; private set; } public static AudioClip Party { get; private set; } public static AudioClip TurnOn { get; private set; } public static AudioClip VolMax { get; private set; } public static AudioClip WhiteNoise { get; private set; } public static void SetClip(string filename, AudioClip clip) { switch (filename.ToLower().Replace(" ", "")) { case "connected": Paired = clip; break; case "connecting": Pairing = clip; break; case "party": Party = clip; break; case "turnon": TurnOn = clip; break; case "volmax": VolMax = clip; break; case "whitenoise": WhiteNoise = clip; break; } } } public static class TaskExtensions { public static TaskAwaiter GetAwaiter(this AsyncOperation asyncOp) { TaskCompletionSource<object> tcs = new TaskCompletionSource<object>(); asyncOp.completed += delegate { tcs.TrySetResult(null); }; return ((Task)tcs.Task).GetAwaiter(); } } public class Controller : MonoBehaviour { [CompilerGenerated] private sealed class <DelayedUIUpdate>d__49 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Controller <>4__this; private PlaybackState <currentState>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedUIUpdate>d__49(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = null; <>1__state = 1; return true; case 1: <>1__state = -1; if (AudioMaster.IsYouTubeMode) { <currentState>5__1 = YouTubePlayer.Instance.CurrentPlaybackState; } else { <currentState>5__1 = AudioMaster.CurrentPlaybackState; } <>4__this.UpdateMainPrompt(<currentState>5__1); <>4__this._currentSongIndex = AudioMaster.CurrentSongIndex; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Controller] Delayed UI update complete. Final state: {<>4__this._playbackState}"); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <InitializeWhenReady>d__17 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Controller <>4__this; private int <waitCount>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InitializeWhenReady>d__17(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Waiting for AudioMaster to initialize..."); } <waitCount>5__1 = 0; break; case 1: <>1__state = -1; <waitCount>5__1++; break; } if (!AudioMaster.IsInitialized && <waitCount>5__1 < 50) { <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; } if (!AudioMaster.IsInitialized) { Plugin.Log.LogError((object)"[Controller] AudioMaster failed to initialize after 5 seconds!"); return false; } Item item = <>4__this.item; item.OnScrolled = (Action<float>)Delegate.Combine(item.OnScrolled, new Action<float>(<>4__this.HandleScroll)); <>4__this._currentAudioClips = AudioMaster.LoadedAudioClips.ToArray(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Loaded " + <>4__this._currentAudioClips.Length + " audio clips")); } AudioMaster.Instance.SetFollow(((Component)<>4__this).transform); <>4__this.SyncWithPersistentAudio(); <>4__this.droneBehaviour.CreateSearchRadiusIndicator(); <>4__this._isInitialized = true; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <TurnOnSequence>d__24 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Controller <>4__this; private float <waitTime>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <TurnOnSequence>d__24(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>4__this._isTurningOn = true; <>4__this.item.UIData.mainInteractPrompt = "..."; Netcode.Instance.SendPlayTurnOn(); <waitTime>5__1 = (((Object)(object)SoundEffects.TurnOn != (Object)null) ? SoundEffects.TurnOn.length : 0.5f); <>2__current = (object)new WaitForSeconds(<waitTime>5__1); <>1__state = 1; return true; case 1: <>1__state = -1; <>4__this._isTurnedOn = true; <>4__this._isTurningOn = false; if (Plugin.TurnOnAndPlay) { <>4__this._playbackState = PlaybackState.Playing; <>4__this.item.UIData.mainInteractPrompt = "PAUSE"; <>4__this.StartPlayback(); } else { <>4__this._playbackState = PlaybackState.Paused; <>4__this.item.UIData.mainInteractPrompt = "PLAY"; } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public Item item; private bool _isInitialized; private bool _isTurningOn; private bool _isTurnedOn; private bool _wasUsingPrimary; private bool _wasUsingSecondary; private int _currentSongIndex; private PlaybackState _playbackState = PlaybackState.Stopped; private AudioClip?[] _currentAudioClips; private const float VolumeStep = 0.05f; private bool _wasPocketed; public bool _audioIsFollowingCharacter; private Character? _pocketedByCharacter; private int _followTargetPhotonViewID = -1; public DroneBehaviour droneBehaviour; private void Awake() { droneBehaviour = ((Component)this).GetComponent<DroneBehaviour>(); if ((Object)(object)droneBehaviour == (Object)null) { droneBehaviour = ((Component)this).gameObject.AddComponent<DroneBehaviour>(); } } private void Start() { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Start called for " + ((Object)((Component)this).gameObject).name)); } item = ((Component)this).GetComponent<Item>(); if ((Object)(object)item == (Object)null) { Plugin.Log.LogError((object)("[Controller] No Item component found on " + ((Object)((Component)this).gameObject).name)); return; } droneBehaviour.Setup(this); AudioMaster.EnsureInitialized(); Netcode.EnsureInitialized(); ((MonoBehaviour)this).StartCoroutine(InitializeWhenReady()); } [IteratorStateMachine(typeof(<InitializeWhenReady>d__17))] private IEnumerator InitializeWhenReady() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitializeWhenReady>d__17(0) { <>4__this = this }; } private void Update() { if (!_isInitialized) { return; } if (AudioMaster.IsYouTubeMode) { PlaybackState currentPlaybackState = YouTubePlayer.Instance.CurrentPlaybackState; YouTubePlayer.LoadingState currentLoadingState = YouTubePlayer.Instance.CurrentLoadingState; if (_playbackState != currentPlaybackState) { UpdateMainPrompt(currentPlaybackState); } else if (_playbackState == PlaybackState.Stopped) { string text = ((currentLoadingState == YouTubePlayer.LoadingState.Loading) ? "BUFFERING" : "START RADIO"); if (item.UIData.mainInteractPrompt != text) { item.UIData.mainInteractPrompt = text; } } } bool isUsingPrimary = item.isUsingPrimary; if (isUsingPrimary && !_wasUsingPrimary) { PrimaryUse(); } _wasUsingPrimary = isUsingPrimary; bool isUsingSecondary = item.isUsingSecondary; if (isUsingSecondary && !_wasUsingSecondary) { HandleSecondaryUse(); } _wasUsingSecondary = isUsingSecondary; UpdateSecondaryPrompt(); if (!Plugin.ytModuleAvailable || !_isTurnedOn || !((Object)(object)item.lastHolderCharacter == (Object)(object)Character.localCharacter) || !Input.GetKeyDown((KeyCode)121)) { return; } if (!Input.GetKey((KeyCode)306) && !Input.GetKey((KeyCode)305)) { if (!AudioMaster.IsYouTubeMode) { Netcode.Instance.SendEnterYouTubeMode(); } YouTubeUI.Instance.Show(); } if ((Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305)) && AudioMaster.IsYouTubeMode) { Netcode.Instance.SendExitYouTubeMode(); if (PhotonNetwork.IsMasterClient) { AudioMaster.Instance.StartAutoSkipMonitoring(); } } } private void HandleSecondaryUse() { if (_isTurningOn) { return; } bool flag = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); bool flag2 = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); bool flag3 = Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307); if (AudioMaster.IsYouTubeMode) { if (flag2) { Netcode.Instance.SendYouTubePreviousRequest(); } else if (flag3) { ToggleYouTubeLoop(); } else { Netcode.Instance.SendYouTubeSkipRequest(); } } else if (flag) { ToggleShuffle(); } else if (flag2) { PreviousSong(); } else if (flag3) { ToggleLoop(); } else { NextSong(); } } public void UpdateMainPrompt(PlaybackState newState) { _playbackState = newState; _isTurnedOn = newState != PlaybackState.Stopped; if (AudioMaster.IsYouTubeMode && newState == PlaybackState.Stopped) { YouTubePlayer.LoadingState currentLoadingState = YouTubePlayer.Instance.CurrentLoadingState; if (currentLoadingState == YouTubePlayer.LoadingState.Loading) { item.UIData.mainInteractPrompt = "BUFFERING"; } else { item.UIData.mainInteractPrompt = "START RADIO"; } } else { switch (newState) { case PlaybackState.Playing: item.UIData.mainInteractPrompt = "PAUSE"; break; case PlaybackState.Paused: item.UIData.mainInteractPrompt = "PLAY"; break; case PlaybackState.Stopped: item.UIData.mainInteractPrompt = "TURN ON"; break; } } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Main prompt updated to: " + item.UIData.mainInteractPrompt)); } } private void UpdateSecondaryPrompt() { bool flag = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); bool flag2 = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); bool flag3 = Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307); string text = "NEXT"; if (AudioMaster.IsYouTubeMode) { if (flag2) { text = "PREVIOUS"; } else if (flag3) { text = "LOOP"; } } else if (flag) { text = "SHUFFLE"; } else if (flag2) { text = "PREVIOUS"; } else if (flag3) { text = "LOOP"; } if (item.UIData.secondaryInteractPrompt != text) { item.UIData.secondaryInteractPrompt = text; } } private void PrimaryUse() { if (_isTurningOn) { return; } if (AudioMaster.IsYouTubeMode && item.UIData.mainInteractPrompt == "START RADIO") { YouTubeUI.Instance.Show(); return; } if (AudioMaster.IsYouTubeMode) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Primary pressed in YouTube Mode. Requesting Toggle Pause."); } Netcode.Instance.SendYouTubeTogglePauseRequest(); return; } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Controller] Primary pressed. Current state: {_playbackState}"); } switch (_playbackState) { case PlaybackState.Stopped: if (!_isTurnedOn) { ((MonoBehaviour)this).StartCoroutine(TurnOnSequence()); break; } item.UIData.mainInteractPrompt = "PAUSE"; StartPlayback(); break; case PlaybackState.Playing: item.UIData.mainInteractPrompt = "PLAY"; PausePlayback(); break; case PlaybackState.Paused: item.UIData.mainInteractPrompt = "PAUSE"; ResumePlayback(); break; } } private void HandleScroll(float scrollAmount) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Invalid comparison between Unknown and I4 if (!((Object)(object)AudioMaster.PersistentAudioSource == (Object)null) && PhotonNetwork.IsMasterClient) { InputScheme currentUsedInputScheme = InputHandler.GetCurrentUsedInputScheme(); float num = (((int)currentUsedInputScheme == 1) ? 0.0050000004f : 0.05f); float networkVolume = AudioMaster.NetworkVolume; float num2 = networkVolume; if (scrollAmount > 0f) { num2 += num; } else if (scrollAmount < 0f) { num2 -= num; } num2 = Mathf.Clamp(num2, 0f, 1f); Netcode.Instance.SendVolumeChange(num2); } } [IteratorStateMachine(typeof(<TurnOnSequence>d__24))] private IEnumerator TurnOnSequence() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <TurnOnSequence>d__24(0) { <>4__this = this }; } private void ToggleShuffle() { if (!_isTurningOn) { if (PhotonNetwork.IsMasterClient) { AudioMaster.ToggleShuffle(); } else { Netcode.Instance.SendRequestShuffleToggle(); } } } private void ToggleLoop() { if (!_isTurningOn) { if (PhotonNetwork.IsMasterClient) { AudioMaster.ToggleLoop(); } else { Netcode.Instance.SendRequestLoopToggle(); } } } private void ToggleYouTubeLoop() { YouTubePlayer.YouTubeLoopState = ((YouTubePlayer.YouTubeLoopState == LoopState.None) ? LoopState.One : LoopState.None); if (PhotonNetwork.IsMasterClient) { Netcode.Instance.SendYouTubeLoopStateChange((int)YouTubePlayer.YouTubeLoopState); } string trackName = ((YouTubePlayer.YouTubeLoopState == LoopState.One) ? "Looping CURRENT song" : "Loop is OFF"); sPEAKerUI.Instance.ShowTrackName(trackName); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Controller] YouTube loop toggled: {YouTubePlayer.YouTubeLoopState}"); } } public void ToggleFloatMode() { if (droneBehaviour.IsFloating) { if ((Object)(object)droneBehaviour.FollowTarget == (Object)null || (Object)(object)droneBehaviour.FollowTarget == (Object)(object)Character.localCharacter) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Toggling float mode OFF."); } Netcode.Instance.SendFloatModeToggle(isFloating: false, -1); } else if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)("[Controller] Request to disable float mode denied. Only the target player (" + ((Object)droneBehaviour.FollowTarget).name + ") can do this.")); } } else { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Toggling float mode ON. Hover and search engaged."); } Netcode.Instance.SendFloatModeToggle(isFloating: true, -1); } } public void RecallToPlayer(Character recallingPlayer) { if ((Object)(object)recallingPlayer == (Object)null) { if (Plugin.DebugMode) { Plugin.Log.LogWarning((object)"[Controller] RecallToPlayer called with a null player."); } return; } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Recalling to player: " + ((Object)recallingPlayer).name)); } int viewID = ((MonoBehaviourPun)recallingPlayer).photonView.ViewID; Netcode.Instance.SendFloatModeToggle(isFloating: true, viewID, isRecall: true); } public void StartDance() { droneBehaviour.StartDance(); } private void StartPlayback() { if (_currentAudioClips == null || _currentAudioClips.Length == 0 || (Object)(object)AudioMaster.PersistentAudioSource == (Object)null) { Plugin.Log.LogWarning((object)"[Controller] Cannot start playback: no audio clips available"); return; } if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Starting playback..."); } if (PhotonNetwork.IsMasterClient) { Netcode.Instance.BroadcastStartPlayback(_currentSongIndex); } else { Netcode.Instance.SendRequestStartPlayback(_currentSongIndex); } } private void PausePlayback() { if (_playbackState == PlaybackState.Playing) { float num = (((Object)(object)AudioMaster.PersistentAudioSource != (Object)null) ? AudioMaster.PersistentAudioSource.time : 0f); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Controller] Pausing at {num}s"); } if (PhotonNetwork.IsMasterClient) { Netcode.Instance.BroadcastPausePlayback(num); } else { Netcode.Instance.SendRequestPausePlayback(num); } } } private void ResumePlayback() { if (_playbackState == PlaybackState.Paused) { float currentPausedTime = AudioMaster.CurrentPausedTime; if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)$"[Controller] Resuming from {currentPausedTime}s"); } if (PhotonNetwork.IsMasterClient) { Netcode.Instance.BroadcastResumePlayback(currentPausedTime); } else { Netcode.Instance.SendRequestResumePlayback(currentPausedTime); } } } private void NextSong() { if (PhotonNetwork.IsMasterClient) { AudioClip[] currentAudioClips = _currentAudioClips; if (currentAudioClips == null || currentAudioClips.Length <= 1) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Not enough songs to switch"); } return; } _currentSongIndex = AudioMaster.GetNextSongIndex(_currentSongIndex); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Switching to song " + (_currentSongIndex + 1))); } string songHash = AudioMaster.IndexToSongHash[_currentSongIndex]; string name = ((Object)_currentAudioClips[_currentSongIndex]).name; Netcode.Instance.SendNextSong(songHash, name); } else { Netcode.Instance.SendRequestNextSong(); } } private void PreviousSong() { if (PhotonNetwork.IsMasterClient) { AudioClip[] currentAudioClips = _currentAudioClips; if (currentAudioClips == null || currentAudioClips.Length <= 1) { if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)"[Controller] Not enough songs to switch"); } return; } _currentSongIndex = AudioMaster.GetPreviousSongIndex(_currentSongIndex); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Switching to previous song " + (_currentSongIndex + 1))); } string songHash = AudioMaster.IndexToSongHash[_currentSongIndex]; string name = ((Object)_currentAudioClips[_currentSongIndex]).name; Netcode.Instance.SendNextSong(songHash, name); } else { Netcode.Instance.SendRequestPreviousSong(); } } private void PlayCurrentSound() { if (_currentSongIndex < 0 || _currentSongIndex >= _currentAudioClips.Length) { Plugin.Log.LogError((object)("[Controller] Invalid sound index: " + _currentSongIndex)); return; } if ((Object)(object)AudioMaster.PersistentAudioSource == (Object)null) { Plugin.Log.LogError((object)"[Controller] Persistent audio source is null!"); return; } AudioClip val = _currentAudioClips[_currentSongIndex]; if ((Object)(object)val == (Object)null) { Plugin.Log.LogError((object)("[Controller] Clip at index " + _currentSongIndex + " is null!")); return; } AudioMaster.PersistentAudioSource.clip = val; AudioMaster.PersistentAudioSource.Play(); if (Plugin.DebugMode) { Plugin.Log.LogInfo((object)("[Controller] Playing sound " + (_currentSongIndex + 1) + ": " + ((Object)val).name)); } MixtapeInfo mixtapeForSongIndex = AudioMaster.GetMixtapeForSongIndex(_currentSongIndex); if (!string.IsNullOrEmpty(mixtapeForSongIndex.Name)) { sPEAKerUI.Instance.ShowTrackName(mixtapeForSongIndex.Name + " - " + ((Object)val).name); sPEAKerUI.Instance.ShowAuthorInfo(mixtapeForSongIndex.Author); } else { sPEAKerUI.Instance.ShowTrackName(((Object)val).name); sPEAKerUI.Instance.HideAuthorInfo(); } sPEAKerUI.Instance.ShowDuration(val.length); } public