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.2.1
CaptainAudio.dll
Decompiled 2 months agousing System; using System.Collections; using System.Collections.Concurrent; 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 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__DisplayClass2_0 { public string resourcePrefix; internal bool <LoadEmbeddedResourcesParallel>b__0(string name) { return name.StartsWith(resourcePrefix); } } [CompilerGenerated] private sealed class <>c__DisplayClass3_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 <CollectAudioFiles>d__6 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string path; public Dictionary<string, AudioClip> singleDict; public Dictionary<string, Dictionary<string, AudioClip>> folderDict; private string[] <>s__1; private int <>s__2; private string <file>5__3; private string[] <>s__4; private int <>s__5; private string <folder>5__6; private string <folderName>5__7; private string[] <>s__8; private int <>s__9; private string <file>5__10; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <CollectAudioFiles>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>s__1 = null; <file>5__3 = null; <>s__4 = null; <folder>5__6 = null; <folderName>5__7 = null; <>s__8 = null; <file>5__10 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>s__1 = Directory.GetFiles(path); <>s__2 = 0; goto IL_009a; case 1: <>1__state = -1; <file>5__3 = null; <>s__2++; goto IL_009a; case 2: { <>1__state = -1; <file>5__10 = null; <>s__9++; goto IL_0182; } IL_009a: if (<>s__2 < <>s__1.Length) { <file>5__3 = <>s__1[<>s__2]; <>2__current = LoadExternalClip(<file>5__3, singleDict); <>1__state = 1; return true; } <>s__1 = null; <>s__4 = Directory.GetDirectories(path); <>s__5 = 0; goto IL_01b6; IL_01b6: if (<>s__5 < <>s__4.Length) { <folder>5__6 = <>s__4[<>s__5]; <folderName>5__7 = Path.GetFileName(<folder>5__6); folderDict[<folderName>5__7] = new Dictionary<string, AudioClip>(); <>s__8 = Directory.GetFiles(<folder>5__6); <>s__9 = 0; goto IL_0182; } <>s__4 = null; return false; IL_0182: if (<>s__9 < <>s__8.Length) { <file>5__10 = <>s__8[<>s__9]; <>2__current = LoadExternalClip(<file>5__10, folderDict[<folderName>5__7]); <>1__state = 2; return true; } <>s__8 = null; <folderName>5__7 = null; <folder>5__6 = null; <>s__5++; goto IL_01b6; } } 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 <InitializeAsync>d__1 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private int <totalMusic>5__1; private int <totalAmbient>5__2; private int <totalSFX>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <InitializeAsync>d__1(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; } CleanupOldTempFiles(); 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 = LoadExternalAudioFiles(); <>1__state = 4; return true; case 4: <>1__state = -1; StringMatchCache.Initialize(); _isInitialized = true; <totalMusic>5__1 = Plugin.CustomMusic.Count + Plugin.CustomMusicList.Values.Sum((Dictionary<string, AudioClip> d) => d.Count); <totalAmbient>5__2 = Plugin.CustomAmbient.Count + Plugin.CustomAmbientList.Values.Sum((Dictionary<string, AudioClip> d) => d.Count); <totalSFX>5__3 = Plugin.CustomSFX.Count + Plugin.CustomSFXList.Values.Sum((Dictionary<string, AudioClip> d) => d.Count); Plugin.Log.LogWarning((object)"=== 오디오 로딩 완료 ==="); Plugin.Log.LogInfo((object)$" 음악: {Plugin.CustomMusicList.Count}개 폴더, {<totalMusic>5__1}개 클립"); Plugin.Log.LogInfo((object)$" 환경음: {Plugin.CustomAmbientList.Count}개 폴더, {<totalAmbient>5__2}개 클립"); Plugin.Log.LogInfo((object)$" 효과음: {Plugin.CustomSFXList.Count}개 폴더, {<totalSFX>5__3}개 클립"); Plugin.Log.LogInfo((object)$" 총 {<totalMusic>5__1 + <totalAmbient>5__2 + <totalSFX>5__3}개 클립 로딩 완료"); 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__4 : 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 Exception <ex>5__5; private UnityWebRequest <www>5__6; private DownloadHandlerAudioClip <handler>5__7; private AudioClip <clip>5__8; private Exception <ex>5__9; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadAudioClipFromBytes>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if ((uint)(num - -4) <= 1u || num == 1) { try { if (num == -4 || num == 1) { try { } finally { <>m__Finally2(); } } } finally { <>m__Finally1(); } } <fileExtension>5__1 = null; <tempPath>5__2 = null; <uri>5__4 = null; <ex>5__5 = null; <www>5__6 = null; <handler>5__7 = null; <clip>5__8 = null; <ex>5__9 = null; <>1__state = -2; } private bool MoveNext() { //IL_00e5: 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_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Invalid comparison between Unknown and I4 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 (Exception ex) { <ex>5__5 = ex; Plugin.Log.LogWarning((object)("임시 파일 쓰기 실패 (" + clipName + "): " + <ex>5__5.Message)); onComplete?.Invoke(null); return false; } <audioType>5__3 = GetAudioType(<fileExtension>5__1); <uri>5__4 = "file:///" + <tempPath>5__2.Replace("\\", "/"); <>1__state = -3; <www>5__6 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__4, <audioType>5__3); <>1__state = -4; <handler>5__7 = (DownloadHandlerAudioClip)<www>5__6.downloadHandler; <handler>5__7.streamAudio = true; <www>5__6.timeout = Plugin.LoadTimeout.Value; <>2__current = <www>5__6.SendWebRequest(); <>1__state = 1; return true; case 1: <>1__state = -4; if ((int)<www>5__6.result == 1) { <clip>5__8 = DownloadHandlerAudioClip.GetContent(<www>5__6); if ((Object)(object)<clip>5__8 != (Object)null && <clip>5__8.length > 0f) { ((Object)<clip>5__8).name = clipName; onComplete?.Invoke(<clip>5__8); } else { Plugin.Log.LogWarning((object)("오디오 클립 로딩 실패 (" + clipName + "): 클립이 null이거나 길이가 0입니다.")); onComplete?.Invoke(null); } <clip>5__8 = null; } else { Plugin.Log.LogWarning((object)("오디오 클립 로딩 실패 (" + clipName + "): " + <www>5__6.error)); onComplete?.Invoke(null); } <handler>5__7 = null; <>m__Finally2(); <www>5__6 = null; <>m__Finally1(); 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 (File.Exists(<tempPath>5__2)) { try { File.Delete(<tempPath>5__2); } catch (Exception ex) { <ex>5__9 = ex; Plugin.Log.LogWarning((object)("임시 파일 삭제 실패: " + <tempPath>5__2 + " - " + <ex>5__9.Message)); } } } private void <>m__Finally2() { <>1__state = -3; if (<www>5__6 != null) { ((IDisposable)<www>5__6).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadEmbeddedResourcesParallel>d__2 : 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__DisplayClass2_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__2(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__DisplayClass2_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 <LoadExternalAudioFiles>d__5 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; private string <externalPath>5__1; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadExternalAudioFiles>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <externalPath>5__1 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <externalPath>5__1 = Path.Combine(Paths.PluginPath, "CaptainAudio"); if (!Directory.Exists(<externalPath>5__1)) { return false; } if (Directory.Exists(Path.Combine(<externalPath>5__1, "Music"))) { <>2__current = CollectAudioFiles(Path.Combine(<externalPath>5__1, "Music"), Plugin.CustomMusic, Plugin.CustomMusicList); <>1__state = 1; return true; } goto IL_00b3; case 1: <>1__state = -1; goto IL_00b3; case 2: <>1__state = -1; goto IL_0103; case 3: { <>1__state = -1; break; } IL_00b3: if (Directory.Exists(Path.Combine(<externalPath>5__1, "SFX"))) { <>2__current = CollectAudioFiles(Path.Combine(<externalPath>5__1, "SFX"), Plugin.CustomSFX, Plugin.CustomSFXList); <>1__state = 2; return true; } goto IL_0103; IL_0103: if (Directory.Exists(Path.Combine(<externalPath>5__1, "Ambient"))) { <>2__current = CollectAudioFiles(Path.Combine(<externalPath>5__1, "Ambient"), Plugin.CustomAmbient, Plugin.CustomAmbientList); <>1__state = 3; 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 <LoadExternalClip>d__7 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string path; public Dictionary<string, AudioClip> dict; private string <uri>5__1; private UnityWebRequest <www>5__2; private DownloadHandlerAudioClip <handler>5__3; private AudioClip <clip>5__4; private string <clipName>5__5; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadExternalClip>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(); } } <uri>5__1 = null; <www>5__2 = null; <handler>5__3 = null; <clip>5__4 = null; <clipName>5__5 = null; <>1__state = -2; } private bool MoveNext() { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Invalid comparison between Unknown and I4 //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0104: 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; } <uri>5__1 = "file:///" + path.Replace("\\", "/"); <www>5__2 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__1, (AudioType)0); <>1__state = -3; <www>5__2.timeout = Plugin.LoadTimeout.Value; <>2__current = <www>5__2.SendWebRequest(); <>1__state = 1; return true; case 1: <>1__state = -3; if ((int)<www>5__2.result == 1) { <handler>5__3 = (DownloadHandlerAudioClip)<www>5__2.downloadHandler; DownloadHandlerAudioClip obj = <handler>5__3; <clip>5__4 = ((obj != null) ? obj.audioClip : null); if ((Object)(object)<clip>5__4 != (Object)null) { <clipName>5__5 = Path.GetFileNameWithoutExtension(path); ((Object)<clip>5__4).name = <clipName>5__5; dict[<clipName>5__5] = <clip>5__4; Plugin.Log.LogInfo((object)("외부 오디오 로딩 성공: " + <clipName>5__5)); <clipName>5__5 = null; } else { Plugin.Log.LogWarning((object)("외부 오디오 로딩 실패 (클립 null): " + path)); } <handler>5__3 = null; <clip>5__4 = null; } else { Plugin.Log.LogWarning((object)("외부 오디오 로딩 실패: " + path + " - " + <www>5__2.error)); } <>m__Finally1(); <www>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 (<www>5__2 != null) { ((IDisposable)<www>5__2).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadSingleClipWithRetry>d__3 : 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__DisplayClass3_0 <>8__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadSingleClipWithRetry>d__3(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: 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) { Plugin.Log.LogInfo((object)$"오디오 로딩 재시도 중... ({<attempt>5__2 + 1}/{<retryCount>5__1}): {task.ClipName}"); <>2__current = (object)new WaitForSeconds(Plugin.LoadRetryDelay.Value); <>1__state = 2; return true; } goto IL_015a; case 2: { <>1__state = -1; goto IL_015a; } IL_015a: <>8__3 = null; <attempt>5__2++; break; } if (<attempt>5__2 < <retryCount>5__1) { <>8__3 = new <>c__DisplayClass3_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; [IteratorStateMachine(typeof(<InitializeAsync>d__1))] public static IEnumerator InitializeAsync() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitializeAsync>d__1(0); } [IteratorStateMachine(typeof(<LoadEmbeddedResourcesParallel>d__2))] 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__2(0) { resourcePrefix = resourcePrefix, singleDict = singleDict, folderDict = folderDict }; } [IteratorStateMachine(typeof(<LoadSingleClipWithRetry>d__3))] private static IEnumerator LoadSingleClipWithRetry(LoadTask task) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadSingleClipWithRetry>d__3(0) { task = task }; } [IteratorStateMachine(typeof(<LoadAudioClipFromBytes>d__4))] private static IEnumerator LoadAudioClipFromBytes(byte[] audioData, string clipName, Action<AudioClip> onComplete) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadAudioClipFromBytes>d__4(0) { audioData = audioData, clipName = clipName, onComplete = onComplete }; } [IteratorStateMachine(typeof(<LoadExternalAudioFiles>d__5))] private static IEnumerator LoadExternalAudioFiles() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadExternalAudioFiles>d__5(0); } [IteratorStateMachine(typeof(<CollectAudioFiles>d__6))] private static IEnumerator CollectAudioFiles(string path, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <CollectAudioFiles>d__6(0) { path = path, singleDict = singleDict, folderDict = folderDict }; } [IteratorStateMachine(typeof(<LoadExternalClip>d__7))] private static IEnumerator LoadExternalClip(string path, Dictionary<string, AudioClip> dict) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadExternalClip>d__7(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 void CleanupOldTempFiles() { try { string temporaryCachePath = Application.temporaryCachePath; if (!Directory.Exists(temporaryCachePath)) { return; } string[] files = Directory.GetFiles(temporaryCachePath, "temp_audio_*"); int num = 0; string[] array = files; foreach (string text in array) { try { FileInfo fileInfo = new FileInfo(text); if ((DateTime.Now - fileInfo.LastWriteTime).TotalHours > 1.0) { File.Delete(text); num++; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("임시 파일 정리 실패: " + text + " - " + ex.Message)); } } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("임시 파일 정리 중 오류: " + ex2.Message)); } } 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) { if (Plugin.ModEnabled.Value) { if (___m_queuedMusic != null) { ReflectionCache.SetValue(___m_queuedMusic, "m_volume", Plugin.MusicVolume.Value); } if (!___m_musicSource.isPlaying && ___m_musicSource.loop) { ___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)) { string name = ((Object)___m_audioSource.clip).name; if (Plugin.CustomMusic.ContainsKey(name)) { ___m_audioSource.clip = Plugin.CustomMusic[name]; ___m_baseVolume *= Plugin.LocationVolMultiplier.Value; } } } } [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")) { AudioSource componentInChildren = ((Component)__instance).GetComponentInChildren<AudioSource>(); if ((Object)(object)componentInChildren != (Object)null) { componentInChildren.clip = Plugin.CustomSFX["portal"]; ((Component)componentInChildren).gameObject.SetActive(false); ((Component)componentInChildren).gameObject.SetActive(true); } } } } [HarmonyPatch(typeof(Fireplace), "Start")] public static class Fireplace_Start_Patch { private static readonly Dictionary<string, string> FireplaceSFXMapping = new Dictionary<string, string> { { "groundtorch", "groundtorch" }, { "walltorch", "walltorch" }, { "fire_pit", "fire_pit" }, { "bonfire", "bonfire" }, { "hearth", "hearth" } }; private static void Postfix(Fireplace __instance) { if (!Plugin.ModEnabled.Value) { return; } string name = ((Object)__instance).name; foreach (KeyValuePair<string, string> item in FireplaceSFXMapping) { if (name.Contains(item.Key) && Plugin.CustomSFX.ContainsKey(item.Value)) { AudioSource fireplaceAudioSource = GetFireplaceAudioSource(__instance, item.Key); if ((Object)(object)fireplaceAudioSource != (Object)null) { fireplaceAudioSource.clip = Plugin.CustomSFX[item.Value]; } break; } } } private static AudioSource GetFireplaceAudioSource(Fireplace fireplace, string keyword) { if (keyword == "groundtorch") { GameObject enabledObject = fireplace.m_enabledObject; return (enabledObject != null) ? enabledObject.GetComponentInChildren<AudioSource>() : null; } if (keyword == "walltorch") { GameObject enabledObjectHigh = fireplace.m_enabledObjectHigh; AudioSource val = ((enabledObjectHigh != null) ? enabledObjectHigh.GetComponentInChildren<AudioSource>() : null); object obj = val; if (obj == null) { GameObject enabledObject2 = fireplace.m_enabledObject; obj = ((enabledObject2 != null) ? enabledObject2.GetComponentInChildren<AudioSource>() : null); } return (AudioSource)obj; } GameObject enabledObjectHigh2 = fireplace.m_enabledObjectHigh; return (enabledObjectHigh2 != null) ? enabledObjectHigh2.GetComponentInChildren<AudioSource>() : null; } } [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; } } string[] array = new string[4] { "Morning", "Day", "Evening", "Night" }; string[] array2 = array; foreach (string timeOfDay in array2) { AddMusicToEnvironment(__instance, i, timeOfDay); } } } 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, "<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.1")] 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<float> LoadRetryDelay; public static ConfigEntry<int> LoadTimeout; 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"); LoadRetryDelay = ((BaseUnityPlugin)this).Config.Bind<float>("Advanced", "LoadRetryDelay", 0.5f, "Delay between retries in seconds"); LoadTimeout = ((BaseUnityPlugin)this).Config.Bind<int>("Advanced", "LoadTimeout", 15, "Timeout for audio loading in seconds"); EnableFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("Advanced", "EnableFallback", true, "Use fallback to vanilla music on load failure"); if (!ModEnabled.Value) { return; } SetupConfigEvents(); try { Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); ((MonoBehaviour)this).StartCoroutine(AudioLoader.InitializeAsync()); Log.LogInfo((object)"Captain Audio v1.2.1 loaded"); } catch (Exception ex) { Log.LogError((object)("Critical error: " + ex.Message)); } } private void SetupConfigEvents() { MusicVolume.SettingChanged += delegate { Log.LogInfo((object)$"음악 볼륨 변경: {MusicVolume.Value:F2} (다음 음악부터 적용됨)"); }; AmbientVolume.SettingChanged += delegate { Log.LogInfo((object)$"환경음 볼륨 변경: {AmbientVolume.Value:F2} (다음 환경음부터 적용됨)"); }; LocationVolMultiplier.SettingChanged += delegate { Log.LogInfo((object)$"Location 음악 볼륨 배율 변경: {LocationVolMultiplier.Value:F2} (다음 음악부터 적용됨)"); }; LoadRetryCount.SettingChanged += delegate { Log.LogInfo((object)$"오디오 로딩 재시도 횟수 변경: {LoadRetryCount.Value}"); }; LoadRetryDelay.SettingChanged += delegate { Log.LogInfo((object)$"재시도 지연 시간 변경: {LoadRetryDelay.Value}초"); }; LoadTimeout.SettingChanged += delegate { Log.LogInfo((object)$"로딩 타임아웃 변경: {LoadTimeout.Value}초"); }; } } 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 ConcurrentDictionary<string, FieldInfo> _fieldCache = new ConcurrentDictionary<string, FieldInfo>(); private static readonly ConcurrentDictionary<string, PropertyInfo> _propertyCache = new ConcurrentDictionary<string, PropertyInfo>(); public static FieldInfo GetField(Type type, string fieldName) { string key = type.FullName + "." + fieldName; return _fieldCache.GetOrAdd(key, (string _) => type.GetField(fieldName)); } 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; } } }