Decompiled source of CaptainAudio v1.2.2

CaptainAudio.dll

Decompiled 21 hours 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.Threading;
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.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.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.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;
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass5_0
		{
			public string resourcePrefix;

			internal bool <LoadEmbeddedResourcesParallel>b__0(string name)
			{
				return name.StartsWith(resourcePrefix);
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass6_0
		{
			public AudioClip clip;

			public bool success;

			internal void <LoadSingleClipWithRetry>b__0(AudioClip loadedClip)
			{
				clip = loadedClip;
				success = (Object)(object)loadedClip != (Object)null;
			}
		}

		[CompilerGenerated]
		private sealed class <InitializeAsync>d__4 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <InitializeAsync>d__4(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;
					if (_isInitialized)
					{
						return false;
					}
					Plugin.CustomMusic.Clear();
					Plugin.CustomAmbient.Clear();
					Plugin.CustomSFX.Clear();
					Plugin.CustomMusicList.Clear();
					Plugin.CustomAmbientList.Clear();
					Plugin.CustomSFXList.Clear();
					<>2__current = LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.Music", Plugin.CustomMusic, Plugin.CustomMusicList);
					<>1__state = 1;
					return true;
				case 1:
					<>1__state = -1;
					<>2__current = LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.Ambient", Plugin.CustomAmbient, Plugin.CustomAmbientList);
					<>1__state = 2;
					return true;
				case 2:
					<>1__state = -1;
					<>2__current = LoadEmbeddedResourcesParallel("CaptainAudio.asset.Resources.SFX", Plugin.CustomSFX, Plugin.CustomSFXList);
					<>1__state = 3;
					return true;
				case 3:
					<>1__state = -1;
					LoadExternalAudioFiles();
					StringMatchCache.Initialize();
					_isInitialized = true;
					Plugin.Log.LogInfo((object)$"<color=#00BFFF>Loaded: {Plugin.CustomMusicList.Count} folders, {Plugin.CustomMusic.Count} clips</color>");
					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 <LoadAudioClipFromBytes>d__7 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public byte[] audioData;

			public string clipName;

			public Action<AudioClip> onComplete;

			private string <tempPath>5__2;

			private UnityWebRequest <www>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadAudioClipFromBytes>d__7(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				int num = <>1__state;
				if (num == -3 || num == 1)
				{
					try
					{
					}
					finally
					{
						<>m__Finally1();
					}
				}
				<tempPath>5__2 = null;
				<www>5__3 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0129: Unknown result type (might be due to invalid IL or missing references)
				//IL_012f: Invalid comparison between Unknown and I4
				//IL_0095: Unknown result type (might be due to invalid IL or missing references)
				//IL_009a: Unknown result type (might be due to invalid IL or missing references)
				//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
				//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
				//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
				//IL_0165: Unknown result type (might be due to invalid IL or missing references)
				//IL_016b: Invalid comparison between Unknown and I4
				//IL_020a: Unknown result type (might be due to invalid IL or missing references)
				try
				{
					switch (<>1__state)
					{
					default:
						return false;
					case 0:
					{
						<>1__state = -1;
						string text = DetectAudioFormat(audioData);
						<tempPath>5__2 = Path.Combine(Application.temporaryCachePath, $"temp_audio_{clipName}_{Guid.NewGuid()}{text}");
						try
						{
							Directory.CreateDirectory(Path.GetDirectoryName(<tempPath>5__2));
							File.WriteAllBytes(<tempPath>5__2, audioData);
						}
						catch
						{
							onComplete?.Invoke(null);
							return false;
						}
						AudioType audioType = GetAudioType(text);
						string text2 = "file:///" + <tempPath>5__2.Replace("\\", "/");
						<www>5__3 = UnityWebRequestMultimedia.GetAudioClip(text2, audioType);
						<>1__state = -3;
						DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)<www>5__3.downloadHandler;
						val.streamAudio = false;
						val.compressed = false;
						<www>5__3.timeout = 15;
						<>2__current = <www>5__3.SendWebRequest();
						<>1__state = 1;
						return true;
					}
					case 1:
						<>1__state = -3;
						if ((int)<www>5__3.result == 1)
						{
							AudioClip content = DownloadHandlerAudioClip.GetContent(<www>5__3);
							if ((Object)(object)content != (Object)null && content.length > 0f && content.samples > 0 && (int)content.loadState == 2)
							{
								((Object)content).name = clipName;
								onComplete?.Invoke(content);
							}
							else
							{
								Plugin.Log.LogWarning((object)$"Invalid clip: {clipName} (length={((content != null) ? new float?(content.length) : null)}, samples={((content != null) ? new int?(content.samples) : null)}, state={((content != null) ? new AudioDataLoadState?(content.loadState) : null)})");
								onComplete?.Invoke(null);
							}
						}
						else
						{
							Plugin.Log.LogWarning((object)("Failed to load: " + clipName + " - " + <www>5__3.error));
							onComplete?.Invoke(null);
						}
						<>m__Finally1();
						<www>5__3 = null;
						if (File.Exists(<tempPath>5__2))
						{
							try
							{
								File.Delete(<tempPath>5__2);
							}
							catch
							{
							}
						}
						return false;
					}
				}
				catch
				{
					//try-fault
					((IDisposable)this).Dispose();
					throw;
				}
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			private void <>m__Finally1()
			{
				<>1__state = -1;
				if (<www>5__3 != null)
				{
					((IDisposable)<www>5__3).Dispose();
				}
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		[CompilerGenerated]
		private sealed class <LoadEmbeddedResourcesParallel>d__5 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public string resourcePrefix;

			public Dictionary<string, Dictionary<string, AudioClip>> folderDict;

			public Dictionary<string, AudioClip> singleDict;

			private List<LoadTask> <tasks>5__2;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadEmbeddedResourcesParallel>d__5(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<tasks>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
				{
					<>1__state = -1;
					<>c__DisplayClass5_0 CS$<>8__locals0 = new <>c__DisplayClass5_0
					{
						resourcePrefix = resourcePrefix
					};
					Assembly executingAssembly = Assembly.GetExecutingAssembly();
					string[] array = (from name in executingAssembly.GetManifestResourceNames()
						where name.StartsWith(CS$<>8__locals0.resourcePrefix)
						select name).ToArray();
					if (array.Length == 0)
					{
						return false;
					}
					<tasks>5__2 = new List<LoadTask>();
					string[] array2 = array;
					foreach (string text in array2)
					{
						Stream manifestResourceStream = executingAssembly.GetManifestResourceStream(text);
						if (manifestResourceStream != null)
						{
							byte[] array3 = new byte[manifestResourceStream.Length];
							manifestResourceStream.Read(array3, 0, array3.Length);
							manifestResourceStream.Dispose();
							var (folderName, text2) = ParseResourcePath(text.Substring(CS$<>8__locals0.resourcePrefix.Length + 1));
							if (!string.IsNullOrEmpty(text2))
							{
								LoadTask loadTask = new LoadTask
								{
									FolderName = folderName,
									ClipName = text2,
									AudioData = array3,
									IsCompleted = false,
									LoadedClip = null
								};
								<tasks>5__2.Add(loadTask);
								((MonoBehaviour)Plugin.instance).StartCoroutine(LoadSingleClipWithRetry(loadTask));
							}
						}
					}
					break;
				}
				case 1:
					<>1__state = -1;
					break;
				}
				if (<tasks>5__2.Any((LoadTask t) => !t.IsCompleted))
				{
					<>2__current = null;
					<>1__state = 1;
					return true;
				}
				foreach (LoadTask item in <tasks>5__2)
				{
					if ((Object)(object)item.LoadedClip == (Object)null)
					{
						continue;
					}
					if (!string.IsNullOrEmpty(item.FolderName))
					{
						if (!folderDict.ContainsKey(item.FolderName))
						{
							folderDict[item.FolderName] = new Dictionary<string, AudioClip>();
						}
						folderDict[item.FolderName][item.ClipName] = item.LoadedClip;
					}
					else
					{
						singleDict[item.ClipName] = item.LoadedClip;
					}
				}
				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 <LoadSingleClipWithRetry>d__6 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public LoadTask task;

			private <>c__DisplayClass6_0 <>8__1;

			private int <retryCount>5__2;

			private int <attempt>5__3;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <LoadSingleClipWithRetry>d__6(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>8__1 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f4: Expected O, but got Unknown
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<retryCount>5__2 = Plugin.LoadRetryCount.Value;
					<attempt>5__3 = 0;
					break;
				case 1:
					<>1__state = -1;
					if (<>8__1.success)
					{
						task.LoadedClip = <>8__1.clip;
						task.IsCompleted = true;
						return false;
					}
					if (<attempt>5__3 < <retryCount>5__2 - 1)
					{
						<>2__current = (object)new WaitForSeconds(0.5f);
						<>1__state = 2;
						return true;
					}
					goto IL_0104;
				case 2:
					{
						<>1__state = -1;
						goto IL_0104;
					}
					IL_0104:
					<>8__1 = null;
					<attempt>5__3++;
					break;
				}
				if (<attempt>5__3 < <retryCount>5__2)
				{
					<>8__1 = new <>c__DisplayClass6_0();
					<>8__1.success = false;
					<>8__1.clip = null;
					<>2__current = LoadAudioClipFromBytes(task.AudioData, task.ClipName, delegate(AudioClip loadedClip)
					{
						<>8__1.clip = loadedClip;
						<>8__1.success = (Object)(object)loadedClip != (Object)null;
					});
					<>1__state = 1;
					return true;
				}
				task.IsCompleted = true;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		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);

		[IteratorStateMachine(typeof(<InitializeAsync>d__4))]
		public static IEnumerator InitializeAsync()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <InitializeAsync>d__4(0);
		}

		[IteratorStateMachine(typeof(<LoadEmbeddedResourcesParallel>d__5))]
		private static IEnumerator LoadEmbeddedResourcesParallel(string resourcePrefix, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadEmbeddedResourcesParallel>d__5(0)
			{
				resourcePrefix = resourcePrefix,
				singleDict = singleDict,
				folderDict = folderDict
			};
		}

		[IteratorStateMachine(typeof(<LoadSingleClipWithRetry>d__6))]
		private static IEnumerator LoadSingleClipWithRetry(LoadTask task)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadSingleClipWithRetry>d__6(0)
			{
				task = task
			};
		}

		[IteratorStateMachine(typeof(<LoadAudioClipFromBytes>d__7))]
		private static IEnumerator LoadAudioClipFromBytes(byte[] audioData, string clipName, Action<AudioClip> onComplete)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <LoadAudioClipFromBytes>d__7(0)
			{
				audioData = audioData,
				clipName = clipName,
				onComplete = onComplete
			};
		}

		private static void LoadExternalAudioFiles()
		{
			string text = Path.Combine(Paths.PluginPath, "CaptainAudio");
			if (Directory.Exists(text))
			{
				_externalOverrideFolders.Clear();
				ScanExternalFolders(text);
				ApplyExternalOverrides();
				if (Directory.Exists(Path.Combine(text, "Music")))
				{
					CollectAudioFiles(Path.Combine(text, "Music"), Plugin.CustomMusic, Plugin.CustomMusicList);
				}
				if (Directory.Exists(Path.Combine(text, "SFX")))
				{
					CollectAudioFiles(Path.Combine(text, "SFX"), Plugin.CustomSFX, Plugin.CustomSFXList);
				}
				if (Directory.Exists(Path.Combine(text, "Ambient")))
				{
					CollectAudioFiles(Path.Combine(text, "Ambient"), Plugin.CustomAmbient, Plugin.CustomAmbientList);
				}
			}
		}

		private static void ScanExternalFolders(string externalPath)
		{
			string[] array = new string[3] { "Music", "Ambient", "SFX" };
			foreach (string text in array)
			{
				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)("<color=#FFA500>[Override] " + text + "/" + fileName + ": 외부 음악 감지됨 → 내장 음악 대체</color>"));
					}
				}
			}
		}

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

		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)$"<color=#FFA500>[Override] {text}/{folderName}: 내장 음악 {count}개 제거됨</color>");
					}
				}
			}
		}

		private static void CollectAudioFiles(string path, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict)
		{
			string[] files = Directory.GetFiles(path);
			for (int i = 0; i < files.Length; i++)
			{
				LoadExternalClip(files[i], singleDict);
			}
			files = Directory.GetDirectories(path);
			foreach (string path2 in files)
			{
				string fileName = Path.GetFileName(path2);
				folderDict[fileName] = new Dictionary<string, AudioClip>();
				string[] files2 = Directory.GetFiles(path2);
				for (int j = 0; j < files2.Length; j++)
				{
					LoadExternalClip(files2[j], folderDict[fileName]);
				}
			}
		}

		private static void LoadExternalClip(string path, Dictionary<string, AudioClip> dict)
		{
			//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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Expected O, but got Unknown
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Invalid comparison between Unknown and I4
			if (path.EndsWith(".txt") || !path.Contains("."))
			{
				return;
			}
			string text = Path.GetExtension(path).ToLower();
			if (text != ".ogg" && text != ".wav" && text != ".mp3")
			{
				return;
			}
			string text2 = "file:///" + path.Replace("\\", "/");
			AudioType audioType = GetAudioType(text);
			UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(text2, audioType);
			DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)audioClip.downloadHandler;
			val.streamAudio = false;
			val.compressed = false;
			audioClip.SendWebRequest();
			float num = 10f;
			float num2 = 0f;
			while (!audioClip.isDone && num2 < num)
			{
				Thread.Sleep(10);
				num2 += 0.01f;
			}
			if ((int)audioClip.result == 1)
			{
				AudioClip val2 = ((val != null) ? val.audioClip : null);
				if ((Object)(object)val2 != (Object)null && val2.length > 0f && val2.samples > 0)
				{
					string key = (((Object)val2).name = Path.GetFileNameWithoutExtension(path));
					dict[key] = val2;
				}
				else
				{
					Plugin.Log.LogWarning((object)("Invalid external clip: " + path));
				}
			}
			else
			{
				Plugin.Log.LogWarning((object)("Failed to load external: " + path + " - " + audioClip.error));
			}
			audioClip.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 (item, 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)
		{
			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
	{
		private static void Postfix(MusicMan __instance)
		{
			if (!Plugin.ModEnabled.Value)
			{
				return;
			}
			for (int i = 0; i < __instance.m_music.Count; i++)
			{
				NamedMusic instance = __instance.m_music[i];
				string value = ReflectionCache.GetValue<string>(instance, "m_name");
				if (Plugin.CustomMusicList.ContainsKey(value))
				{
					AudioClip[] value2 = Plugin.CustomMusicList[value].Values.ToArray();
					ReflectionCache.SetValue(instance, "m_clips", value2);
					continue;
				}
				string text = StringMatchCache.FindMatch(value);
				if (text != null && Plugin.CustomMusicList.ContainsKey(text))
				{
					AudioClip[] value3 = Plugin.CustomMusicList[text].Values.ToArray();
					ReflectionCache.SetValue(instance, "m_clips", value3);
					continue;
				}
				AudioClip[] value4 = ReflectionCache.GetValue<AudioClip[]>(instance, "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];
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(MusicMan), "UpdateMusic")]
	public static class MusicMan_UpdateMusic_Patch
	{
		private static void Prefix(ref object ___m_queuedMusic, AudioSource ___m_musicSource)
		{
			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 && ___m_musicSource.clip.length - ___m_musicSource.time < 0.5f)
				{
					___m_musicSource.loop = false;
				}
			}
		}
	}
	[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 i = 0; i < __instance.m_audioClips.Length; i++)
				{
					if ((Object)(object)__instance.m_audioClips[i] != (Object)null && Plugin.CustomSFX.ContainsKey(((Object)__instance.m_audioClips[i]).name))
					{
						__instance.m_audioClips[i] = Plugin.CustomSFX[((Object)__instance.m_audioClips[i]).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 text = envMan.m_environments[index].m_name + timeOfDay;
			if (Plugin.CustomMusicList.ContainsKey(text))
			{
				switch (timeOfDay)
				{
				case "Morning":
					envMan.m_environments[index].m_musicMorning = text;
					break;
				case "Day":
					envMan.m_environments[index].m_musicDay = text;
					break;
				case "Evening":
					envMan.m_environments[index].m_musicEvening = text;
					break;
				case "Night":
					envMan.m_environments[index].m_musicNight = text;
					break;
				}
				object obj = Activator.CreateInstance(((object)MusicMan.instance.m_music[0]).GetType());
				ReflectionCache.SetValue(obj, "m_name", text);
				ReflectionCache.SetValue(obj, "m_clips", (from x in Plugin.CustomMusicList[text]
					orderby x.Key
					select x.Value).ToArray());
				ReflectionCache.SetValue(obj, "m_loop", true);
				ReflectionCache.SetValue(obj, "m_ambientMusic", true);
				ReflectionCache.SetValue(obj, "m_resume", true);
				((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, "<color=#00BFFF>Captain Audio config reloaded</color>");
				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, $"<color=#00BFFF>Current: {arg} | Folders: {Plugin.CustomMusicList.Count} | Clips: {Plugin.CustomMusic.Count}</color>");
				}
				return false;
			case "captainaudio env":
				AddOutput(__instance, text);
				if ((Object)(object)EnvMan.instance != (Object)null)
				{
					AddOutput(__instance, "<color=#00BFFF>Environment: " + EnvMan.instance.GetCurrentEnvironment().m_name + "</color>");
				}
				else
				{
					AddOutput(__instance, "<color=#00BFFF>Must be called in-game</color>");
				}
				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.2.2")]
	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 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");
			if (!ModEnabled.Value)
			{
				return;
			}
			try
			{
				Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
				((MonoBehaviour)this).StartCoroutine(AudioLoader.InitializeAsync());
				Log.LogInfo((object)"<color=#00BFFF>Captain Audio v1.2.2 loaded</color>");
			}
			catch (Exception ex)
			{
				Log.LogError((object)("<color=#00BFFF>Critical error: " + ex.Message + "</color>"));
			}
		}
	}
	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);
			if (num == -1)
			{
				return name;
			}
			return name.Remove(num);
		}

		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);
			if (!(field != null))
			{
				return default(T);
			}
			return (T)field.GetValue(instance);
		}
	}
	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("menu", "menu", "start", "intro", "mainmenu", "main_menu", "title");
				AddMapping("meadows", "meadow", "meadows");
				AddMapping("blackforest", "forest", "dark", "blackforest");
				AddMapping("swamp", "swamp");
				_initialized = true;
			}
		}

		private static void AddMapping(string target, params string[] sources)
		{
			foreach (string text in sources)
			{
				_musicMappingCache[text + "->" + target] = 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;
		}
	}
}