Decompiled source of sPEAKer v2.3.3

plugins/onlystar.sPEAKer.dll

Decompiled 3 weeks ago
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