Please disclose if your mod was created primarily using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of CaptainAudio v1.3.0
CaptainAudio.dll
Decompiled a week agousing 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.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] [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)$"Loaded: {Plugin.CustomMusicList.Count} folders, {Plugin.CustomMusic.Count} clips"); 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 <fileExtension>5__1; private string <tempPath>5__2; private AudioType <audioType>5__3; private string <uri>5__4; private UnityWebRequest <www>5__5; private DownloadHandlerAudioClip <handler>5__6; private AudioClip <clip>5__7; 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(); } } <fileExtension>5__1 = null; <tempPath>5__2 = null; <uri>5__4 = null; <www>5__5 = null; <handler>5__6 = null; <clip>5__7 = null; <>1__state = -2; } private bool MoveNext() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Invalid comparison between Unknown and I4 //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Invalid comparison between Unknown and I4 //IL_0284: Unknown result type (might be due to invalid IL or missing references) try { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <fileExtension>5__1 = DetectAudioFormat(audioData); <tempPath>5__2 = Path.Combine(Application.temporaryCachePath, $"temp_audio_{clipName}_{Guid.NewGuid()}{<fileExtension>5__1}"); try { Directory.CreateDirectory(Path.GetDirectoryName(<tempPath>5__2)); File.WriteAllBytes(<tempPath>5__2, audioData); } catch { onComplete?.Invoke(null); return false; } <audioType>5__3 = GetAudioType(<fileExtension>5__1); <uri>5__4 = "file:///" + <tempPath>5__2.Replace("\\", "/"); <www>5__5 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__4, <audioType>5__3); <>1__state = -3; <handler>5__6 = (DownloadHandlerAudioClip)<www>5__5.downloadHandler; <handler>5__6.streamAudio = false; <handler>5__6.compressed = false; <www>5__5.timeout = 15; <>2__current = <www>5__5.SendWebRequest(); <>1__state = 1; return true; case 1: <>1__state = -3; if ((int)<www>5__5.result == 1) { <clip>5__7 = DownloadHandlerAudioClip.GetContent(<www>5__5); if ((Object)(object)<clip>5__7 != (Object)null && <clip>5__7.length > 0f && <clip>5__7.samples > 0 && (int)<clip>5__7.loadState == 2) { ((Object)<clip>5__7).name = clipName; onComplete?.Invoke(<clip>5__7); } else { ManualLogSource log = Plugin.Log; object[] obj = new object[4] { clipName, null, null, null }; AudioClip obj2 = <clip>5__7; obj[1] = ((obj2 != null) ? new float?(obj2.length) : null); AudioClip obj3 = <clip>5__7; obj[2] = ((obj3 != null) ? new int?(obj3.samples) : null); AudioClip obj4 = <clip>5__7; obj[3] = ((obj4 != null) ? new AudioDataLoadState?(obj4.loadState) : null); log.LogWarning((object)string.Format("Invalid clip: {0} (length={1}, samples={2}, state={3})", obj)); onComplete?.Invoke(null); } <clip>5__7 = null; } else { Plugin.Log.LogWarning((object)("Failed to load: " + clipName + " - " + <www>5__5.error)); onComplete?.Invoke(null); } <handler>5__6 = null; <>m__Finally1(); <www>5__5 = 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__5 != null) { ((IDisposable)<www>5__5).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, AudioClip> singleDict; public Dictionary<string, Dictionary<string, AudioClip>> folderDict; private <>c__DisplayClass5_0 <>8__1; private Assembly <assembly>5__2; private string[] <resourceNames>5__3; private List<LoadTask> <tasks>5__4; private string[] <>s__5; private int <>s__6; private string <resourceName>5__7; private Stream <stream>5__8; private byte[] <audioData>5__9; private string <fileName>5__10; private string <folderName>5__11; private string <clipName>5__12; private LoadTask <task>5__13; private List<LoadTask>.Enumerator <>s__14; private LoadTask <task>5__15; 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() { <>8__1 = null; <assembly>5__2 = null; <resourceNames>5__3 = null; <tasks>5__4 = null; <>s__5 = null; <resourceName>5__7 = null; <stream>5__8 = null; <audioData>5__9 = null; <fileName>5__10 = null; <folderName>5__11 = null; <clipName>5__12 = null; <task>5__13 = null; <>s__14 = default(List<LoadTask>.Enumerator); <task>5__15 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>8__1 = new <>c__DisplayClass5_0(); <>8__1.resourcePrefix = resourcePrefix; <assembly>5__2 = Assembly.GetExecutingAssembly(); <resourceNames>5__3 = (from name in <assembly>5__2.GetManifestResourceNames() where name.StartsWith(<>8__1.resourcePrefix) select name).ToArray(); if (<resourceNames>5__3.Length == 0) { return false; } <tasks>5__4 = new List<LoadTask>(); <>s__5 = <resourceNames>5__3; for (<>s__6 = 0; <>s__6 < <>s__5.Length; <>s__6++) { <resourceName>5__7 = <>s__5[<>s__6]; <stream>5__8 = <assembly>5__2.GetManifestResourceStream(<resourceName>5__7); if (<stream>5__8 != null) { <audioData>5__9 = new byte[<stream>5__8.Length]; <stream>5__8.Read(<audioData>5__9, 0, <audioData>5__9.Length); <stream>5__8.Dispose(); <fileName>5__10 = <resourceName>5__7.Substring(<>8__1.resourcePrefix.Length + 1); (<folderName>5__11, <clipName>5__12) = ParseResourcePath(<fileName>5__10); if (!string.IsNullOrEmpty(<clipName>5__12)) { <task>5__13 = new LoadTask { FolderName = <folderName>5__11, ClipName = <clipName>5__12, AudioData = <audioData>5__9, IsCompleted = false, LoadedClip = null }; <tasks>5__4.Add(<task>5__13); ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadSingleClipWithRetry(<task>5__13)); <stream>5__8 = null; <audioData>5__9 = null; <fileName>5__10 = null; <folderName>5__11 = null; <clipName>5__12 = null; <task>5__13 = null; <resourceName>5__7 = null; } } } <>s__5 = null; break; case 1: <>1__state = -1; break; } if (<tasks>5__4.Any((LoadTask t) => !t.IsCompleted)) { <>2__current = null; <>1__state = 1; return true; } <>s__14 = <tasks>5__4.GetEnumerator(); try { while (<>s__14.MoveNext()) { <task>5__15 = <>s__14.Current; if ((Object)(object)<task>5__15.LoadedClip == (Object)null) { continue; } if (!string.IsNullOrEmpty(<task>5__15.FolderName)) { if (!folderDict.ContainsKey(<task>5__15.FolderName)) { folderDict[<task>5__15.FolderName] = new Dictionary<string, AudioClip>(); } folderDict[<task>5__15.FolderName][<task>5__15.ClipName] = <task>5__15.LoadedClip; } else { singleDict[<task>5__15.ClipName] = <task>5__15.LoadedClip; } <task>5__15 = null; } } finally { ((IDisposable)<>s__14).Dispose(); } <>s__14 = default(List<LoadTask>.Enumerator); 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 int <retryCount>5__1; private int <attempt>5__2; private <>c__DisplayClass6_0 <>8__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__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <retryCount>5__1 = Plugin.LoadRetryCount.Value; <attempt>5__2 = 0; break; case 1: <>1__state = -1; if (<>8__3.success) { task.LoadedClip = <>8__3.clip; task.IsCompleted = true; return false; } if (<attempt>5__2 < <retryCount>5__1 - 1) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 2; return true; } goto IL_011d; case 2: { <>1__state = -1; goto IL_011d; } IL_011d: <>8__3 = null; <attempt>5__2++; break; } if (<attempt>5__2 < <retryCount>5__1) { <>8__3 = new <>c__DisplayClass6_0(); <>8__3.success = false; <>8__3.clip = null; <>2__current = LoadAudioClipFromBytes(task.AudioData, task.ClipName, delegate(AudioClip loadedClip) { <>8__3.clip = loadedClip; <>8__3.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 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(); CollectAudioFiles(path, Plugin.CustomMusic, Plugin.CustomMusicList); return; } 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" }; 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 void CollectAudioFiles(string path, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict) { string[] files = Directory.GetFiles(path); foreach (string path2 in files) { LoadExternalClip(path2, singleDict); } string[] directories = Directory.GetDirectories(path); foreach (string path3 in directories) { string fileName = Path.GetFileName(path3); folderDict[fileName] = new Dictionary<string, AudioClip>(); string[] files2 = Directory.GetFiles(path3); foreach (string path4 in files2) { LoadExternalClip(path4, folderDict[fileName]); } } } private static void LoadExternalClip(string path, Dictionary<string, AudioClip> dict) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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) { //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 { 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) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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) { return; } 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 < 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; } Type type = ((object)MusicMan.instance.m_music[0]).GetType(); object obj = Activator.CreateInstance(type); 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, "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; 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.0")] 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.LogMessage((object)"Captain Audio v1.2.2 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("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; } } }