Decompiled source of CaptainAudio v1.3.54

CaptainAudio.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 CaptainAudio
{
	public static class AudioLoader
	{
		private class LoadTask
		{
			public string FolderName;

			public string ClipName;

			public byte[] AudioData;

			public bool IsCompleted;

			public AudioClip LoadedClip;
		}

		private static bool _isInitialized = false;

		private const int MAX_RETRY_COUNT = 3;

		private const float RETRY_DELAY = 0.5f;

		private static HashSet<string> _externalOverrideFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		public static IEnumerator InitializeAsync()
		{
			if (!_isInitialized)
			{
				Plugin.CustomMusic.Clear();
				Plugin.CustomAmbient.Clear();
				Plugin.CustomSFX.Clear();
				Plugin.CustomMusicList.Clear();
				Plugin.CustomAmbientList.Clear();
				Plugin.CustomSFXList.Clear();
				yield return LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.Music", Plugin.CustomMusic, Plugin.CustomMusicList);
				yield return LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.Ambient", Plugin.CustomAmbient, Plugin.CustomAmbientList);
				yield return LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.SFX", Plugin.CustomSFX, Plugin.CustomSFXList);
				yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadExternalAudioFilesAsync());
				StringMatchCache.Initialize();
				_isInitialized = true;
				Plugin.Log.LogInfo((object)$"Loaded: {Plugin.CustomMusicList.Count} folders, {Plugin.CustomMusic.Count} clips");
				if ((Object)(object)MusicMan.instance != (Object)null)
				{
					MusicMan_Awake_Patch.ApplyBossMusic(MusicMan.instance);
					Plugin.Log.LogInfo((object)"[CaptainAudio] 보스 음악 재적용 완료");
				}
			}
		}

		private static IEnumerator LoadEmbeddedResourcesParallel(string resourcePrefix, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict)
		{
			Assembly assembly = Assembly.GetExecutingAssembly();
			string[] resourceNames = (from name in assembly.GetManifestResourceNames()
				where name.StartsWith(resourcePrefix)
				select name).ToArray();
			if (resourceNames.Length == 0)
			{
				yield break;
			}
			List<LoadTask> tasks = new List<LoadTask>();
			string[] array = resourceNames;
			foreach (string resourceName in array)
			{
				Stream stream = assembly.GetManifestResourceStream(resourceName);
				if (stream != null)
				{
					byte[] audioData = new byte[stream.Length];
					stream.Read(audioData, 0, audioData.Length);
					stream.Dispose();
					string fileName = resourceName.Substring(resourcePrefix.Length + 1);
					var (folderName, clipName) = ParseResourcePath(fileName);
					if (!string.IsNullOrEmpty(clipName))
					{
						LoadTask task = new LoadTask
						{
							FolderName = folderName,
							ClipName = clipName,
							AudioData = audioData,
							IsCompleted = false,
							LoadedClip = null
						};
						tasks.Add(task);
						((MonoBehaviour)Plugin.instance).StartCoroutine(LoadSingleClipWithRetry(task));
					}
				}
			}
			while (tasks.Any((LoadTask t) => !t.IsCompleted))
			{
				yield return null;
			}
			foreach (LoadTask task2 in tasks)
			{
				if ((Object)(object)task2.LoadedClip == (Object)null)
				{
					continue;
				}
				if (!string.IsNullOrEmpty(task2.FolderName))
				{
					if (!folderDict.ContainsKey(task2.FolderName))
					{
						folderDict[task2.FolderName] = new Dictionary<string, AudioClip>();
					}
					folderDict[task2.FolderName][task2.ClipName] = task2.LoadedClip;
				}
				else
				{
					singleDict[task2.ClipName] = task2.LoadedClip;
				}
			}
			tasks.Clear();
		}

		private static IEnumerator LoadSingleClipWithRetry(LoadTask task)
		{
			int retryCount = Plugin.LoadRetryCount.Value;
			for (int attempt = 0; attempt < retryCount; attempt++)
			{
				bool success = false;
				AudioClip clip = null;
				yield return LoadAudioClipFromBytes(task.AudioData, task.ClipName, delegate(AudioClip loadedClip)
				{
					clip = loadedClip;
					success = (Object)(object)loadedClip != (Object)null;
				});
				if (success)
				{
					task.LoadedClip = clip;
					task.AudioData = null;
					task.IsCompleted = true;
					yield break;
				}
				if (attempt < retryCount - 1)
				{
					yield return (object)new WaitForSeconds(0.5f);
				}
			}
			task.IsCompleted = true;
		}

		private static IEnumerator LoadAudioClipFromBytes(byte[] audioData, string clipName, Action<AudioClip> onComplete)
		{
			string fileExtension = DetectAudioFormat(audioData);
			string tempPath = Path.Combine(Application.temporaryCachePath, $"temp_audio_{clipName}_{Guid.NewGuid()}{fileExtension}");
			try
			{
				Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
				File.WriteAllBytes(tempPath, audioData);
			}
			catch
			{
				onComplete?.Invoke(null);
				yield break;
			}
			AudioType audioType = GetAudioType(fileExtension);
			string uri = new Uri(tempPath).AbsoluteUri;
			UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(uri, audioType);
			try
			{
				DownloadHandlerAudioClip handler = (DownloadHandlerAudioClip)www.downloadHandler;
				handler.streamAudio = false;
				handler.compressed = false;
				www.timeout = 15;
				yield return www.SendWebRequest();
				if ((int)www.result == 1)
				{
					AudioClip clip = DownloadHandlerAudioClip.GetContent(www);
					if ((Object)(object)clip != (Object)null && clip.length > 0f && clip.samples > 0 && (int)clip.loadState == 2)
					{
						((Object)clip).name = clipName;
						onComplete?.Invoke(clip);
					}
					else
					{
						ManualLogSource log = Plugin.Log;
						object[] obj2 = new object[4] { clipName, null, null, null };
						obj2[1] = ((clip != null) ? new float?(clip.length) : ((float?)null));
						obj2[2] = ((clip != null) ? new int?(clip.samples) : ((int?)null));
						obj2[3] = ((clip != null) ? new AudioDataLoadState?(clip.loadState) : ((AudioDataLoadState?)null));
						log.LogWarning((object)string.Format("Invalid clip: {0} (length={1}, samples={2}, state={3})", obj2));
						onComplete?.Invoke(null);
					}
				}
				else
				{
					Plugin.Log.LogWarning((object)("Failed to load: " + clipName + " - " + www.error));
					onComplete?.Invoke(null);
				}
			}
			finally
			{
				((IDisposable)www)?.Dispose();
			}
			if (File.Exists(tempPath))
			{
				try
				{
					File.Delete(tempPath);
				}
				catch
				{
				}
			}
		}

		private static string ResolveMusicPath(string pluginMusicPath)
		{
			if (!Plugin.GetMusicFromConfig.Value)
			{
				return pluginMusicPath;
			}
			string text = Path.Combine(Paths.ConfigPath, "CaptainAudio", "music");
			try
			{
				if (!Directory.Exists(text))
				{
					Directory.CreateDirectory(text);
					Plugin.Log.LogInfo((object)("[Config] music 폴더 생성됨: " + text));
				}
				if (Directory.GetFiles(text, "*", SearchOption.AllDirectories).Any((string f) => IsValidAudioFile(f)))
				{
					return text;
				}
				Plugin.Log.LogInfo((object)("[Config] config music 폴더에 오디오 파일 없음 → plugins 폴더 사용: " + pluginMusicPath));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Config] config music 폴더 접근 실패, plugins 폴더 사용: " + ex.Message));
			}
			return pluginMusicPath;
		}

		private static IEnumerator LoadExternalAudioFilesAsync()
		{
			string newBasePath = Path.Combine(Paths.PluginPath, "korCaptain-CaptainAudio");
			string newMusicPath = ResolveMusicPath(Path.Combine(newBasePath, "music"));
			string newSfxPath = Path.Combine(newBasePath, "sfx");
			string newAmbientPath = Path.Combine(newBasePath, "ambient");
			if (Directory.Exists(newMusicPath) || Directory.Exists(newSfxPath) || Directory.Exists(newAmbientPath))
			{
				_externalOverrideFolders.Clear();
				if (Directory.Exists(newMusicPath))
				{
					string[] directories = Directory.GetDirectories(newMusicPath);
					foreach (string subFolder in directories)
					{
						string folderName = Path.GetFileName(subFolder);
						if (Directory.GetFiles(subFolder).Any((string f) => IsValidAudioFile(f)))
						{
							_externalOverrideFolders.Add("Music:" + folderName);
							Plugin.Log.LogInfo((object)("[Override] Music/" + folderName + ": 외부 음악 감지됨 → 내장 음악 대체"));
						}
					}
				}
				if (Directory.Exists(newSfxPath))
				{
					string[] directories2 = Directory.GetDirectories(newSfxPath);
					foreach (string subFolder2 in directories2)
					{
						string folderName2 = Path.GetFileName(subFolder2);
						if (Directory.GetFiles(subFolder2).Any((string f) => IsValidAudioFile(f)))
						{
							_externalOverrideFolders.Add("SFX:" + folderName2);
							Plugin.Log.LogInfo((object)("[Override] SFX/" + folderName2 + ": 외부 SFX 감지됨 → 내장 SFX 대체"));
						}
					}
				}
				if (Directory.Exists(newAmbientPath))
				{
					string[] directories3 = Directory.GetDirectories(newAmbientPath);
					foreach (string subFolder3 in directories3)
					{
						string folderName3 = Path.GetFileName(subFolder3);
						if (Directory.GetFiles(subFolder3).Any((string f) => IsValidAudioFile(f)))
						{
							_externalOverrideFolders.Add("Ambient:" + folderName3);
							Plugin.Log.LogInfo((object)("[Override] Ambient/" + folderName3 + ": 외부 Ambient 감지됨 → 내장 Ambient 대체"));
						}
					}
				}
				ApplyExternalOverrides();
				if (Directory.Exists(newMusicPath))
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(newMusicPath, Plugin.CustomMusic, Plugin.CustomMusicList));
				}
				if (Directory.Exists(newSfxPath))
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(newSfxPath, Plugin.CustomSFX, Plugin.CustomSFXList));
				}
				if (Directory.Exists(newAmbientPath))
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(newAmbientPath, Plugin.CustomAmbient, Plugin.CustomAmbientList));
				}
				yield break;
			}
			string externalPath = Path.Combine(Paths.PluginPath, "CaptainAudio");
			if (Directory.Exists(externalPath))
			{
				_externalOverrideFolders.Clear();
				ScanExternalFolders(externalPath);
				ApplyExternalOverrides();
				string musicPath = Path.Combine(externalPath, "Music");
				string sfxPath = Path.Combine(externalPath, "SFX");
				string ambientPath = Path.Combine(externalPath, "Ambient");
				if (Directory.Exists(musicPath))
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(musicPath, Plugin.CustomMusic, Plugin.CustomMusicList));
				}
				if (Directory.Exists(sfxPath))
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(sfxPath, Plugin.CustomSFX, Plugin.CustomSFXList));
				}
				if (Directory.Exists(ambientPath))
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(ambientPath, Plugin.CustomAmbient, Plugin.CustomAmbientList));
				}
			}
		}

		private static void ScanExternalFolders(string externalPath)
		{
			string[] array = new string[3] { "Music", "Ambient", "SFX" };
			string[] array2 = array;
			foreach (string text in array2)
			{
				string path = Path.Combine(externalPath, text);
				if (!Directory.Exists(path))
				{
					continue;
				}
				string[] directories = Directory.GetDirectories(path);
				foreach (string path2 in directories)
				{
					string fileName = Path.GetFileName(path2);
					if (Directory.GetFiles(path2).Any((string file) => IsValidAudioFile(file)))
					{
						string item = text + ":" + fileName;
						_externalOverrideFolders.Add(item);
						Plugin.Log.LogInfo((object)("[Override] " + text + "/" + fileName + ": 외부 음악 감지됨 → 내장 음악 대체"));
					}
				}
			}
		}

		private static bool IsValidAudioFile(string filePath)
		{
			if (string.IsNullOrEmpty(filePath))
			{
				return false;
			}
			string text = Path.GetExtension(filePath).ToLower();
			return text == ".ogg" || text == ".wav" || text == ".mp3";
		}

		private static void ApplyExternalOverrides()
		{
			foreach (string externalOverrideFolder in _externalOverrideFolders)
			{
				string[] array = externalOverrideFolder.Split(':');
				if (array.Length != 2)
				{
					continue;
				}
				string text = array[0];
				string folderName = array[1];
				Dictionary<string, Dictionary<string, AudioClip>> dictionary = null;
				switch (text)
				{
				case "Music":
					dictionary = Plugin.CustomMusicList;
					break;
				case "Ambient":
					dictionary = Plugin.CustomAmbientList;
					break;
				case "SFX":
					dictionary = Plugin.CustomSFXList;
					break;
				}
				if (dictionary != null)
				{
					string text2 = dictionary.Keys.FirstOrDefault((string k) => string.Equals(k, folderName, StringComparison.OrdinalIgnoreCase));
					if (text2 != null)
					{
						int count = dictionary[text2].Count;
						dictionary[text2].Clear();
						Plugin.Log.LogInfo((object)$"[Override] {text}/{folderName}: 내장 음악 {count}개 제거됨");
					}
				}
			}
		}

		private static IEnumerator CollectAudioFilesAsync(string path, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict)
		{
			string[] files = Directory.GetFiles(path);
			foreach (string file in files)
			{
				yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadExternalClipAsync(file, singleDict));
			}
			string[] directories = Directory.GetDirectories(path);
			foreach (string folder in directories)
			{
				string folderName = Path.GetFileName(folder);
				folderDict[folderName] = new Dictionary<string, AudioClip>();
				string[] files2 = Directory.GetFiles(folder);
				foreach (string file2 in files2)
				{
					yield return ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadExternalClipAsync(file2, folderDict[folderName]));
				}
			}
		}

		private static IEnumerator LoadExternalClipAsync(string path, Dictionary<string, AudioClip> dict)
		{
			if (path.EndsWith(".txt") || !path.Contains("."))
			{
				yield break;
			}
			string extension = Path.GetExtension(path).ToLower();
			if (extension != ".ogg" && extension != ".wav" && extension != ".mp3")
			{
				yield break;
			}
			string uri = new Uri(path).AbsoluteUri;
			AudioType audioType = GetAudioType(extension);
			UnityWebRequest www = UnityWebRequestMultimedia.GetAudioClip(uri, audioType);
			try
			{
				DownloadHandlerAudioClip handler = (DownloadHandlerAudioClip)www.downloadHandler;
				handler.streamAudio = false;
				handler.compressed = false;
				www.timeout = 15;
				yield return www.SendWebRequest();
				if ((int)www.result == 1)
				{
					AudioClip clip = ((handler != null) ? handler.audioClip : null);
					if ((Object)(object)clip != (Object)null && clip.length > 0f && clip.samples > 0)
					{
						string clipName = (((Object)clip).name = Path.GetFileNameWithoutExtension(path));
						dict[clipName] = clip;
					}
					else
					{
						Plugin.Log.LogWarning((object)("Invalid external clip: " + path));
					}
				}
				else
				{
					Plugin.Log.LogWarning((object)("Failed to load external: " + path + " - " + www.error));
				}
			}
			finally
			{
				((IDisposable)www)?.Dispose();
			}
		}

		private static (string folderName, string clipName) ParseResourcePath(string fileName)
		{
			string item = null;
			string item2 = null;
			if (fileName.Contains("\\"))
			{
				string[] array = fileName.Split('\\');
				if (array.Length >= 2)
				{
					item = array[0];
					item2 = Path.GetFileNameWithoutExtension(array[^1]);
				}
			}
			else
			{
				string[] array2 = fileName.Split('.');
				if (array2.Length >= 3)
				{
					item = array2[0];
					item2 = string.Join(".", array2.Skip(1).Take(array2.Length - 2));
				}
				else if (array2.Length >= 2)
				{
					item = null;
					item2 = array2[0];
				}
			}
			return (folderName: item, clipName: item2);
		}

		private static string DetectAudioFormat(byte[] audioData)
		{
			if (audioData.Length < 4)
			{
				return ".ogg";
			}
			if (audioData[0] == 82 && audioData[1] == 73 && audioData[2] == 70 && audioData[3] == 70)
			{
				return ".wav";
			}
			if ((audioData[0] == 73 && audioData[1] == 68 && audioData[2] == 51) || (audioData[0] == byte.MaxValue && (audioData[1] & 0xE0) == 224))
			{
				return ".mp3";
			}
			return ".ogg";
		}

		private static AudioType GetAudioType(string extension)
		{
			//IL_0023: 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_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!(extension == ".wav"))
			{
				if (extension == ".mp3")
				{
					return (AudioType)13;
				}
				return (AudioType)14;
			}
			return (AudioType)20;
		}
	}
	[HarmonyPatch(typeof(MusicMan), "Awake")]
	public static class MusicMan_Awake_Patch
	{
		public static void Apply(MusicMan instance)
		{
			for (int i = 0; i < instance.m_music.Count; i++)
			{
				NamedMusic instance2 = instance.m_music[i];
				string value = ReflectionCache.GetValue<string>(instance2, "m_name");
				if (Plugin.CustomMusicList.ContainsKey(value))
				{
					AudioClip[] value2 = Plugin.CustomMusicList[value].Values.ToArray();
					ReflectionCache.SetValue(instance2, "m_clips", value2);
					ReflectionCache.SetValue(instance2, "m_resume", false);
					continue;
				}
				string text = StringMatchCache.FindMatch(value);
				if (text != null && Plugin.CustomMusicList.ContainsKey(text))
				{
					AudioClip[] value3 = Plugin.CustomMusicList[text].Values.ToArray();
					ReflectionCache.SetValue(instance2, "m_clips", value3);
					ReflectionCache.SetValue(instance2, "m_resume", false);
					continue;
				}
				AudioClip[] value4 = ReflectionCache.GetValue<AudioClip[]>(instance2, "m_clips");
				if (value4 == null)
				{
					continue;
				}
				for (int j = 0; j < value4.Length; j++)
				{
					if ((Object)(object)value4[j] != (Object)null && Plugin.CustomMusic.ContainsKey(((Object)value4[j]).name))
					{
						value4[j] = Plugin.CustomMusic[((Object)value4[j]).name];
					}
				}
			}
		}

		public static void ApplyBossMusic(MusicMan instance)
		{
			for (int i = 0; i < instance.m_music.Count; i++)
			{
				NamedMusic instance2 = instance.m_music[i];
				string value = ReflectionCache.GetValue<string>(instance2, "m_name");
				if (value != null)
				{
					string text = (Plugin.CustomMusicList.ContainsKey(value) ? value : StringMatchCache.FindMatch(value));
					if (text != null && text.StartsWith("Boss_") && Plugin.CustomMusicList.ContainsKey(text))
					{
						AudioClip[] value2 = Plugin.CustomMusicList[text].Values.ToArray();
						ReflectionCache.SetValue(instance2, "m_clips", value2);
						ReflectionCache.SetValue(instance2, "m_resume", false);
					}
				}
			}
		}

		private static void Postfix(MusicMan __instance)
		{
			if (Plugin.ModEnabled.Value)
			{
				Apply(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "UpdateMusic")]
	public static class MusicMan_UpdateMusic_Patch
	{
		private static void Prefix(ref object ___m_queuedMusic, object ___m_currentMusic, AudioSource ___m_musicSource)
		{
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Invalid comparison between Unknown and I4
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Invalid comparison between Unknown and I4
			if (!Plugin.ModEnabled.Value)
			{
				return;
			}
			if (___m_queuedMusic != null)
			{
				ReflectionCache.SetValue(___m_queuedMusic, "m_volume", Plugin.MusicVolume.Value);
			}
			if ((Object)(object)___m_musicSource != (Object)null && ___m_musicSource.loop)
			{
				if (!___m_musicSource.isPlaying)
				{
					___m_musicSource.loop = false;
				}
				else if ((Object)(object)___m_musicSource.clip != (Object)null && (int)___m_musicSource.clip.loadState == 2)
				{
					float num = ___m_musicSource.clip.length - ___m_musicSource.time;
					if (num < 1.5f)
					{
						___m_musicSource.loop = false;
					}
				}
			}
			if (___m_currentMusic != null)
			{
				ReflectionCache.SetValue(___m_currentMusic, "m_resume", false);
			}
			if (___m_queuedMusic != null)
			{
				ReflectionCache.SetValue(___m_queuedMusic, "m_resume", false);
			}
			if ((Object)(object)___m_musicSource != (Object)null && (Object)(object)___m_musicSource.clip != (Object)null && (int)___m_musicSource.clip.loadState == 2 && ___m_musicSource.time >= ___m_musicSource.clip.length)
			{
				___m_musicSource.time = 0f;
			}
		}

		private static void Postfix(AudioSource ___m_musicSource)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Invalid comparison between Unknown and I4
			if (Plugin.ModEnabled.Value && (Object)(object)___m_musicSource != (Object)null && (Object)(object)___m_musicSource.clip != (Object)null && (int)___m_musicSource.clip.loadState == 2 && ___m_musicSource.clip.length > 0f && ___m_musicSource.time >= ___m_musicSource.clip.length)
			{
				___m_musicSource.time = 0f;
			}
		}
	}
	[HarmonyPatch(typeof(MusicLocation), "Awake")]
	public static class MusicLocation_Awake_Patch
	{
		private static void Postfix(ref AudioSource ___m_audioSource, ref float ___m_baseVolume)
		{
			if (!Plugin.ModEnabled.Value || (Object)(object)___m_audioSource == (Object)null || (Object)(object)___m_audioSource.clip == (Object)null)
			{
				return;
			}
			string name = ((Object)___m_audioSource.clip).name;
			if (!Plugin.CustomMusic.ContainsKey(name))
			{
				return;
			}
			AudioClip val = Plugin.CustomMusic[name];
			if (!((Object)(object)val == (Object)null) && !(val.length <= 0f))
			{
				bool isPlaying = ___m_audioSource.isPlaying;
				if (isPlaying)
				{
					___m_audioSource.Stop();
				}
				___m_audioSource.clip = val;
				___m_audioSource.time = 0f;
				___m_baseVolume *= Plugin.LocationVolMultiplier.Value;
				if (isPlaying)
				{
					___m_audioSource.Play();
				}
			}
		}
	}
	[HarmonyPatch(typeof(AudioMan), "Awake")]
	public static class AudioMan_Awake_Patch
	{
		private static void Postfix(AudioMan __instance, IList ___m_randomAmbients, AudioSource ___m_oceanAmbientSource, AudioSource ___m_windLoopSource)
		{
			if (Plugin.ModEnabled.Value)
			{
				for (int i = 0; i < ___m_randomAmbients.Count; i++)
				{
					object obj = ___m_randomAmbients[i];
					string value = ReflectionCache.GetValue<string>(obj, "m_name");
					ReplaceAmbientClips(obj, "m_randomAmbientClips");
					ReplaceAmbientClips(obj, "m_randomAmbientClipsDay");
					ReplaceAmbientClips(obj, "m_randomAmbientClipsNight");
					ReplaceAmbientList(obj, value, "_day", "m_randomAmbientClipsDay");
					ReplaceAmbientList(obj, value, "_night", "m_randomAmbientClipsNight");
					ReplaceAmbientList(obj, value, "", "m_randomAmbientClips");
				}
				if (Plugin.CustomAmbient.ContainsKey("ocean"))
				{
					___m_oceanAmbientSource.clip = Plugin.CustomAmbient["ocean"];
				}
				if (Plugin.CustomAmbient.ContainsKey("wind"))
				{
					___m_windLoopSource.clip = Plugin.CustomAmbient["wind"];
				}
			}
		}

		private static void ReplaceAmbientClips(object ambient, string fieldName)
		{
			IList value = ReflectionCache.GetValue<IList>(ambient, fieldName);
			if (value == null)
			{
				return;
			}
			for (int i = 0; i < value.Count; i++)
			{
				object? obj = value[i];
				AudioClip val = (AudioClip)((obj is AudioClip) ? obj : null);
				if ((Object)(object)val != (Object)null && Plugin.CustomAmbient.ContainsKey(((Object)val).name))
				{
					value[i] = Plugin.CustomAmbient[((Object)val).name];
				}
			}
		}

		private static void ReplaceAmbientList(object ambient, string ambientName, string suffix, string fieldName)
		{
			string key = ambientName + suffix;
			if (!Plugin.CustomAmbientList.ContainsKey(key))
			{
				return;
			}
			IList value = ReflectionCache.GetValue<IList>(ambient, fieldName);
			if (value == null)
			{
				return;
			}
			List<AudioClip> list = Plugin.CustomAmbientList[key].Values.ToList();
			value.Clear();
			foreach (AudioClip item in list)
			{
				value.Add(item);
			}
		}
	}
	[HarmonyPatch(typeof(AudioMan), "QueueAmbientLoop")]
	public static class AudioMan_QueueAmbientLoop_Patch
	{
		private static void Prefix(ref float ___m_queuedAmbientVol, ref float ___m_ambientVol, ref float vol)
		{
			if (Plugin.ModEnabled.Value)
			{
				vol = Plugin.AmbientVolume.Value;
				___m_ambientVol = Plugin.AmbientVolume.Value;
				___m_queuedAmbientVol = Plugin.AmbientVolume.Value;
			}
		}
	}
	[HarmonyPatch(typeof(ZSFX), "Awake")]
	public static class ZSFX_Awake_Patch
	{
		private static void Postfix(ZSFX __instance)
		{
			if (!Plugin.ModEnabled.Value)
			{
				return;
			}
			string zSFXName = AudioUtils.GetZSFXName(__instance);
			if (Plugin.CustomSFXList.TryGetValue(zSFXName, out var value))
			{
				__instance.m_audioClips = (from x in value
					orderby x.Key
					select x.Value).ToArray();
			}
			else
			{
				if (__instance.m_audioClips == null)
				{
					return;
				}
				for (int num = 0; num < __instance.m_audioClips.Length; num++)
				{
					if ((Object)(object)__instance.m_audioClips[num] != (Object)null && Plugin.CustomSFX.ContainsKey(((Object)__instance.m_audioClips[num]).name))
					{
						__instance.m_audioClips[num] = Plugin.CustomSFX[((Object)__instance.m_audioClips[num]).name];
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(TeleportWorld), "Awake")]
	public static class TeleportWorld_Awake_Patch
	{
		private static void Postfix(TeleportWorld __instance)
		{
			if (!Plugin.ModEnabled.Value || !Plugin.CustomSFX.ContainsKey("portal"))
			{
				return;
			}
			AudioSource componentInChildren = ((Component)__instance).GetComponentInChildren<AudioSource>();
			if (!((Object)(object)componentInChildren != (Object)null))
			{
				return;
			}
			AudioClip val = Plugin.CustomSFX["portal"];
			if (!((Object)(object)val == (Object)null) && !(val.length <= 0f))
			{
				bool isPlaying = componentInChildren.isPlaying;
				componentInChildren.Stop();
				componentInChildren.clip = val;
				componentInChildren.time = 0f;
				if (isPlaying || componentInChildren.playOnAwake)
				{
					componentInChildren.Play();
				}
			}
		}
	}
	[HarmonyPatch(typeof(Fireplace), "Start")]
	public static class Fireplace_Start_Patch
	{
		private static void Postfix(Fireplace __instance)
		{
			if (!Plugin.ModEnabled.Value)
			{
				return;
			}
			string name = ((Object)__instance).name;
			AudioSource val = null;
			AudioClip val2 = null;
			if (name.Contains("groundtorch") && Plugin.CustomSFX.ContainsKey("groundtorch"))
			{
				GameObject enabledObject = __instance.m_enabledObject;
				val = ((enabledObject != null) ? enabledObject.GetComponentInChildren<AudioSource>() : null);
				val2 = Plugin.CustomSFX["groundtorch"];
			}
			else if (name.Contains("walltorch") && Plugin.CustomSFX.ContainsKey("walltorch"))
			{
				GameObject enabledObjectHigh = __instance.m_enabledObjectHigh;
				val = ((enabledObjectHigh != null) ? enabledObjectHigh.GetComponentInChildren<AudioSource>() : null);
				if ((Object)(object)val == (Object)null)
				{
					GameObject enabledObject2 = __instance.m_enabledObject;
					val = ((enabledObject2 != null) ? enabledObject2.GetComponentInChildren<AudioSource>() : null);
				}
				val2 = Plugin.CustomSFX["walltorch"];
			}
			else if (name.Contains("fire_pit") && Plugin.CustomSFX.ContainsKey("fire_pit"))
			{
				GameObject enabledObjectHigh2 = __instance.m_enabledObjectHigh;
				val = ((enabledObjectHigh2 != null) ? enabledObjectHigh2.GetComponentInChildren<AudioSource>() : null);
				val2 = Plugin.CustomSFX["fire_pit"];
			}
			else if (name.Contains("bonfire") && Plugin.CustomSFX.ContainsKey("bonfire"))
			{
				GameObject enabledObjectHigh3 = __instance.m_enabledObjectHigh;
				val = ((enabledObjectHigh3 != null) ? enabledObjectHigh3.GetComponentInChildren<AudioSource>() : null);
				val2 = Plugin.CustomSFX["bonfire"];
			}
			else if (name.Contains("hearth") && Plugin.CustomSFX.ContainsKey("hearth"))
			{
				GameObject enabledObjectHigh4 = __instance.m_enabledObjectHigh;
				val = ((enabledObjectHigh4 != null) ? enabledObjectHigh4.GetComponentInChildren<AudioSource>() : null);
				val2 = Plugin.CustomSFX["hearth"];
			}
			if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null && val2.length > 0f)
			{
				SafeReplaceClip(val, val2);
			}
		}

		private static void SafeReplaceClip(AudioSource source, AudioClip newClip)
		{
			bool isPlaying = source.isPlaying;
			source.Stop();
			source.clip = newClip;
			source.time = 0f;
			if (isPlaying || source.playOnAwake)
			{
				source.Play();
			}
		}
	}
	[HarmonyPatch(typeof(EnvMan), "Awake")]
	public static class EnvMan_Awake_Patch
	{
		private static void Postfix(EnvMan __instance)
		{
			if (!Plugin.ModEnabled.Value)
			{
				return;
			}
			for (int i = 0; i < __instance.m_environments.Count; i++)
			{
				string name = __instance.m_environments[i].m_name;
				foreach (string key in Plugin.CustomMusicList.Keys)
				{
					string text = name.ToLower();
					string text2 = key.ToLower();
					if (text == text2 || text.Contains(text2) || text2.Contains(text))
					{
						string name2 = __instance.m_environments[i].m_name;
						__instance.m_environments[i].m_name = key;
						AddMusicToEnvironment(__instance, i, "");
						__instance.m_environments[i].m_name = name2;
						break;
					}
				}
				AddMusicToEnvironment(__instance, i, "Morning");
				AddMusicToEnvironment(__instance, i, "Day");
				AddMusicToEnvironment(__instance, i, "Evening");
				AddMusicToEnvironment(__instance, i, "Night");
			}
		}

		private static void AddMusicToEnvironment(EnvMan envMan, int index, string timeOfDay)
		{
			string musicName = envMan.m_environments[index].m_name + timeOfDay;
			if (!Plugin.CustomMusicList.ContainsKey(musicName))
			{
				musicName = Plugin.CustomMusicList.Keys.FirstOrDefault((string k) => string.Equals(k, musicName, StringComparison.OrdinalIgnoreCase));
				if (musicName == null)
				{
					return;
				}
			}
			switch (timeOfDay)
			{
			case "Morning":
				envMan.m_environments[index].m_musicMorning = musicName;
				break;
			case "Day":
				envMan.m_environments[index].m_musicDay = musicName;
				break;
			case "Evening":
				envMan.m_environments[index].m_musicEvening = musicName;
				break;
			case "Night":
				envMan.m_environments[index].m_musicNight = musicName;
				break;
			}
			Type type = ((object)MusicMan.instance.m_music[0]).GetType();
			object obj = Activator.CreateInstance(type);
			ReflectionCache.SetValue(obj, "m_name", musicName);
			ReflectionCache.SetValue(obj, "m_clips", (from x in Plugin.CustomMusicList[musicName]
				orderby x.Key
				select x.Value).ToArray());
			ReflectionCache.SetValue(obj, "m_loop", true);
			ReflectionCache.SetValue(obj, "m_ambientMusic", true);
			ReflectionCache.SetValue(obj, "m_resume", false);
			((IList)MusicMan.instance.m_music).Add(obj);
		}
	}
	[HarmonyPatch(typeof(Terminal), "InputText")]
	public static class Terminal_InputText_Patch
	{
		private static bool Prefix(Terminal __instance)
		{
			if (!Plugin.ModEnabled.Value)
			{
				return true;
			}
			FieldInfo field = ((object)__instance).GetType().GetField("m_input");
			if (field == null)
			{
				return true;
			}
			object value = field.GetValue(__instance);
			if (value == null)
			{
				return true;
			}
			PropertyInfo property = value.GetType().GetProperty("text");
			if (property == null)
			{
				return true;
			}
			string text = (string)property.GetValue(value);
			switch (text.ToLower())
			{
			case "captainaudio reset":
				((BaseUnityPlugin)Plugin.instance).Config.Reload();
				((BaseUnityPlugin)Plugin.instance).Config.Save();
				AddOutput(__instance, text);
				AddOutput(__instance, "Captain Audio config reloaded");
				return false;
			case "captainaudio music":
				AddOutput(__instance, text);
				if ((Object)(object)EnvMan.instance != (Object)null)
				{
					Player localPlayer = Player.m_localPlayer;
					string arg = ((localPlayer != null && localPlayer.IsSafeInHome()) ? "home" : EnvMan.instance.GetAmbientMusic());
					AddOutput(__instance, $"Current: {arg} | Folders: {Plugin.CustomMusicList.Count} | Clips: {Plugin.CustomMusic.Count}");
				}
				return false;
			case "captainaudio env":
				AddOutput(__instance, text);
				if ((Object)(object)EnvMan.instance != (Object)null)
				{
					AddOutput(__instance, "Environment: " + EnvMan.instance.GetCurrentEnvironment().m_name);
				}
				else
				{
					AddOutput(__instance, "Must be called in-game");
				}
				return false;
			case "captainaudio bosses":
				AddOutput(__instance, text);
				if ((Object)(object)MusicMan.instance != (Object)null)
				{
					StringBuilder stringBuilder = new StringBuilder("Boss entries: ");
					bool flag = false;
					foreach (NamedMusic item in MusicMan.instance.m_music)
					{
						string value2 = ReflectionCache.GetValue<string>(item, "m_name");
						if (value2 != null && value2.ToLower().Contains("boss"))
						{
							stringBuilder.Append(value2).Append(" | ");
							flag = true;
						}
					}
					AddOutput(__instance, flag ? stringBuilder.ToString() : "No boss entries found in MusicMan");
				}
				else
				{
					AddOutput(__instance, "Must be called in-game");
				}
				return false;
			default:
				return true;
			}
		}

		private static void AddOutput(Terminal terminal, string text)
		{
			Traverse.Create((object)terminal).Method("AddString", new object[1] { text }).GetValue();
		}
	}
	[BepInPlugin("captain.CaptainAudio", "Captain Audio", "1.3.54")]
	public class Plugin : BaseUnityPlugin
	{
		public static Plugin instance;

		public static ConfigEntry<bool> ModEnabled;

		public static ConfigEntry<float> MusicVolume;

		public static ConfigEntry<float> AmbientVolume;

		public static ConfigEntry<float> LocationVolMultiplier;

		public static ConfigEntry<int> LoadRetryCount;

		public static ConfigEntry<bool> EnableFallback;

		public static ConfigEntry<bool> GetMusicFromConfig;

		public static Dictionary<string, AudioClip> CustomMusic = new Dictionary<string, AudioClip>();

		public static Dictionary<string, Dictionary<string, AudioClip>> CustomMusicList = new Dictionary<string, Dictionary<string, AudioClip>>();

		public static Dictionary<string, AudioClip> CustomAmbient = new Dictionary<string, AudioClip>();

		public static Dictionary<string, Dictionary<string, AudioClip>> CustomAmbientList = new Dictionary<string, Dictionary<string, AudioClip>>();

		public static Dictionary<string, AudioClip> CustomSFX = new Dictionary<string, AudioClip>();

		public static Dictionary<string, Dictionary<string, AudioClip>> CustomSFXList = new Dictionary<string, Dictionary<string, AudioClip>>();

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable this mod");
			MusicVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MusicVolume", 0.6f, "Music volume (0.0 - 1.0)");
			AmbientVolume = ((BaseUnityPlugin)this).Config.Bind<float>("General", "AmbientVolume", 0.3f, "Ambient volume (0.0 - 1.0)");
			LocationVolMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("General", "LocationVolumeMultiplier", 5f, "Location music volume multiplier");
			LoadRetryCount = ((BaseUnityPlugin)this).Config.Bind<int>("Advanced", "LoadRetryCount", 3, "Number of retries when audio loading fails");
			EnableFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Advanced", "EnableFallback", true, "Use fallback to vanilla music on load failure");
			GetMusicFromConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Advanced", "GetMusicFromConfig", false, "false: load music from plugins/korCaptain-CaptainAudio/music. true: load from config/CaptainAudio/music (created automatically; falls back to the plugins folder if it has no audio files)");
			if (!ModEnabled.Value)
			{
				return;
			}
			try
			{
				Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
				((MonoBehaviour)this).StartCoroutine(AudioLoader.InitializeAsync());
				try
				{
					Type type = typeof(BaseUnityPlugin).Assembly.GetType("BepInEx.ConsoleManager");
					MethodInfo methodInfo = type?.GetMethod("SetConsoleColor", BindingFlags.Static | BindingFlags.Public);
					TextWriter textWriter = type?.GetProperty("StandardOutStream", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) as TextWriter;
					if (methodInfo != null && textWriter != null)
					{
						methodInfo.Invoke(null, new object[1] { ConsoleColor.Cyan });
						textWriter.WriteLine("[CaptainAudio] CaptainAudio 1.3.54 Loaded!");
						methodInfo.Invoke(null, new object[1] { ConsoleColor.Gray });
					}
					else
					{
						Log.LogMessage((object)"[CaptainAudio] CaptainAudio 1.3.54 Loaded!");
					}
				}
				catch
				{
					Log.LogMessage((object)"[CaptainAudio] CaptainAudio 1.3.54 Loaded!");
				}
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Critical error: " + ex.Message));
			}
		}
	}
	public static class AudioUtils
	{
		public static string GetZSFXName(ZSFX zsfx)
		{
			string name = ((Object)zsfx).name;
			char[] anyOf = new char[2] { '(', ' ' };
			int num = name.IndexOfAny(anyOf);
			return (num != -1) ? name.Remove(num) : name;
		}

		public static void SafeSetVolume(AudioSource source, float volume)
		{
			if ((Object)(object)source != (Object)null)
			{
				source.volume = Mathf.Clamp01(volume);
			}
		}
	}
	public static class ReflectionCache
	{
		private static readonly Dictionary<string, FieldInfo> _fieldCache = new Dictionary<string, FieldInfo>();

		private static readonly Dictionary<string, PropertyInfo> _propertyCache = new Dictionary<string, PropertyInfo>();

		public static FieldInfo GetField(Type type, string fieldName)
		{
			string key = type.FullName + "." + fieldName;
			if (!_fieldCache.ContainsKey(key))
			{
				_fieldCache[key] = type.GetField(fieldName);
			}
			return _fieldCache[key];
		}

		public static void SetValue(object instance, string fieldName, object value)
		{
			GetField(instance.GetType(), fieldName)?.SetValue(instance, value);
		}

		public static T GetValue<T>(object instance, string fieldName)
		{
			FieldInfo field = GetField(instance.GetType(), fieldName);
			return (field != null) ? ((T)field.GetValue(instance)) : default(T);
		}
	}
	public static class StringMatchCache
	{
		private static readonly Dictionary<string, string> _lowerCaseCache = new Dictionary<string, string>();

		private static readonly Dictionary<string, string> _musicMappingCache = new Dictionary<string, string>();

		private static bool _initialized = false;

		public static string GetLowerCase(string input)
		{
			if (string.IsNullOrEmpty(input))
			{
				return string.Empty;
			}
			if (!_lowerCaseCache.ContainsKey(input))
			{
				_lowerCaseCache[input] = input.ToLower();
			}
			return _lowerCaseCache[input];
		}

		public static void Initialize()
		{
			if (!_initialized)
			{
				AddMapping("Boss_TheElder", "boss_gdking", "gdking");
				AddMapping("Boss_Yagluth", "boss_goblinking", "goblinking");
				AddMapping("Boss_TheQueen", "boss_queen", "queen");
				_initialized = true;
			}
		}

		private static void AddMapping(string target, params string[] sources)
		{
			foreach (string key in sources)
			{
				_musicMappingCache[key] = target;
			}
		}

		public static string FindMatch(string musicName)
		{
			if (string.IsNullOrEmpty(musicName))
			{
				return null;
			}
			if (Plugin.CustomMusicList.ContainsKey(musicName))
			{
				return musicName;
			}
			if (_musicMappingCache.ContainsKey(musicName))
			{
				return _musicMappingCache[musicName];
			}
			string lowerCase = GetLowerCase(musicName);
			foreach (string key in Plugin.CustomMusicList.Keys)
			{
				string lowerCase2 = GetLowerCase(key);
				if (lowerCase.Contains(lowerCase2) || lowerCase2.Contains(lowerCase))
				{
					_musicMappingCache[musicName] = key;
					return key;
				}
			}
			return null;
		}
	}
}