Decompiled source of sPEAKer v2.3.3
plugins/onlystar.sPEAKer.dll
Decompiled 3 weeks 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.Net; 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.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using PEAKLib.Core; using POpusCodec.Enums; using Peak.UI; using Photon.Pun; using Photon.Realtime; using Photon.Voice; using Photon.Voice.Unity; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityEngine.Video; 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("2.3.3.0")] [assembly: AssemblyInformationalVersion("2.3.3+bd0fee052938777c89dc6665ec3819e87208e300")] [assembly: AssemblyProduct("onlystar.sPEAKer")] [assembly: AssemblyTitle("sPEAKer")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.3.3.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", "2.3.3")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <BeachSpawn>d__177 : 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 <BeachSpawn>d__177(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(5f); <>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); if (MilestonesEnabled && AudioMaster.IsInitialized) { MilestoneTracker.Instance.CheckSoundtrack2MyLife(); } 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 <DelayedManualMode>d__172 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Controller controller; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedManualMode>d__172(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 (controller.droneBehaviour.IsFloating && controller.droneBehaviour.CurrentBehaviourMode != DroneBehaviourMode.Manual && controller.droneBehaviour.SwitchBehaviour(DroneBehaviourMode.Manual)) { sPEAKerUI.Instance.ShowToast("Drone Mode: Manual"); } 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 <LobbySpawn>d__176 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private Vector3 <seatPosition>5__1; private Quaternion <seatRotation>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LobbySpawn>d__176(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>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_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_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: 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(6f); <>1__state = 1; return true; case 1: <>1__state = -1; <seatPosition>5__1 = new Vector3(-6.89f, 1.7f, 87.27f); <seatRotation>5__2 = Quaternion.Euler(0f, 90f, 0f); PhotonNetwork.InstantiateItemRoom(((Object)_prefab).name, <seatPosition>5__1, <seatRotation>5__2); Log("[Plugin] Spawned sPEAKer at Airport seat."); if (MilestonesEnabled && AudioMaster.IsInitialized) { MilestoneTracker.Instance.CheckSoundtrack2MyLife(); } 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 string PluginFolder; public static TMP_FontAsset PeakFont; public static bool MilestonesEnabled; public static bool LevelingAvailable; private ConfigEntry<string> externalAudioFolderPathConfig = null; private ConfigEntry<bool> loadDemoSongConfig = null; private ConfigEntry<bool> extendedFormatSupportConfig = null; private ConfigEntry<bool> startupLoadingIndicatorConfig = null; private ConfigEntry<bool> fastLoadConfig = null; internal ConfigEntry<bool> showTrackDurationConfig = null; internal ConfigEntry<bool> displayAuthorInfoConfig = null; private ConfigEntry<bool> shuffleOnStartConfig = null; private ConfigEntry<bool> turnOnAndPlayConfig = null; private ConfigEntry<bool> soundEffectsEnabledConfig = null; internal ConfigEntry<bool> useSpatialAudioConfig = null; private ConfigEntry<bool> renderVisualizersConfig = null; internal ConfigEntry<string> queueUIThemeConfig = null; private ConfigEntry<bool> spawnAtAirportConfig = null; private ConfigEntry<bool> spawnAtGameStartConfig = null; private ConfigEntry<bool> spawnWithF4Config = null; private ConfigEntry<bool> recallEnabledConfig = null; private ConfigEntry<KeyCode> droneToggleKeyConfig = null; private ConfigEntry<KeyCode> recallCommandKeyConfig = null; private ConfigEntry<KeyCode> danceKeyConfig = null; private ConfigEntry<KeyCode> spawnKeyConfig = null; private ConfigEntry<KeyCode> queueUIKeyConfig = null; private ConfigEntry<KeyCode> ytKeyConfig = null; private ConfigEntry<KeyCode> hostOnlyKeyConfig = null; internal ConfigEntry<bool> autoPlayConfig = null; internal ConfigEntry<int> maxCachedSongsConfig = null; internal ConfigEntry<int> predownloadCountConfig = null; private ConfigEntry<bool> debugModeConfig = null; internal const bool Strange = true; public static Plugin Instance { get; private set; } = null; internal static ManualLogSource LogObj { get; private set; } = null; public static ushort ItemID { get; private set; } = 50592; public static string DefaultMusicPath { get; private set; } = ""; public static string ExternalAudioFolderPath { get; set; } = ""; public static bool LoadDemoSong { get; set; } public static bool ExtendedFormatSupport { get; set; } public static bool StartupLoadingIndicator { get; set; } public static bool FastLoad { get; set; } public static bool ShowTrackDuration { get; set; } public static bool DisplayAuthorInfo { get; set; } public static bool ShuffleOnStart { get; set; } public static bool TurnOnAndPlay { get; set; } public static bool SoundEffectsEnabled { get; set; } public static bool UseSpatialAudio { get; set; } public static bool RenderVisualizers { get; set; } public static string QueueUITheme { get; set; } = "default"; private static bool SpawnAtAirport { get; set; } private static bool SpawnAtGameStart { get; set; } private static bool SpawnWithF4 { get; set; } private static bool RecallEnabled { get; set; } public static KeyCode DroneToggleKey { get; set; } public static KeyCode RecallCommandKey { get; set; } public static KeyCode DanceKey { get; set; } private static KeyCode SpawnKey { get; set; } public static KeyCode QueueUIKey { get; set; } public static KeyCode YouTubeKey { get; set; } private static KeyCode HostOnlyKey { get; set; } public static bool AutoPlay { get; set; } = true; public static int MaxCachedSongs { get; set; } = 30; public static int PredownloadCount { get; set; } = 5; private static bool DebugMode { get; set; } public static event Action OnFontLoaded; private void Awake() { Instance = this; LogObj = ((BaseUnityPlugin)this).Logger; SetUserConfigs(); Localization.Apply(); ApplyPatches(); Log("[Plugin] Plugin awakening..."); ModDefinition.GetOrCreate(((BaseUnityPlugin)this).Info.Metadata); PluginFolder = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location); LoadFont(); AudioMaster.EnsureInitialized(); Netcode.EnsureInitialized(); TheObserver.EnsureInitialized(); MilestonesEnabled = true; MilestoneTracker.EnsureInitialized(); MilestoneUI.EnsureInitialized(); LevelingAvailable = Chainloader.PluginInfos.ContainsKey("com.atomic.leveling"); if (!LevelingAvailable) { Log("Leveling is not installed. Milestones will work, but XP will be pending.", "warning"); } if (ytModuleCompatibility.enabled) { ytModuleCompatibility.EnsureYouTubePlayerInitialized(); ytModuleCompatibility.EnsureYouTubeQueueInitialized(); RateLimitUI.EnsureInitialized(); } try { Log("[Plugin] Attempting to load bundle..."); BundleLoader.LoadBundleWithName((BaseUnityPlugin)(object)this, "ps.peakbundle", (Action<PeakBundle>)InitSpeaker); Log("[Plugin] Bundle load request submitted"); } catch (Exception ex) { Log("[Plugin] Failed to load bundle: " + ex.Message, "error"); } } private void InitSpeaker(PeakBundle bundle) { Log("[Plugin] Initializing sPEAKer with bundle..."); Bundle = bundle; _prefab = Bundle.LoadAsset<GameObject>("assets/sPEAKer.prefab"); Log("[Plugin] Successfully loaded sPEAKer prefab."); SetupMaterials(_prefab); SceneManager.sceneLoaded += OnSceneLoaded; _prefab.AddComponent<Controller>(); Bundle.Mod.RegisterContent(); Log("[Plugin] Plugin sPEAKer is loaded!"); } private static void SetupMaterials(GameObject gameObject) { Shader val = Shader.Find("W/Character"); if ((Object)(object)val == (Object)null) { Log("[Plugin] W/Character shader not found, falling back to W/Peak_Standard", "warning"); val = Shader.Find("W/Peak_Standard"); } if ((Object)(object)val == (Object)null) { Log("[Plugin] Neither W/Character nor W/Peak_Standard shader found!", "error"); return; } Renderer[] componentsInChildren = gameObject.GetComponentsInChildren<Renderer>(); Renderer[] array = componentsInChildren; foreach (Renderer val2 in array) { Material[] materials = val2.materials; Material[] array2 = materials; foreach (Material val3 in array2) { if (((Object)val3.shader).name == "Universal Render Pipeline/Lit" || ((Object)val3.shader).name == "Standard") { Texture val4 = val3.GetTexture("_BaseMap") ?? val3.GetTexture("_MainTex"); val3.shader = val; if ((Object)(object)val4 != (Object)null) { val3.SetTexture("_BaseTexture", val4); } Log("[Plugin] Applied W/Character shader to material: " + ((Object)val3).name); } } val2.materials = materials; } } private static void LoadFont() { if ((Object)(object)PeakFont != (Object)null) { return; } TMP_FontAsset[] array = Resources.FindObjectsOfTypeAll<TMP_FontAsset>(); TMP_FontAsset[] array2 = array; foreach (TMP_FontAsset val in array2) { if (((Object)val).name.Contains("DarumaDropOne") || ((Object)val).name.Contains("Daruma")) { PeakFont = val; Log("[Plugin] Found game font: " + ((Object)val).name); Plugin.OnFontLoaded?.Invoke(); Plugin.OnFontLoaded = null; return; } } Log("[Plugin] Daruma font not found", "warning"); } private void SetUserConfigs() { //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04e4: 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; fastLoadConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Audio Loading", "FastLoad", true, "Makes sPEAKer load songs during gameplay instead of at game start."); FastLoad = fastLoadConfig.Value; showTrackDurationConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "ShowTrackDuration", true, "Shows song's length and current time."); ShowTrackDuration = showTrackDurationConfig.Value; displayAuthorInfoConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "DisplayAuthorInfo", true, "Displays mixtape author under the stamina bar."); DisplayAuthorInfo = displayAuthorInfoConfig.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; soundEffectsEnabledConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "SoundEffectsEnabled", true, "Enables JBL sound effects."); SoundEffectsEnabled = soundEffectsEnabledConfig.Value; useSpatialAudioConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "UseSpatialAudio", true, "Enables proximity-based audio. If disabled, playback will sound globally instead of coming from the sPEAKer."); UseSpatialAudio = useSpatialAudioConfig.Value; renderVisualizersConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Playback", "RenderVisualizers", true, "Enables rendering audio visualizers on airport screens."); RenderVisualizers = renderVisualizersConfig.Value; queueUIThemeConfig = ((BaseUnityPlugin)this).Config.Bind<string>("Playback", "QueueUITheme", "default", "Theme for the Queue UI. Options: default, royal_amethyst, deep_sea, pinky_promise, blood_moon, terminal, wood, cyberpunk, forest"); QueueUITheme = queueUIThemeConfig.Value; spawnAtAirportConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawning", "SpawnAtAirport", true, "Automatically spawn sPEAKer at the airport."); SpawnAtAirport = spawnAtAirportConfig.Value; spawnAtGameStartConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Spawning", "SpawnAtGameStart", true, "Automatically spawn 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 by pressing the spawn key (see Keybinds section).\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; 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; droneToggleKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "DroneToggle", (KeyCode)112, "Drone Mode toggle."); DroneToggleKey = droneToggleKeyConfig.Value; hostOnlyKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "HostOnlyKey", (KeyCode)104, "Toggles between streaming your voice or music."); HostOnlyKey = hostOnlyKeyConfig.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; spawnKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "SpawnKey", (KeyCode)285, "Spawn a sPEAKer if the corresponding spawning config is enabled."); SpawnKey = spawnKeyConfig.Value; queueUIKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "QueueUIKey", (KeyCode)117, "Opens Queue UI."); QueueUIKey = queueUIKeyConfig.Value; ytKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Keybinds", "YouTubeKey", (KeyCode)121, "Enter Youtube Radio mode. Hold Ctrl and this key to exit."); YouTubeKey = ytKeyConfig.Value; autoPlayConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("YouTube Radio", "AutoPlay", true, "When enabled, reaching the end of the queue will automatically fetch 10 more recommendations and continue playing."); AutoPlay = autoPlayConfig.Value; maxCachedSongsConfig = ((BaseUnityPlugin)this).Config.Bind<int>("YouTube Radio", "MaxCachedSongs", 30, "Maximum number of downloaded songs to keep cached. Older songs are deleted when this limit is exceeded. Range: 10-100."); MaxCachedSongs = Mathf.Clamp(maxCachedSongsConfig.Value, 10, 100); predownloadCountConfig = ((BaseUnityPlugin)this).Config.Bind<int>("YouTube Radio", "PredownloadCount", 5, "Number of upcoming tracks to predownload. Higher values use more disk space but reduce buffering. Range: 1-20."); PredownloadCount = Mathf.Clamp(predownloadCountConfig.Value, 1, 20); debugModeConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("zZzZz", "Debug", true, "Enable debug mode AKA verbose logging."); DebugMode = debugModeConfig.Value; } private void Update() { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0089: 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_0225: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Invalid comparison between Unknown and I4 //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Invalid comparison between Unknown and I4 //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) if (Input.GetKeyDown(SpawnKey) && PhotonNetwork.IsMasterClient) { if (!SpawnWithF4) { return; } if (TheObserver.Instance.DoessPEAKerExist()) { if (RecallEnabled && TheObserver.Instance.CanRecallsPEAKer()) { Controller controllerInstance = TheObserver.Instance.GetControllerInstance(); controllerInstance.KillYourself(); Instance.SpawnAtHand(); } } else { Instance.SpawnAtHand(); } } if (Input.GetKeyDown(DanceKey)) { Controller controllerInstance2 = TheObserver.Instance.GetControllerInstance(); if ((Object)(object)controllerInstance2 != (Object)null && (int)controllerInstance2.item.itemState == 0) { Netcode.Instance.SendRequestDanceEmote(); } else { Log("[Plugin] Could not find controller to initiate Dance.", "warning"); } } if (Input.GetKeyDown(DroneToggleKey)) { Controller controllerInstance3 = TheObserver.Instance.GetControllerInstance(); if ((Object)(object)controllerInstance3 != (Object)null && (int)controllerInstance3.item.itemState == 0) { bool key = Input.GetKey(RecallCommandKey); bool key2 = Input.GetKey((KeyCode)307); bool key3 = Input.GetKey((KeyCode)303); if (key2 && key3) { if (controllerInstance3.droneBehaviour.IsFloating) { controllerInstance3.CycleDroneBehaviour(); } else { controllerInstance3.ToggleFloatMode(); ((MonoBehaviour)this).StartCoroutine(DelayedManualMode(controllerInstance3)); } } else if (key) { controllerInstance3.RecallToPlayer(Character.localCharacter); } else if (controllerInstance3.droneBehaviour.IsFloating && controllerInstance3.droneBehaviour.CurrentBehaviourMode == DroneBehaviourMode.Manual) { controllerInstance3.droneBehaviour.SwitchBehaviour(DroneBehaviourMode.Orbiting); controllerInstance3.ToggleFloatMode(); sPEAKerUI.Instance.ShowToast("Drone Mode: OFF"); } else { controllerInstance3.ToggleFloatMode(); } } else { Log("[Plugin] Could not find controller to initiate Drone/Recall mode.", "warning"); } } if (Input.GetKeyDown(QueueUIKey)) { if (!TheObserver.Instance.DoessPEAKerExist()) { return; } QueueUI.Instance.ToggleQueue(); } if (Input.GetKeyDown(YouTubeKey)) { if (ytModuleCompatibility.enabled) { ytModuleCompatibility.HandleYouTubeKeyPress(); } else { sPEAKerUI.Instance.ShowToast("YouTube Radio is not available"); } } YouTubeUI instance = YouTubeUI.Instance; if ((Object)(object)instance != (Object)null && instance.IsVisible() && Input.GetKeyDown((KeyCode)32) && ytModuleCompatibility.enabled) { ytModuleCompatibility.HandleYouTubeSpaceKey(); } if (Input.GetKeyDown((KeyCode)256)) { RateLimitUI instance2 = RateLimitUI.Instance; if (instance2 != null) { ((MenuWindow)instance2).Open(); } } if (Input.GetKeyDown(HostOnlyKey)) { if (!HostOnly.Instance.IsActive) { HostOnly.Instance.EnableHostMode(); } else { HostOnly.Instance.DisableHostMode(); } } } [IteratorStateMachine(typeof(<DelayedManualMode>d__172))] private static IEnumerator DelayedManualMode(Controller controller) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedManualMode>d__172(0) { controller = controller }; } private void OnDestroy() { Log("[Plugin] Plugin destroying..."); SceneManager.sceneLoaded -= OnSceneLoaded; Instance = null; LogObj = null; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { Log("[Plugin] Scene loaded: " + ((Scene)(ref scene)).name); TheObserver.Instance.ResetState(); if (((Scene)(ref scene)).name == "Airport") { if ((Object)(object)PeakFont == (Object)null) { LoadFont(); } string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "data"); string airportMediaPath = Path.Combine(path, "airport"); AirportEditor.Initialize(airportMediaPath); QueueUI.Instance.PreWarmUI(); MilestoneUI.Instance.LoadCategoryIcons(); if (SpawnAtAirport) { Log("[Plugin] Spawning at airport."); ((MonoBehaviour)this).StartCoroutine(LobbySpawn()); } } if (((Scene)(ref scene)).name.StartsWith("Level_") && SpawnAtGameStart && PhotonNetwork.IsConnected && PhotonNetwork.IsMasterClient) { ((MonoBehaviour)this).StartCoroutine(BeachSpawn()); } } 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()); } [IteratorStateMachine(typeof(<LobbySpawn>d__176))] private static IEnumerator LobbySpawn() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LobbySpawn>d__176(0); } [IteratorStateMachine(typeof(<BeachSpawn>d__177))] private static IEnumerator BeachSpawn() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <BeachSpawn>d__177(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); } public static void Log(string message, string type = "info") { if (DebugMode) { switch (type) { case "info": LogObj.LogInfo((object)message); break; case "warning": LogObj.LogWarning((object)message); break; case "error": LogObj.LogError((object)message); break; default: LogObj.LogInfo((object)message); break; } } } } } namespace sPEAKer.Scripts { public class AudioMaster : MonoBehaviourPun { [CompilerGenerated] private sealed class <FadeOut>d__132 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public float playDuration; public float fadeDuration; public AudioMaster <>4__this; private float <startVolume>5__1; private float <elapsed>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FadeOut>d__132(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(playDuration); <>1__state = 1; return true; case 1: <>1__state = -1; <startVolume>5__1 = MixtapeAudioSource.volume; <elapsed>5__2 = 0f; break; case 2: <>1__state = -1; break; } if (<elapsed>5__2 < fadeDuration) { <elapsed>5__2 += Time.deltaTime; MixtapeAudioSource.volume = Mathf.Lerp(<startVolume>5__1, 0f, <elapsed>5__2 / fadeDuration); <>2__current = null; <>1__state = 2; return true; } MixtapeAudioSource.volume = <startVolume>5__1; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <MonitorForAutoSkip>d__126 : 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__126(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; Plugin.Log("[AudioMaster] Auto-skip monitor started."); <>2__current = (object)new WaitUntil((Func<bool>)(() => (Object)(object)MixtapeAudioSource != (Object)null && MixtapeAudioSource.isPlaying)); <>1__state = 1; return true; case 1: <>1__state = -1; break; case 2: <>1__state = -1; break; } if (CurrentPlaybackState == PlaybackState.Playing && MixtapeAudioSource.isPlaying) { if (IsYouTubeMode) { Plugin.Log("[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) { Plugin.Log("[AudioMaster] Song finished. Automatically playing next track."); <>4__this.AutoSkipToNext(); } Plugin.Log("[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 Transform _targetTransform; private Character _targetCharacter; 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 static AudioMaster _instance; private static int NonOggFiles; private Coroutine _autoSkipCoroutine; public static bool IsInitialized { get; private set; } private static bool IsLoading { get; set; } public static AudioSource MixtapeAudioSource { get; set; } = null; private static AudioSource EffectsAudioSource { get; set; } = null; public static AudioSource YouTubeAudioSource { get; private set; } = null; public static AudioSource ActiveSource { get { if (IsYouTubeMode) { return YouTubeAudioSource; } return MixtapeAudioSource; } } public static List<AudioClip> LoadedAudioClips { get; private set; } = new List<AudioClip>(); public static List<MixtapeInfo> AvailableMixtapes { get; set; } = new List<MixtapeInfo>(); 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 Dictionary<int, string> IndexToFilePath { 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; } public static bool IsYouTubeMode { get; private set; } public static float NetworkVolume { get; private set; } = 0.35f; public static float LocalVolumeMultiplier { get; set; } = 0.5f; public static LoopState CurrentLoopState { get; private set; } = LoopState.None; public static List<string> SongQueue { get; private set; } = new List<string>(); 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); Plugin.Log("[AudioMaster] Created new instance and marked as DontDestroyOnLoad."); } } return _instance; } } public static event Action<string> OnSongChanged; public static event Action OnQueueSynced; public static event Action<PlaybackActionType, AudioClip, float> OnPlaybackAction; public static event Action<PlaybackState> OnPlaybackStateChanged; public static event Action<LoopState> OnLoopStateChanged; public static event Action<bool> OnYouTubeModeChanged; public static void EnsureInitialized() { Plugin.Log("[AudioMaster] EnsureInitialized called."); AudioMaster instance = Instance; if (!IsInitialized && !IsLoading) { Plugin.Log("[AudioMaster] Not initialized, starting initialization..."); instance.InitializeAsync(); } } private void Awake() { Plugin.Log("[AudioMaster] Awake called."); if ((Object)(object)_instance != (Object)null && (Object)(object)_instance != (Object)(object)this) { Plugin.Log("[AudioMaster] Duplicate instance detected, destroying."); Object.Destroy((Object)(object)((Component)this).gameObject); return; } _instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); Plugin.Log("[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)MixtapeAudioSource == (Object)null) { MixtapeAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); MixtapeAudioSource.loop = false; MixtapeAudioSource.playOnAwake = false; MixtapeAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; if (Plugin.UseSpatialAudio) { MixtapeAudioSource.minDistance = 2f; MixtapeAudioSource.maxDistance = 50f; MixtapeAudioSource.spatialBlend = 1f; MixtapeAudioSource.dopplerLevel = 0.1f; MixtapeAudioSource.spatialize = true; MixtapeAudioSource.rolloffMode = (AudioRolloffMode)2; MixtapeAudioSource.SetCustomCurve((AudioSourceCurveType)0, val); } else { MixtapeAudioSource.spatialBlend = 0f; } Plugin.Log("[AudioMaster] Created persistent audio source."); } if ((Object)(object)EffectsAudioSource == (Object)null) { EffectsAudioSource = ((Component)this).gameObject.AddComponent<AudioSource>(); EffectsAudioSource.playOnAwake = false; EffectsAudioSource.loop = false; if (Plugin.UseSpatialAudio) { EffectsAudioSource.spatialBlend = 1f; EffectsAudioSource.volume = 1f; EffectsAudioSource.minDistance = 2f; EffectsAudioSource.maxDistance = 25f; EffectsAudioSource.rolloffMode = (AudioRolloffMode)0; } else { EffectsAudioSource.spatialBlend = 0f; EffectsAudioSource.volume = 1f; } Plugin.Log("[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; if (Plugin.UseSpatialAudio) { 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); } else { YouTubeAudioSource.spatialBlend = 0f; } Plugin.Log("[AudioMaster] Created YouTube audio source."); } } private async Task InitializeAsync() { IsLoading = true; Plugin.Log("[AudioMaster] Starting initialization... "); LoadOrCreateHashCache(); sPEAKerUI.Instance.ShowLoading("Initializing sPEAKer..."); await LoadFolderAudio(); sPEAKerUI.Instance.ShowLoading("Loading bundle audio..."); await LoadBundleAudio(); if (LoadedAudioClips.Count > 0) { SongQueue = IndexToSongHash.Values.ToList(); Plugin.Log($"[AudioMaster] Initial song queue populated with {SongQueue.Count} tracks."); if (Plugin.ShuffleOnStart) { Master_ShuffleQueue(broadcast: false); Plugin.Log("[AudioMaster] Shuffled on start."); } } sPEAKerUI.Instance.ShowLoadingComplete(); SaveChangesToCache(); IsInitialized = true; IsLoading = false; Plugin.Log("[AudioMaster] Initialization complete. Total clips loaded: " + LoadedAudioClips.Count + ", Mixtapes loaded: " + AvailableMixtapes.Count); if (Plugin.MilestonesEnabled) { if (LoadedAudioClips.Count > 1 && NonOggFiles == 0) { MilestoneTracker.Instance.ReportProgress("CrystalClear"); } MilestoneTracker.Instance.SetAbsoluteProgress("ElectricRelaxation", LoadedAudioClips.Count); MilestoneTracker.Instance.SetAbsoluteProgress("DontStop", AvailableMixtapes.Count); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "Airport") { StopAllPlayback(); sPEAKerUI.Instance.HideDuration(); sPEAKerUI.Instance.HideAuthorInfo(); Plugin.Log("[AudioMaster] Airport scene detected - stopped audio playback"); } } private void OnDestroy() { Plugin.Log("[AudioMaster] OnDestroy called."); SceneManager.sceneLoaded -= OnSceneLoaded; if ((Object)(object)_instance == (Object)(object)this) { IsInitialized = false; IsLoading = false; } } private async Task LoadBundleAudio() { Plugin.Log("[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("[AudioMaster] Bundle not available after 10 seconds - skipping bundle audio loading", "error"); return; } Plugin.Log("[AudioMaster] Bundle is now available, proceeding with loading..."); string[] allAssets = Plugin.Bundle.GetAllAssetNames(); Plugin.Log("[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[] laserFX = allAssets.Where((string asset) => asset.Contains("laser") && asset.EndsWith(".ogg")).ToArray(); string[] array2 = laserFX; foreach (string fx2 in array2) { AudioClip clip2 = Plugin.Bundle.LoadAsset<AudioClip>(fx2); string filename2 = Path.GetFileNameWithoutExtension(fx2); SoundEffects.SetClip(filename2, clip2); } 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; IndexToFilePath[index] = null; } else { Plugin.Log("[AudioMaster] Failed to load demo song audio data", "error"); } } } Plugin.Log("[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("[AudioMaster] Configured path '" + configuredPath + "' is invalid. Falling back to the default path.", "warning"); finalUserPath = Plugin.DefaultMusicPath; } } catch (Exception ex) { Exception e = ex; Plugin.Log("[AudioMaster] Error validating configured path '" + Plugin.ExternalAudioFolderPath + "'. Falling back to default. Error: " + e.Message, "error"); finalUserPath = Plugin.DefaultMusicPath; } try { if (!Directory.Exists(finalUserPath)) { Directory.CreateDirectory(finalUserPath); Plugin.Log("[AudioMaster] Created audio folder: " + finalUserPath); } foldersToProcess.Add(finalUserPath); } catch (Exception e2) { Plugin.Log("[AudioMaster] Failed to create or access the final user path '" + finalUserPath + "'. Error: " + e2.Message, "error"); } string pluginsPath = Paths.PluginPath; if (Directory.Exists(pluginsPath)) { Plugin.Log("[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); Plugin.Log("[AudioMaster] Found mixtape mod folder: " + subDir); } } } Plugin.Log("[AudioMaster] Total unique folders to process: " + foldersToProcess.Count); foreach (string folderPath in foldersToProcess) { await ProcessFolderForAudio(folderPath); } } private async Task ProcessAudioFolder(string folderPath) { Plugin.Log("[AudioMaster] Processing loose audio files in: " + folderPath); List<string> searchPatterns = new List<string> { "*.ogg" }; if (Plugin.ExtendedFormatSupport) { Plugin.Log("[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(folderPath, pattern, SearchOption.TopDirectoryOnly)).ToList(); Plugin.Log("[AudioMaster] Found " + audioFiles.Count + " loose audio files in " + folderPath); if (audioFiles.Count == 0) { Plugin.Log("[AudioMaster] No loose audio files found, skipping: " + folderPath); return; } _processedFilePaths.AddRange(audioFiles.Select(NormalizePath)); int successCount = 0; int failCount = 0; foreach (string file in audioFiles) { string fileName = Path.GetFileName(file); if (!fileName.Contains(".ogg")) { NonOggFiles++; } Plugin.Log("[AudioMaster] Loading file: " + fileName); string url = "file://" + file.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); AudioType audioType = GetAudioTypeFromExtension(file); if ((int)audioType == 0) { Plugin.Log("[AudioMaster] Skipping unsupported file type: " + fileName, "warning"); continue; } UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, audioType); try { ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = Plugin.FastLoad; await (AsyncOperation)(object)www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Plugin.Log("[AudioMaster] Failed to load audio: " + fileName + " - Error: " + www.error, "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; IndexToFilePath[index] = file; successCount++; } else { Plugin.Log("[AudioMaster] Failed to preload audio data for: " + file, "error"); } } } } finally { ((IDisposable)www)?.Dispose(); } await Task.Yield(); } Plugin.Log("[AudioMaster] Loose file processing complete for " + folderPath + ". Success: " + successCount + ", Failed: " + failCount); } private async Task ProcessFolderForAudio(string folderPath) { List<string> searchPatterns = new List<string> { "*.ogg" }; if (Plugin.ExtendedFormatSupport) { searchPatterns.Add("*.mp3"); searchPatterns.Add("*.wav"); } List<string> looseAudioFiles = searchPatterns.SelectMany((string pattern) => Directory.GetFiles(folderPath, pattern, SearchOption.TopDirectoryOnly)).ToList(); string[] subDirectories = Directory.GetDirectories(folderPath); bool hasSpeakerJson = File.Exists(Path.Combine(folderPath, "sPEAKer.json")); bool hasLooseAudio = looseAudioFiles.Count > 0; if (hasSpeakerJson && hasLooseAudio) { await LoadLooseFilesAsMixtape(folderPath, looseAudioFiles); } else if (hasLooseAudio) { await ProcessAudioFolder(folderPath); } string[] array = subDirectories; foreach (string mixtapeDir in array) { await LoadMixtapeAsync(mixtapeDir); } } private async Task<bool> LoadMixtapeAsync(string mixtapePath) { string mixtapeName = new DirectoryInfo(mixtapePath).Name; Plugin.Log("[AudioMaster] Loading mixtape: " + mixtapeName); Plugin.Log("[AudioMaster] From path: " + mixtapePath); MixtapeInfo mixtapeInfo = new MixtapeInfo { Name = mixtapeName, Author = "Unknown", FirstSongIndex = LoadedAudioClips.Count, Path = mixtapePath }; string metadataPath = Path.Combine(mixtapePath, "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; } Plugin.Log("[AudioMaster] Loaded mixtape metadata - Name: " + mixtapeInfo.Name + ", Author: " + mixtapeInfo.Author); } catch (Exception ex) { Exception e2 = ex; Plugin.Log("[AudioMaster] Failed to parse mixtape metadata for " + mixtapeName + ": " + e2.Message, "warning"); } } string iconPath = Path.Combine(mixtapePath, "..", "icon.png"); Plugin.Log("[AudioMaster] Checking for icon at path: " + iconPath); string normalizedIconPath = Path.GetFullPath(iconPath); Plugin.Log("[AudioMaster] Normalized icon path: " + normalizedIconPath); Plugin.Log($"[AudioMaster] Icon file exists: {File.Exists(normalizedIconPath)}"); if (File.Exists(normalizedIconPath)) { try { FileInfo fileInfo = new FileInfo(normalizedIconPath); Plugin.Log($"[AudioMaster] Icon file size: {fileInfo.Length} bytes"); string iconUrl = "file://" + normalizedIconPath.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); Plugin.Log("[AudioMaster] Converted icon URL: " + iconUrl); UnityWebRequest www2 = UnityWebRequest.Get(iconUrl); try { Plugin.Log("[AudioMaster] Starting web request for icon: " + mixtapeName); await (AsyncOperation)(object)www2.SendWebRequest(); Plugin.Log($"[AudioMaster] Web request result: {www2.result}"); Plugin.Log($"[AudioMaster] Response code: {www2.responseCode}"); if ((int)www2.result == 2 || (int)www2.result == 3) { Plugin.Log("[AudioMaster] Failed to load icon for mixtape " + mixtapeName + ": " + www2.error, "error"); } else { byte[] iconBytes = www2.downloadHandler.data; Texture2D iconTexture = new Texture2D(2, 2); if (ImageConversion.LoadImage(iconTexture, iconBytes)) { Plugin.Log($"[AudioMaster] Icon texture dimensions: {((Texture)iconTexture).width}x{((Texture)iconTexture).height}"); Plugin.Log($"[AudioMaster] Icon texture is readable: {((Texture)iconTexture).isReadable}"); mixtapeInfo.IconTexture = iconTexture; Plugin.Log("[AudioMaster] Successfully loaded and assigned icon for mixtape: " + mixtapeName); } else { Plugin.Log("[AudioMaster] Failed to load image data from bytes for: " + mixtapeName, "error"); } } } finally { ((IDisposable)www2)?.Dispose(); } } catch (Exception ex) { Exception e = ex; Plugin.Log("[AudioMaster] Exception loading icon for mixtape " + mixtapeName + ": " + e.Message, "error"); } } 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(mixtapePath, pattern, SearchOption.TopDirectoryOnly)).ToList(); _processedFilePaths.AddRange(audioFiles.Select(NormalizePath)); Plugin.Log("[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 (!fileName.Contains(".ogg")) { NonOggFiles++; } Plugin.Log("[AudioMaster] Loading mixtape song: " + fileName); string url = "file://" + audioFile.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); AudioType audioType = GetAudioTypeFromExtension(audioFile); if ((int)audioType == 0) { Plugin.Log("[AudioMaster] Skipping unsupported file type in mixtape: " + fileName, "warning"); continue; } UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, audioType); try { ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = Plugin.FastLoad; await (AsyncOperation)(object)www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Plugin.Log("[AudioMaster] Failed to load mixtape audio: " + fileName + " - Error: " + www.error, "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; IndexToFilePath[index] = audioFile; totalDuration += clip.length; songSuccessCount++; } else { Plugin.Log("[AudioMaster] Failed to preload audio data for mixtape song: " + audioFile, "error"); } } } } 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); Plugin.Log("[AudioMaster] Successfully loaded mixtape '" + mixtapeInfo.Name + "' by " + mixtapeInfo.Author + " - " + songSuccessCount + " songs, " + mixtapeInfo.TotalLength + " total"); return true; } Plugin.Log("[AudioMaster] Mixtape folder '" + mixtapeInfo.Name + "' contains no valid audio files.", "warning"); return false; } private async Task LoadLooseFilesAsMixtape(string folderPath, List<string> audioFiles) { string folderName = new DirectoryInfo(folderPath).Name; Plugin.Log("[AudioMaster] Loading loose files as mixtape: " + folderName); MixtapeInfo mixtapeInfo = new MixtapeInfo { Name = folderName, Author = "Unknown", FirstSongIndex = LoadedAudioClips.Count, Path = folderPath }; string metadataPath = Path.Combine(folderPath, "mixtape.json"); if (File.Exists(metadataPath)) { try { string jsonContent = File.ReadAllText(metadataPath); Plugin.Log("[AudioMaster] Raw JSON content: " + jsonContent); MixtapeMetadata metadata = JsonUtility.FromJson<MixtapeMetadata>(jsonContent); Plugin.Log("[AudioMaster] Parsed metadata - name: '" + metadata?.name + "', author: '" + metadata?.author + "'"); if (metadata != null) { if (!string.IsNullOrEmpty(metadata.name)) { mixtapeInfo.Name = metadata.name; sPEAKerUI.Instance.UpdateLoadingPhase("Loading " + mixtapeInfo.Name + "..."); } else { sPEAKerUI.Instance.UpdateLoadingPhase("Loading " + folderName + "..."); } if (!string.IsNullOrEmpty(metadata.author)) { mixtapeInfo.Author = metadata.author; } } else { Plugin.Log("[AudioMaster] JsonUtility.FromJson returned null for mixtape metadata", "warning"); } Plugin.Log("[AudioMaster] Final mixtape info - Name: " + mixtapeInfo.Name + ", Author: " + mixtapeInfo.Author); } catch (Exception ex) { Exception e2 = ex; Plugin.Log("[AudioMaster] Failed to parse mixtape metadata for " + folderName + ": " + e2.Message, "warning"); string[] parts2 = folderName.Split('-', 2); if (parts2.Length == 2) { mixtapeInfo.Author = parts2[0].Trim(); mixtapeInfo.Name = parts2[1].Trim(); Plugin.Log("[AudioMaster] Used fallback parsing - Author: " + mixtapeInfo.Author + ", Name: " + mixtapeInfo.Name); } } } else { string[] parts = folderName.Split('-', 2); if (parts.Length == 2) { mixtapeInfo.Author = parts[0].Trim(); mixtapeInfo.Name = parts[1].Trim(); Plugin.Log("[AudioMaster] Parsed folder name - Author: " + mixtapeInfo.Author + ", Name: " + mixtapeInfo.Name); } sPEAKerUI.Instance.UpdateLoadingPhase("Loading " + mixtapeInfo.Name + "..."); } string iconPath = Path.Combine(folderPath, "icon.png"); if (File.Exists(iconPath)) { try { string iconUrl = "file://" + iconPath.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); UnityWebRequest www2 = UnityWebRequest.Get(iconUrl); try { await (AsyncOperation)(object)www2.SendWebRequest(); if ((int)www2.result == 2 || (int)www2.result == 3) { Plugin.Log("[AudioMaster] Failed to load icon for mixtape " + folderName + ": " + www2.error, "error"); } else { byte[] iconBytes = www2.downloadHandler.data; Texture2D iconTexture = new Texture2D(2, 2); if (ImageConversion.LoadImage(iconTexture, iconBytes)) { mixtapeInfo.IconTexture = iconTexture; Plugin.Log("[AudioMaster] Successfully loaded icon for loose files mixtape: " + folderName); } } } finally { ((IDisposable)www2)?.Dispose(); } } catch (Exception ex) { Exception e = ex; Plugin.Log("[AudioMaster] Exception loading icon for loose files mixtape " + folderName + ": " + e.Message, "error"); } } _processedFilePaths.AddRange(audioFiles.Select(NormalizePath)); int songSuccessCount = 0; float totalDuration = 0f; foreach (string audioFile in audioFiles) { string fileName = Path.GetFileName(audioFile); if (!fileName.Contains(".ogg")) { NonOggFiles++; } Plugin.Log("[AudioMaster] Loading loose file as mixtape song: " + fileName); string url = "file://" + audioFile.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); AudioType audioType = GetAudioTypeFromExtension(audioFile); if ((int)audioType == 0) { Plugin.Log("[AudioMaster] Skipping unsupported file type: " + fileName, "warning"); continue; } UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, audioType); try { ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = Plugin.FastLoad; await (AsyncOperation)(object)www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { Plugin.Log("[AudioMaster] Failed to load loose audio: " + fileName + " - Error: " + www.error, "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; IndexToFilePath[index] = audioFile; totalDuration += clip.length; songSuccessCount++; } else { Plugin.Log("[AudioMaster] Failed to preload audio data for loose file: " + audioFile, "error"); } } } } 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); Plugin.Log("[AudioMaster] Successfully loaded loose files as mixtape '" + mixtapeInfo.Name + "' by " + mixtapeInfo.Author + " - " + songSuccessCount + " songs, " + mixtapeInfo.TotalLength + " total"); } else { Plugin.Log("[AudioMaster] Loose files in '" + folderName + "' could not be loaded as mixtape.", "warning"); } } 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 var cachedEntry) && cachedEntry.Size == size && cachedEntry.LastModified == lastModified) { _cacheHits++; return cachedEntry.Hash; } _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("[AudioMaster] Failed to generate metadata hash for " + filePath + ": " + e.Message, "error"); return await Task.FromResult<string>(null); } } 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); Plugin.Log("[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; Plugin.Log($"[Cache] Successfully loaded {_fileHashCache.Hashes.Count} hashes from cache (Algo v{1})."); } else { Plugin.Log("[Cache] Cache is from an incompatible algorithm version. Regenerating.", "warning"); _fileHashCache = new FileHashCache { AlgorithmVersion = 1 }; } } else { Plugin.Log("[Cache] No cache file found. A new one will be created."); _fileHashCache = new FileHashCache { AlgorithmVersion = 1 }; } } catch (Exception ex) { Plugin.Log("[Cache] Error loading hash cache, regenerating. Error: " + ex.Message, "error"); _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); } Plugin.Log($"[Cache] Cleanup removed {list.Count} stale entries."); Plugin.Log($"[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); } Plugin.Log($"[Cache] Saved {_fileHashCache.Hashes.Count} hashes to cache at {_cachePath}"); } catch (Exception ex) { Plugin.Log("[Cache] Failed to save hash cache: " + ex.Message, "error"); } } public static void FirePlaybackAction(PlaybackActionType action, AudioClip clip, float time) { AudioMaster.OnPlaybackAction?.Invoke(action, clip, time); Plugin.Log(string.Format("[AudioMaster] PlaybackAction fired: {0}, clip={1}, time={2}", action, ((clip != null) ? ((Object)clip).name : null) ?? "null", time)); } public async Task<AudioClip> GetNonStreamedClipAsync(int index) { if (index < 0 || index >= LoadedAudioClips.Count) { return null; } if (!Plugin.FastLoad) { return LoadedAudioClips[index]; } if (!IndexToFilePath.TryGetValue(index, out var filePath) || string.IsNullOrEmpty(filePath)) { return LoadedAudioClips[index]; } Plugin.Log($"[AudioMaster] Loading non-streamed clip for index {index} from: {Path.GetFileName(filePath)}"); string url = "file://" + filePath.Replace('\\', '/').Replace("+", "%2B").Replace("#", "%23"); AudioType audioType = GetAudioTypeFromExtension(filePath); UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(url, audioType); try { ((DownloadHandlerAudioClip)www.downloadHandler).streamAudio = false; await (AsyncOperation)(object)www.SendWebRequest(); if ((int)www.result != 1) { Plugin.Log("[AudioMaster] Failed to reload clip non-streamed: " + www.error, "error"); return null; } AudioClip clip = DownloadHandlerAudioClip.GetContent(www); if ((Object)(object)clip == (Object)null) { return null; } ((Object)clip).name = ((Object)LoadedAudioClips[index]).name + " (HostOnly)"; clip.LoadAudioData(); while ((int)clip.loadState == 1) { await Task.Yield(); } if ((int)clip.loadState != 2) { Plugin.Log("[AudioMaster] Failed to load audio data for non-streamed clip.", "error"); return null; } Plugin.Log("[AudioMaster] Non-streamed clip ready: " + ((Object)clip).name); return clip; } finally { ((IDisposable)www)?.Dispose(); } } public static void UpdatePlaybackState(PlaybackState newState, int songIndex = -1, float pausedTime = 0f) { CurrentPlaybackState = newState; if (songIndex >= 0) { CurrentSongIndex = songIndex; } CurrentPausedTime = pausedTime; AudioMaster.OnPlaybackStateChanged?.Invoke(newState); Plugin.Log($"[AudioMaster] Playback state updated - State: {newState}, Song: {CurrentSongIndex}, PausedTime: {pausedTime}"); } private static void ResetPlaybackState() { CurrentPlaybackState = PlaybackState.Stopped; CurrentSongIndex = 0; CurrentPausedTime = 0f; Plugin.Log("[AudioMaster] Playback state reset to defaults"); } private void PlaySongAtIndex(int index) { if (index < 0 || index >= LoadedAudioClips.Count || (Object)(object)MixtapeAudioSource == (Object)null) { Plugin.Log($"[AudioMaster] PlaySongAtIndex called with invalid index: {index}. Aborting.", "error"); return; } string text = IndexToSongHash[index]; Plugin.Log($"[AudioMaster] PlaySongAtIndex: Playing index {index} with hash '{text}'."); AudioMaster.OnSongChanged?.Invoke(text); if (Plugin.MilestonesEnabled) { MilestoneTracker.Instance.RecordSongPlayed(text); } Plugin.Log("[AudioMaster] OnSongChanged event has been invoked for hash '" + text + "'."); AudioClip val = LoadedAudioClips[index]; MixtapeAudioSource.clip = val; MixtapeAudioSource.Play(); FirePlaybackAction(PlaybackActionType.Play, val, 0f); if (Plugin.MilestonesEnabled) { MilestoneTracker.Instance.ReportProgress("UnstoppableForce"); } CurrentMixtape = GetMixtapeForSongIndex(index); if (!string.IsNullOrEmpty(CurrentMixtape.Name)) { sPEAKerUI.Instance.ShowToast(CurrentMixtape.Name + " - " + ((Object)val).name); sPEAKerUI.Instance.ShowAuthorInfo(CurrentMixtape.Author, IsYouTubeMode); } else { sPEAKerUI.Instance.HideAuthorInfo(); sPEAKerUI.Instance.ShowToast(((Object)val).name); } sPEAKerUI.Instance.ShowDuration(val.length); } [IteratorStateMachine(typeof(<MonitorForAutoSkip>d__126))] private IEnumerator MonitorForAutoSkip() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <MonitorForAutoSkip>d__126(0) { <>4__this = this }; } private void AutoSkipToNext() { List<AudioClip> loadedAudioClips = LoadedAudioClips; if (loadedAudioClips != null && loadedAudioClips.Count <= 0) { Plugin.Log("[AudioMaster] Not enough songs to auto-skip"); return; } int num2; switch (CurrentLoopState) { case LoopState.One: num2 = CurrentSongIndex; break; case LoopState.All: num2 = GetNextSongIndex(CurrentSongIndex); break; default: { string item = IndexToSongHash[CurrentSongIndex]; int num = SongQueue.IndexOf(item); if (num == SongQueue.Count - 1) { Plugin.Log("[AudioMaster] Reached end of queue. Stopping."); Netcode.Instance.SendStopPlayback(); return; } num2 = GetNextSongIndex(CurrentSongIndex); break; } } string songHash = IndexToSongHash[num2]; string name = ((Object)LoadedAudioClips[num2]).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 StopAllPlayback() { ResetPlaybackState(); FirePlaybackAction(PlaybackActionType.Stop, null, 0f); if ((Object)(object)MixtapeAudioSource != (Object)null && MixtapeAudioSource.isPlaying) { MixtapeAudioSource.Stop(); } if ((Object)(object)YouTubeAudioSource != (Object)null && YouTubeAudioSource.isPlaying) { YouTubeAudioSource.Stop(); } } [IteratorStateMachine(typeof(<FadeOut>d__132))] private IEnumerator FadeOut(float playDuration, float fadeDuration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FadeOut>d__132(0) { <>4__this = this, playDuration = playDuration, fadeDuration = fadeDuration }; } public static void PauseLocalAudio() { if ((Object)(object)MixtapeAudioSource != (Object)null && MixtapeAudioSource.isPlaying) { MixtapeAudioSource.Pause(); Plugin.Log("[AudioMaster] Local audio paused for YouTube mode"); } } public static void ResumeLocalAudio() { if (!((Object)(object)MixtapeAudioSource != (Object)null) || !((Object)(object)MixtapeAudioSource.clip != (Object)null)) { return; } MixtapeAudioSource.UnPause(); if (MixtapeAudioSource.isPlaying) { CurrentPlaybackState = PlaybackState.Playing; Controller controllerInstance = TheObserver.Instance.GetControllerInstance(); if ((Object)(object)controllerInstance != (Object)null) { controllerInstance.UpdateMainPrompt(PlaybackState.Playing); } } AirportEditor.PlayAllVisualizers(ActiveSource); Plugin.Log("[AudioMaster] Local audio resumed from YouTube mode"); } public static void ToggleLoop() { CurrentLoopState = (LoopState)((int)(CurrentLoopState + 1) % 3); switch (CurrentLoopState) { case LoopState.None: sPEAKerUI.Instance.ShowToast("Loop is OFF"); break; case LoopState.All: sPEAKerUI.Instance.ShowToast("Looping ALL songs"); break; case LoopState.One: sPEAKerUI.Instance.ShowToast("Looping CURRENT song"); if (Plugin.MilestonesEnabled) { MilestoneTracker.Instance.ReportProgress("OneMoreTime"); } break; } if (PhotonNetwork.IsMasterClient) { Netcode.Instance.SendLoopStateChange((int)CurrentLoopState); } AudioMaster.OnLoopStateChanged?.Invoke(CurrentLoopState); Plugin.Log($"[AudioMaster] Loop state toggled: {CurrentLoopState}"); } public void SetVolume(float newVolume) { float num = Mathf.Clamp(newVolume, 0f, 1f); if (num >= 1f && NetworkVolume < 1f) { PlayFX(SoundEffects.VolMax); if (Plugin.MilestonesEnabled) { MilestoneTracker.Instance.ReportProgress("SubeElBajo"); } } if (num <= 0f && NetworkVolume > 0f && Plugin.MilestonesEnabled) { MilestoneTracker.Instance.ReportProgress("Mute"); } NetworkVolume = num; ApplyVolumeMultiplier(); Plugin.Log("[AudioMaster] Network volume set to " + NetworkVolume + " with local multiplier " + LocalVolumeMultiplier + " = final volume " + NetworkVolume * LocalVolumeMultiplier); } public static void SetLocalVolumeMultiplier(float volume) { float localVolumeMultiplier = LocalVolumeMultiplier; LocalVolumeMultiplier = Mathf.Clamp01(volume); if (Plugin.MilestonesEnabled && Mathf.Abs(localVolumeMultiplier - LocalVolumeMultiplier) > 0.01f) { MilestoneTracker.Instance.ReportProgress("MyWay"); } ApplyVolumeMultiplier(); } private static void ApplyVolumeMultiplier() { if ((Object)(object)MixtapeAudioSource != (Object)null) { MixtapeAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; } if ((Object)(object)YouTubeAudioSource != (Object)null) { YouTubeAudioSource.volume = NetworkVolume * LocalVolumeMultiplier; } } public static void UpdateSpatialAudioSetting(bool useSpatial) { float spatialBlend = (useSpatial ? 1f : 0f); if ((Object)(object)MixtapeAudioSource != (Object)null) { MixtapeAudioSource.spatialBlend = spatialBlend; } if ((Object)(object)EffectsAudioSource != (Object)null) { EffectsAudioSource.spatialBlend = spatialBlend; } if ((Object)(object)YouTubeAudioSource != (Object)null) { YouTubeAudioSource.spatialBlend = spatialBlend; } Plugin.Log("[AudioMaster] Spatial audio updated: " + (useSpatial ? "enabled" : "disabled")); } public static void PlayFX(AudioClip clip) { if (Plugin.SoundEffectsEnabled && !((Object)(object)EffectsAudioSource == (Object)null) && !((Object)(object)clip == (Object)null)) { EffectsAudioSource.clip = clip; EffectsAudioSource.Play(); Plugin.Log("[AudioMaster] Playing sound effect: " + ((Object)clip).name); } } public static void PlayLaserFX() { if (Plugin.SoundEffectsEnabled && !((Object)(object)EffectsAudioSource == (Object)null)) { List<AudioClip> list = new List<AudioClip>(3) { SoundEffects.Laser1, SoundEffects.Laser2, SoundEffects.Laser3 }; int index = Random.Range(0, 2); EffectsAudioSource.volume = 0.2f; EffectsAudioSource.clip = list[index]; EffectsAudioSource.Play(); Plugin.Log("[AudioMaster] Playing laser sound effect: " + ((Object)EffectsAudioSource.clip).name); } } public static int GetNextSongIndex(int currentIndex) { if (SongQueue.Count < 2) { return currentIndex; } string valueOrDefault = IndexToSongHash.GetValueOrDefault(currentIndex, ""); if (string.IsNullOrEmpty(valueOrDefault)) { return (currentIndex + 1) % LoadedAudioClips.Count; } int num = SongQueue.IndexOf(valueOrDefault); if (num == -1) { return (currentIndex + 1) % LoadedAudioClips.Count; } int index = (num + 1) % SongQueue.Count; string key = SongQueue[index]; return SongHashToIndex.GetValueOrDefault(key, 0); } public static int GetPreviousSongIndex(int currentIndex) { if (SongQueue.Count < 2) { return currentIndex; } string valueOrDefault = IndexToSongHash.GetValueOrDefault(currentIndex, ""); if (string.IsNullOrEmpty(valueOrDefault)) { return (currentIndex - 1 + LoadedAudioClips.Count) % LoadedAudioClips.Count; } int num = SongQueue.IndexOf(valueOrDefault); if (num == -1) { return (currentIndex - 1 + LoadedAudioClips.Count) % LoadedAudioClips.Count; } int index = (num - 1 + SongQueue.Count) % SongQueue.Count; string key = SongQueue[index]; return SongHashToIndex.GetValueOrDefault(key, 0); } public static void SetYouTubeMode(bool enabled) { IsYouTubeMode = enabled; Plugin.Log($"[AudioMaster] YouTube mode set to {enabled}"); AudioMaster.OnYouTubeModeChanged?.Invoke(enabled); } public void HandleNextSong(string songHash, string songName) { if (Plugin.MilestonesEnabled) { MilestoneTracker.Instance.ReportProgress("ThankUNext"); } if (CurrentPlaybackState == PlaybackState.Stopped) { return; } if (SongHashToIndex.TryGetValue(songHash, out var value)) { CurrentSongIndex = value; PlaySongAtIndex(value); } else { Plugin.Log("[AudioMaster] Song not available for next song, playing white noise", "warning"); MixtapeAudioSource.clip = SoundEffects.WhiteNoise; MixtapeAudioSource.Play(); if (Plugin.MilestonesEnabled) { MilestoneTracker.Instance.ReportProgress("WhiteNoise"); } sPEAKerUI.Instance.ShowToast(songName); float length = SoundEffects.WhiteNoise.length; ((MonoBehaviour)this).StartCoroutine(FadeOut(length / 2f, length / 4f)); } UpdatePlaybackState(PlaybackState.Playing, CurrentSongIndex); if (PhotonNetwork.IsMasterClient && !IsYouTubeMode) { StartAutoSkipMonitoring(); } } public void HandleNetworkLoopStateChange(int loopState) { CurrentLoopState = (LoopState)loopState; if (!PhotonNetwork.IsMasterClient) { switch (CurrentLoopState) { case LoopState.None: sPEAKerUI.Instance.ShowToast("Loop is OFF"); break; case LoopState.All: sPEAKerUI.Instance.ShowToast("Looping ALL songs"); break; case LoopState.One: sPEAKerUI.Instance.ShowToast("Looping CURRENT song"); break; } } AudioMaster.OnLoopStateChanged?.Invoke(CurrentLoopState); Plugin.Log($"[AudioMaster] Network updated loop state: {CurrentLoopState}"); } public void HandleNetworkQueueSync(string[] newQueue) { SongQueue = newQueue.ToList(); Plugin.Log($"[AudioMaster] Synced queue from network. {SongQueue.Count} tracks."); AudioMaster.OnQueueSynced?.Invoke(); } public void Master_PlaySongByHash(string songHash) { if (PhotonNetwork.IsMasterClient && SongHashToIndex.TryGetValue(songHash, out var value) && value >= 0 && value < LoadedAudioClips.Count) { string name = ((Object)LoadedAudioClips[value]).name; Netcode.Instance.SendNextSong(songHash, name); } } public void Master_UpdateQueue(List<string> newQueueOrder, bool broadcast = true) { if (PhotonNetwork.IsMasterClient) { SongQueue = new List<string>(newQueueOrder); Plugin.Log($"[AudioMaster] Master updated queue. New count: {SongQueue.Count}"); if (broadcast) { Netcode.Instance.BroadcastSongQueue(SongQueue.ToArray()); } } } public void Master_ShuffleQueue(bool broadcast = true) { if (PhotonNetwork.IsMasterClient) { Random random = new Random(); SongQueue = SongQueue.OrderBy((string x) => random.Next()).ToList(); Plugin.Log("[AudioMaster] Master shuffled the queue."); sPEAKerUI.Instance.ShowToast("Queue has been shuffled!"); if (broadcast) { Netcode.Instance.BroadcastSongQueue(SongQueue.ToArray()); } } } public void SetFollow(Transform target) { _targetTransform = target; _targetCharacter = null; _isFollowing = (Object)(object)target != (Object)null; if (_isFollowing) { Plugin.Log("[AudioMaster] Set to follow transform: " + ((Object)target).name); } else { Plugin.Log("[AudioMaster] Stopped following."); } } public void SetFollow(Character target) { _targetCharacter = target; _targetTransform = null; _isFollowing = (Object)(object)target != (Object)null; if (_isFollowing) { Plugin.Log("[AudioMaster] Set to follow character: " + ((target != null) ? ((Object)target).name : null)); } else { Plugin.Log("[AudioMaster] Stopped following."); } } public static MixtapeInfo GetMixtapeForSongIndex(int songIndex) { return AvailableMixtapes.FirstOrDefault((MixtapeInfo m) => m.ContainsSongIndex(songIndex)) ?? new MixtapeInfo { Author = "unknown" }; } public Texture2D GetIconForSongHash(string songHash) { if (SongHashToIndex.TryGetValue(songHash, out var songIndex)) { MixtapeInfo mixtapeInfo = AvailableMixtapes.FirstOrDefault((MixtapeInfo m) => m.ContainsSongIndex(songIndex)); if (mixtapeInfo != null) { return mixtapeInfo.IconTexture; } } return null; } public static Texture2D GetCurrentMixtapeIcon() { if (LoadedAudioClips.Count == 0) { return null; } return GetMixtapeForSongIndex(CurrentSongIndex)?.IconTexture; } public string GetCurrentMixtapeAuthor() { if (LoadedAudioClips.Count == 0) { return null; } return GetMixtapeForSongIndex(CurrentSongIndex)?.Author; } 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)MixtapeAudioSource != (Object)null && ((Component)MixtapeAudioSource).transform.position != val) { ((Component)MixtapeAudioSource).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 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 == PlaybackState.Stopped || !((Object)(object)ActiveSource != (Object)null) || !((Object)(object)ActiveSource.clip != (Object)null)) { return; } sPEAKerUI.Instance.ShowDuration(ActiveSource.clip.length); if (IsYouTubeMode) { ytTrack ytTrack2 = YouTubePlayer.Instance?.currentTrack; if (ytTrack2 != null && !string.IsNullOrEmpty(ytTrack2.queuedBy)) { sPEAKerUI.Instance.ShowAuthorInfo(ytTrack2.queuedBy, IsYTMode: true); } else { sPEAKerUI.Instance.HideAuthorInfo(); } } else { MixtapeInfo mixtapeForSongIndex = GetMixtapeForSongIndex(CurrentSongIndex); if (!string.IsNullOrEmpty(mixtapeForSongIndex.Name)) { sPEAKerUI.Instance.ShowAuthorInfo(mixtapeForSongIndex.Author, IsYTMode: false); } else { sPEAKerUI.Instance.HideAuthorInfo(); } } } } 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 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 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 PartyOff { 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 AudioClip Laser1 { get; private set; } public static AudioClip Laser2 { get; private set; } public static AudioClip Laser3 { get; private set; } public static AudioClip Respawn { 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 "partyoff": PartyOff = clip; break; case "turnon": TurnOn = clip; break; case "volmax": VolMax = clip; break; case "whitenoise": WhiteNoise = clip; break; case "laser1": Laser1 = clip; break; case "laser2": Laser2 = clip; break; case "laser3": Laser3 = clip; break; case "respawn": Respawn = 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 enum PlaybackActionType { Play, Pause, Resume, Stop } public class Controller : MonoBehaviour { [CompilerGenerated] private sealed class <CheckHitTheFloor>d__60 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Controller <>4__this; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CheckHitTheFloor>d__60(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)<>4__this.item == (Object)null) { return false; } if ((Object)(object)AudioMaster.ActiveSource == (Object)null || !AudioMaster.ActiveSource.isPlaying) { return false; } if ((Object)(object)Character.localCharacter == (Object)null) { return false; } if ((Object)(object)Character.localCharacter != (Object)(object)<>4__this.item.lastHolderCharacter) { return false; } MilestoneTracker.Instance.ReportProgress("HitTheFloor"); 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 <DelayedUIUpdate>d__56 : 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__56(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; Plugin.Log($"[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__21 : 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__21(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; Plugin.Log("[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("[Controller] AudioMaster failed to initialize after 5 seconds!", "error"); 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(); Plugin.Log("[Controller] Loaded " + <>4__this._currentAudioClips.Length + " audio clips"); AudioMaster.Instance.SetFollow(((Component)<>4__this).transform); <>4__this.SyncWithPersistentAudio(); <>4__this.droneBehaviour.CreateSearchRadiusIndicator(); TheObserver.Instance.OnsPEAKerSpawned(<>4__this); 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__29 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public Controller <>4__this; private float <waitTime>5__1; private float <syncTimeout>5__2; private float <syncWaitTime>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <TurnOnSequence>d__29(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1