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.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 b__0(string name) { return name.StartsWith(resourcePrefix); } } [CompilerGenerated] private sealed class <>c__DisplayClass6_0 { public AudioClip clip; public bool success; internal void b__0(AudioClip loadedClip) { clip = loadedClip; success = (Object)(object)loadedClip != (Object)null; } } [CompilerGenerated] private sealed class d__12 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string path; public Dictionary singleDict; public Dictionary> folderDict; private string[] <>7__wrap1; private int <>7__wrap2; private string 5__4; private string[] <>7__wrap4; private int <>7__wrap5; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__12(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>7__wrap1 = null; 5__4 = null; <>7__wrap4 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>7__wrap1 = Directory.GetFiles(path); <>7__wrap2 = 0; goto IL_0084; case 1: <>1__state = -1; <>7__wrap2++; goto IL_0084; case 2: { <>1__state = -1; <>7__wrap5++; goto IL_0150; } IL_0084: if (<>7__wrap2 < <>7__wrap1.Length) { string text = <>7__wrap1[<>7__wrap2]; <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadExternalClipAsync(text, singleDict)); <>1__state = 1; return true; } <>7__wrap1 = null; <>7__wrap1 = Directory.GetDirectories(path); <>7__wrap2 = 0; goto IL_017c; IL_017c: if (<>7__wrap2 < <>7__wrap1.Length) { string text2 = <>7__wrap1[<>7__wrap2]; 5__4 = Path.GetFileName(text2); folderDict[5__4] = new Dictionary(); <>7__wrap4 = Directory.GetFiles(text2); <>7__wrap5 = 0; goto IL_0150; } <>7__wrap1 = null; return false; IL_0150: if (<>7__wrap5 < <>7__wrap4.Length) { string text3 = <>7__wrap4[<>7__wrap5]; <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadExternalClipAsync(text3, folderDict[5__4])); <>1__state = 2; return true; } <>7__wrap4 = null; 5__4 = null; <>7__wrap2++; goto IL_017c; } } 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 d__4 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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; <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadExternalAudioFilesAsync()); <>1__state = 4; return true; case 4: <>1__state = -1; 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] 보스 음악 재적용 완료"); } 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 d__7 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public byte[] audioData; public string clipName; public Action onComplete; private string 5__2; private UnityWebRequest 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public 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(); } } 5__2 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0120: 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_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Invalid comparison between Unknown and I4 //IL_01fb: 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); 5__2 = Path.Combine(Application.temporaryCachePath, $"temp_audio_{clipName}_{Guid.NewGuid()}{text}"); try { Directory.CreateDirectory(Path.GetDirectoryName(5__2)); File.WriteAllBytes(5__2, audioData); } catch { onComplete?.Invoke(null); return false; } AudioType audioType = GetAudioType(text); string absoluteUri = new Uri(5__2).AbsoluteUri; 5__3 = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, audioType); <>1__state = -3; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)5__3.downloadHandler; val.streamAudio = false; val.compressed = false; 5__3.timeout = 15; <>2__current = 5__3.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)5__3.result == 1) { AudioClip content = DownloadHandlerAudioClip.GetContent(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 + " - " + 5__3.error)); onComplete?.Invoke(null); } <>m__Finally1(); 5__3 = null; if (File.Exists(5__2)) { try { File.Delete(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 (5__3 != null) { ((IDisposable)5__3).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__5 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string resourcePrefix; public Dictionary> folderDict; public Dictionary singleDict; private List 5__2; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 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; } 5__2 = new List(); 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 }; 5__2.Add(loadTask); ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadSingleClipWithRetry(loadTask)); } } } break; } case 1: <>1__state = -1; break; } if (5__2.Any((LoadTask t) => !t.IsCompleted)) { <>2__current = null; <>1__state = 1; return true; } foreach (LoadTask item in 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(); } folderDict[item.FolderName][item.ClipName] = item.LoadedClip; } else { singleDict[item.ClipName] = item.LoadedClip; } } 5__2.Clear(); 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 d__8 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private string 5__2; private string 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__8(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; string path = Path.Combine(Paths.PluginPath, "korCaptain-CaptainAudio", "music"); if (Directory.Exists(path)) { _externalOverrideFolders.Clear(); string[] directories = Directory.GetDirectories(path); foreach (string path2 in directories) { string fileName = Path.GetFileName(path2); if (Directory.GetFiles(path2).Any((string f) => IsValidAudioFile(f))) { _externalOverrideFolders.Add("Music:" + fileName); Plugin.Log.LogInfo((object)("[Override] Music/" + fileName + ": 외부 음악 감지됨 → 내장 음악 대체")); } } ApplyExternalOverrides(); <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(path, Plugin.CustomMusic, Plugin.CustomMusicList)); <>1__state = 1; return true; } string text = Path.Combine(Paths.PluginPath, "CaptainAudio"); if (!Directory.Exists(text)) { return false; } _externalOverrideFolders.Clear(); ScanExternalFolders(text); ApplyExternalOverrides(); string path3 = Path.Combine(text, "Music"); 5__2 = Path.Combine(text, "SFX"); 5__3 = Path.Combine(text, "Ambient"); if (Directory.Exists(path3)) { <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(path3, Plugin.CustomMusic, Plugin.CustomMusicList)); <>1__state = 2; return true; } goto IL_01a5; } case 1: <>1__state = -1; return false; case 2: <>1__state = -1; goto IL_01a5; case 3: <>1__state = -1; goto IL_01e7; case 4: { <>1__state = -1; break; } IL_01a5: if (Directory.Exists(5__2)) { <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(5__2, Plugin.CustomSFX, Plugin.CustomSFXList)); <>1__state = 3; return true; } goto IL_01e7; IL_01e7: if (Directory.Exists(5__3)) { <>2__current = ((MonoBehaviour)Plugin.instance).StartCoroutine(CollectAudioFilesAsync(5__3, Plugin.CustomAmbient, Plugin.CustomAmbientList)); <>1__state = 4; return true; } break; } 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 d__13 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string path; public Dictionary dict; private UnityWebRequest 5__2; private DownloadHandlerAudioClip 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__13(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(); } } 5__2 = null; 5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Invalid comparison between Unknown and I4 //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if (path.EndsWith(".txt") || !path.Contains(".")) { return false; } string text = Path.GetExtension(path).ToLower(); if (text != ".ogg" && text != ".wav" && text != ".mp3") { return false; } string absoluteUri = new Uri(path).AbsoluteUri; AudioType audioType = GetAudioType(text); 5__2 = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, audioType); <>1__state = -3; 5__3 = (DownloadHandlerAudioClip)5__2.downloadHandler; 5__3.streamAudio = false; 5__3.compressed = false; 5__2.timeout = 15; <>2__current = 5__2.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)5__2.result == 1) { DownloadHandlerAudioClip obj = 5__3; AudioClip val = ((obj != null) ? obj.audioClip : null); if ((Object)(object)val != (Object)null && val.length > 0f && val.samples > 0) { string key = (((Object)val).name = Path.GetFileNameWithoutExtension(path)); dict[key] = val; } else { Plugin.Log.LogWarning((object)("Invalid external clip: " + path)); } } else { Plugin.Log.LogWarning((object)("Failed to load external: " + path + " - " + 5__2.error)); } 5__3 = null; <>m__Finally1(); 5__2 = null; 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 (5__2 != null) { ((IDisposable)5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__6 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public LoadTask task; private <>c__DisplayClass6_0 <>8__1; private int 5__2; private int 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; 5__2 = Plugin.LoadRetryCount.Value; 5__3 = 0; break; case 1: <>1__state = -1; if (<>8__1.success) { task.LoadedClip = <>8__1.clip; task.AudioData = null; task.IsCompleted = true; return false; } if (5__3 < 5__2 - 1) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 2; return true; } goto IL_0110; case 2: { <>1__state = -1; goto IL_0110; } IL_0110: <>8__1 = null; 5__3++; break; } if (5__3 < 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 _externalOverrideFolders = new HashSet(StringComparer.OrdinalIgnoreCase); [IteratorStateMachine(typeof(d__4))] public static IEnumerator InitializeAsync() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__4(0); } [IteratorStateMachine(typeof(d__5))] private static IEnumerator LoadEmbeddedResourcesParallel(string resourcePrefix, Dictionary singleDict, Dictionary> folderDict) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__5(0) { resourcePrefix = resourcePrefix, singleDict = singleDict, folderDict = folderDict }; } [IteratorStateMachine(typeof(d__6))] private static IEnumerator LoadSingleClipWithRetry(LoadTask task) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__6(0) { task = task }; } [IteratorStateMachine(typeof(d__7))] private static IEnumerator LoadAudioClipFromBytes(byte[] audioData, string clipName, Action onComplete) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__7(0) { audioData = audioData, clipName = clipName, onComplete = onComplete }; } [IteratorStateMachine(typeof(d__8))] private static IEnumerator LoadExternalAudioFilesAsync() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__8(0); } 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)("[Override] " + text + "/" + fileName + ": 외부 음악 감지됨 → 내장 음악 대체")); } } } } 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> 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}개 제거됨"); } } } } [IteratorStateMachine(typeof(d__12))] private static IEnumerator CollectAudioFilesAsync(string path, Dictionary singleDict, Dictionary> folderDict) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__12(0) { path = path, singleDict = singleDict, folderDict = folderDict }; } [IteratorStateMachine(typeof(d__13))] private static IEnumerator LoadExternalClipAsync(string path, Dictionary dict) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__13(0) { path = path, dict = dict }; } 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 { 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(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(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(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_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: 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 && ___m_musicSource.clip.length - ___m_musicSource.time < 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: 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(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(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(ambient, fieldName); if (value == null) { return; } List 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(); 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() : null); val2 = Plugin.CustomSFX["groundtorch"]; } else if (name.Contains("walltorch") && Plugin.CustomSFX.ContainsKey("walltorch")) { GameObject enabledObjectHigh = __instance.m_enabledObjectHigh; val = ((enabledObjectHigh != null) ? enabledObjectHigh.GetComponentInChildren() : null); if ((Object)(object)val == (Object)null) { GameObject enabledObject2 = __instance.m_enabledObject; val = ((enabledObject2 != null) ? enabledObject2.GetComponentInChildren() : 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() : 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() : null); val2 = Plugin.CustomSFX["bonfire"]; } else if (name.Contains("hearth") && Plugin.CustomSFX.ContainsKey("hearth")) { GameObject enabledObjectHigh4 = __instance.m_enabledObjectHigh; val = ((enabledObjectHigh4 != null) ? enabledObjectHigh4.GetComponentInChildren() : 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", 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(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.3")] public class Plugin : BaseUnityPlugin { public static Plugin instance; public static ConfigEntry ModEnabled; public static ConfigEntry MusicVolume; public static ConfigEntry AmbientVolume; public static ConfigEntry LocationVolMultiplier; public static ConfigEntry LoadRetryCount; public static ConfigEntry EnableFallback; public static Dictionary CustomMusic = new Dictionary(); public static Dictionary> CustomMusicList = new Dictionary>(); public static Dictionary CustomAmbient = new Dictionary(); public static Dictionary> CustomAmbientList = new Dictionary>(); public static Dictionary CustomSFX = new Dictionary(); public static Dictionary> CustomSFXList = new Dictionary>(); public static ManualLogSource Log { get; private set; } private void Awake() { instance = this; Log = ((BaseUnityPlugin)this).Logger; ModEnabled = ((BaseUnityPlugin)this).Config.Bind("General", "Enabled", true, "Enable this mod"); MusicVolume = ((BaseUnityPlugin)this).Config.Bind("General", "MusicVolume", 0.6f, "Music volume (0.0 - 1.0)"); AmbientVolume = ((BaseUnityPlugin)this).Config.Bind("General", "AmbientVolume", 0.3f, "Ambient volume (0.0 - 1.0)"); LocationVolMultiplier = ((BaseUnityPlugin)this).Config.Bind("General", "LocationVolumeMultiplier", 5f, "Location music volume multiplier"); LoadRetryCount = ((BaseUnityPlugin)this).Config.Bind("Advanced", "LoadRetryCount", 3, "Number of retries when audio loading fails"); EnableFallback = ((BaseUnityPlugin)this).Config.Bind("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()); 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.3 Loaded!"); methodInfo.Invoke(null, new object[1] { ConsoleColor.Gray }); } else { Log.LogMessage((object)"[CaptainAudio] CaptainAudio 1.3.3 Loaded!"); } } catch { Log.LogMessage((object)"[CaptainAudio] CaptainAudio 1.3.3 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); 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 _fieldCache = new Dictionary(); private static readonly Dictionary _propertyCache = new Dictionary(); 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(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 _lowerCaseCache = new Dictionary(); private static readonly Dictionary _musicMappingCache = new Dictionary(); 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"); _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; } } }