Please disclose if any significant portion of your mod was created 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 DMC Atmosphere v1.1.0
DMCAtmosthere.dll
Decompiled 2 months 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.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 DMCAtmosthere { 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("DMCAtmosthere.asset.Resources.Music", Plugin.CustomMusic, Plugin.CustomMusicList); <>1__state = 1; return true; case 1: <>1__state = -1; <>2__current = LoadEmbeddedResourcesParallel("DMCAtmosthere.asset.Resources.Ambient", Plugin.CustomAmbient, Plugin.CustomAmbientList); <>1__state = 2; return true; case 2: <>1__state = -1; <>2__current = LoadEmbeddedResourcesParallel("DMCAtmosthere.asset.Resources.SFX", Plugin.CustomSFX, Plugin.CustomSFXList); <>1__state = 3; return true; case 3: <>1__state = -1; LoadExternalAudioFiles(); StringMatchCache.Initialize(); _isInitialized = true; Plugin.Log.LogInfo((object)$"<color=#00BFFF>Loaded: {Plugin.CustomMusicList.Count} folders, {Plugin.CustomMusic.Count} clips</color>"); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadAudioClipFromBytes>d__7 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public byte[] audioData; public string clipName; public Action<AudioClip> onComplete; private string <tempPath>5__2; private UnityWebRequest <www>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadAudioClipFromBytes>d__7(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <tempPath>5__2 = null; <www>5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Invalid comparison between Unknown and I4 //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Invalid comparison between Unknown and I4 //IL_020a: Unknown result type (might be due to invalid IL or missing references) try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; string text = DetectAudioFormat(audioData); <tempPath>5__2 = Path.Combine(Application.temporaryCachePath, $"temp_audio_{clipName}_{Guid.NewGuid()}{text}"); try { Directory.CreateDirectory(Path.GetDirectoryName(<tempPath>5__2)); File.WriteAllBytes(<tempPath>5__2, audioData); } catch { onComplete?.Invoke(null); return false; } AudioType audioType = GetAudioType(text); string text2 = "file:///" + <tempPath>5__2.Replace("\\", "/"); <www>5__3 = UnityWebRequestMultimedia.GetAudioClip(text2, audioType); <>1__state = -3; DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)<www>5__3.downloadHandler; val.streamAudio = false; val.compressed = false; <www>5__3.timeout = 15; <>2__current = <www>5__3.SendWebRequest(); <>1__state = 1; return true; } case 1: <>1__state = -3; if ((int)<www>5__3.result == 1) { AudioClip content = DownloadHandlerAudioClip.GetContent(<www>5__3); if ((Object)(object)content != (Object)null && content.length > 0f && content.samples > 0 && (int)content.loadState == 2) { ((Object)content).name = clipName; onComplete?.Invoke(content); } else { Plugin.Log.LogWarning((object)$"Invalid clip: {clipName} (length={((content != null) ? new float?(content.length) : null)}, samples={((content != null) ? new int?(content.samples) : null)}, state={((content != null) ? new AudioDataLoadState?(content.loadState) : null)})"); onComplete?.Invoke(null); } } else { Plugin.Log.LogWarning((object)("Failed to load: " + clipName + " - " + <www>5__3.error)); onComplete?.Invoke(null); } <>m__Finally1(); <www>5__3 = null; if (File.Exists(<tempPath>5__2)) { try { File.Delete(<tempPath>5__2); } catch { } } return false; } } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; if (<www>5__3 != null) { ((IDisposable)<www>5__3).Dispose(); } } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadEmbeddedResourcesParallel>d__5 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public string resourcePrefix; public Dictionary<string, Dictionary<string, AudioClip>> folderDict; public Dictionary<string, AudioClip> singleDict; private List<LoadTask> <tasks>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadEmbeddedResourcesParallel>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <tasks>5__2 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; <>c__DisplayClass5_0 CS$<>8__locals0 = new <>c__DisplayClass5_0 { resourcePrefix = resourcePrefix }; Assembly executingAssembly = Assembly.GetExecutingAssembly(); string[] array = (from name in executingAssembly.GetManifestResourceNames() where name.StartsWith(CS$<>8__locals0.resourcePrefix) select name).ToArray(); if (array.Length == 0) { return false; } <tasks>5__2 = new List<LoadTask>(); string[] array2 = array; foreach (string text in array2) { Stream manifestResourceStream = executingAssembly.GetManifestResourceStream(text); if (manifestResourceStream != null) { byte[] array3 = new byte[manifestResourceStream.Length]; manifestResourceStream.Read(array3, 0, array3.Length); manifestResourceStream.Dispose(); var (folderName, text2) = ParseResourcePath(text.Substring(CS$<>8__locals0.resourcePrefix.Length + 1)); if (!string.IsNullOrEmpty(text2)) { LoadTask loadTask = new LoadTask { FolderName = folderName, ClipName = text2, AudioData = array3, IsCompleted = false, LoadedClip = null }; <tasks>5__2.Add(loadTask); ((MonoBehaviour)Plugin.instance).StartCoroutine(LoadSingleClipWithRetry(loadTask)); } } } break; } case 1: <>1__state = -1; break; } if (<tasks>5__2.Any((LoadTask t) => !t.IsCompleted)) { <>2__current = null; <>1__state = 1; return true; } foreach (LoadTask item in <tasks>5__2) { if ((Object)(object)item.LoadedClip == (Object)null) { continue; } if (!string.IsNullOrEmpty(item.FolderName)) { if (!folderDict.ContainsKey(item.FolderName)) { folderDict[item.FolderName] = new Dictionary<string, AudioClip>(); } folderDict[item.FolderName][item.ClipName] = item.LoadedClip; } else { singleDict[item.ClipName] = item.LoadedClip; } } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadSingleClipWithRetry>d__6 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public LoadTask task; private <>c__DisplayClass6_0 <>8__1; private int <retryCount>5__2; private int <attempt>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadSingleClipWithRetry>d__6(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>8__1 = null; <>1__state = -2; } private bool MoveNext() { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <retryCount>5__2 = Plugin.LoadRetryCount.Value; <attempt>5__3 = 0; break; case 1: <>1__state = -1; if (<>8__1.success) { task.LoadedClip = <>8__1.clip; task.IsCompleted = true; return false; } if (<attempt>5__3 < <retryCount>5__2 - 1) { <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 2; return true; } goto IL_0104; case 2: { <>1__state = -1; goto IL_0104; } IL_0104: <>8__1 = null; <attempt>5__3++; break; } if (<attempt>5__3 < <retryCount>5__2) { <>8__1 = new <>c__DisplayClass6_0(); <>8__1.success = false; <>8__1.clip = null; <>2__current = LoadAudioClipFromBytes(task.AudioData, task.ClipName, delegate(AudioClip loadedClip) { <>8__1.clip = loadedClip; <>8__1.success = (Object)(object)loadedClip != (Object)null; }); <>1__state = 1; return true; } task.IsCompleted = true; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool _isInitialized = false; private const int MAX_RETRY_COUNT = 3; private const float RETRY_DELAY = 0.5f; private static HashSet<string> _externalOverrideFolders = new HashSet<string>(StringComparer.OrdinalIgnoreCase); [IteratorStateMachine(typeof(<InitializeAsync>d__4))] public static IEnumerator InitializeAsync() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <InitializeAsync>d__4(0); } [IteratorStateMachine(typeof(<LoadEmbeddedResourcesParallel>d__5))] private static IEnumerator LoadEmbeddedResourcesParallel(string resourcePrefix, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadEmbeddedResourcesParallel>d__5(0) { resourcePrefix = resourcePrefix, singleDict = singleDict, folderDict = folderDict }; } [IteratorStateMachine(typeof(<LoadSingleClipWithRetry>d__6))] private static IEnumerator LoadSingleClipWithRetry(LoadTask task) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadSingleClipWithRetry>d__6(0) { task = task }; } [IteratorStateMachine(typeof(<LoadAudioClipFromBytes>d__7))] private static IEnumerator LoadAudioClipFromBytes(byte[] audioData, string clipName, Action<AudioClip> onComplete) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadAudioClipFromBytes>d__7(0) { audioData = audioData, clipName = clipName, onComplete = onComplete }; } private static void LoadExternalAudioFiles() { string text = Path.Combine(Paths.PluginPath, "DMCAtmosthere"); if (Directory.Exists(text)) { _externalOverrideFolders.Clear(); ScanExternalFolders(text); ApplyExternalOverrides(); if (Directory.Exists(Path.Combine(text, "Music"))) { CollectAudioFiles(Path.Combine(text, "Music"), Plugin.CustomMusic, Plugin.CustomMusicList); } if (Directory.Exists(Path.Combine(text, "SFX"))) { CollectAudioFiles(Path.Combine(text, "SFX"), Plugin.CustomSFX, Plugin.CustomSFXList); } if (Directory.Exists(Path.Combine(text, "Ambient"))) { CollectAudioFiles(Path.Combine(text, "Ambient"), Plugin.CustomAmbient, Plugin.CustomAmbientList); } } } private static void ScanExternalFolders(string externalPath) { string[] array = new string[3] { "Music", "Ambient", "SFX" }; foreach (string text in array) { string path = Path.Combine(externalPath, text); if (!Directory.Exists(path)) { continue; } string[] directories = Directory.GetDirectories(path); foreach (string path2 in directories) { string fileName = Path.GetFileName(path2); if (Directory.GetFiles(path2).Any((string file) => IsValidAudioFile(file))) { string item = text + ":" + fileName; _externalOverrideFolders.Add(item); Plugin.Log.LogInfo((object)("<color=#FFA500>[Override] " + text + "/" + fileName + ": 외부 음악 감지됨 → 내장 음악 대체</color>")); } } } } private static bool IsValidAudioFile(string filePath) { if (string.IsNullOrEmpty(filePath)) { return false; } string text = Path.GetExtension(filePath).ToLower(); if (!(text == ".ogg") && !(text == ".wav")) { return text == ".mp3"; } return true; } private static void ApplyExternalOverrides() { foreach (string externalOverrideFolder in _externalOverrideFolders) { string[] array = externalOverrideFolder.Split(':'); if (array.Length != 2) { continue; } string text = array[0]; string folderName = array[1]; Dictionary<string, Dictionary<string, AudioClip>> dictionary = null; switch (text) { case "Music": dictionary = Plugin.CustomMusicList; break; case "Ambient": dictionary = Plugin.CustomAmbientList; break; case "SFX": dictionary = Plugin.CustomSFXList; break; } if (dictionary != null) { string text2 = dictionary.Keys.FirstOrDefault((string k) => string.Equals(k, folderName, StringComparison.OrdinalIgnoreCase)); if (text2 != null) { int count = dictionary[text2].Count; dictionary[text2].Clear(); Plugin.Log.LogInfo((object)$"<color=#FFA500>[Override] {text}/{folderName}: 내장 음악 {count}개 제거됨</color>"); } } } } private static void CollectAudioFiles(string path, Dictionary<string, AudioClip> singleDict, Dictionary<string, Dictionary<string, AudioClip>> folderDict) { string[] files = Directory.GetFiles(path); for (int i = 0; i < files.Length; i++) { LoadExternalClip(files[i], singleDict); } files = Directory.GetDirectories(path); foreach (string path2 in files) { string fileName = Path.GetFileName(path2); folderDict[fileName] = new Dictionary<string, AudioClip>(); string[] files2 = Directory.GetFiles(path2); for (int j = 0; j < files2.Length; j++) { LoadExternalClip(files2[j], folderDict[fileName]); } } } private static void LoadExternalClip(string path, Dictionary<string, AudioClip> dict) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Invalid comparison between Unknown and I4 if (path.EndsWith(".txt") || !path.Contains(".")) { return; } string text = Path.GetExtension(path).ToLower(); if (text != ".ogg" && text != ".wav" && text != ".mp3") { return; } string text2 = "file:///" + path.Replace("\\", "/"); AudioType audioType = GetAudioType(text); UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(text2, audioType); DownloadHandlerAudioClip val = (DownloadHandlerAudioClip)audioClip.downloadHandler; val.streamAudio = false; val.compressed = false; audioClip.SendWebRequest(); float num = 10f; float num2 = 0f; while (!audioClip.isDone && num2 < num) { Thread.Sleep(10); num2 += 0.01f; } if ((int)audioClip.result == 1) { AudioClip val2 = ((val != null) ? val.audioClip : null); if ((Object)(object)val2 != (Object)null && val2.length > 0f && val2.samples > 0) { string key = (((Object)val2).name = Path.GetFileNameWithoutExtension(path)); dict[key] = val2; } else { Plugin.Log.LogWarning((object)("Invalid external clip: " + path)); } } else { Plugin.Log.LogWarning((object)("Failed to load external: " + path + " - " + audioClip.error)); } audioClip.Dispose(); } private static (string folderName, string clipName) ParseResourcePath(string fileName) { string item = null; string item2 = null; if (fileName.Contains("\\")) { string[] array = fileName.Split('\\'); if (array.Length >= 2) { item = array[0]; item2 = Path.GetFileNameWithoutExtension(array[^1]); } } else { string[] array2 = fileName.Split('.'); if (array2.Length >= 3) { item = array2[0]; item2 = string.Join(".", array2.Skip(1).Take(array2.Length - 2)); } else if (array2.Length >= 2) { item = null; item2 = array2[0]; } } return (item, item2); } private static string DetectAudioFormat(byte[] audioData) { if (audioData.Length < 4) { return ".ogg"; } if (audioData[0] == 82 && audioData[1] == 73 && audioData[2] == 70 && audioData[3] == 70) { return ".wav"; } if ((audioData[0] == 73 && audioData[1] == 68 && audioData[2] == 51) || (audioData[0] == byte.MaxValue && (audioData[1] & 0xE0) == 224)) { return ".mp3"; } return ".ogg"; } private static AudioType GetAudioType(string extension) { if (!(extension == ".wav")) { if (extension == ".mp3") { return (AudioType)13; } return (AudioType)14; } return (AudioType)20; } } [HarmonyPatch(typeof(MusicMan), "Awake")] public static class MusicMan_Awake_Patch { private static void Postfix(MusicMan __instance) { if (!Plugin.ModEnabled.Value) { return; } for (int i = 0; i < __instance.m_music.Count; i++) { NamedMusic instance = __instance.m_music[i]; string value = ReflectionCache.GetValue<string>(instance, "m_name"); if (Plugin.CustomMusicList.ContainsKey(value)) { AudioClip[] value2 = Plugin.CustomMusicList[value].Values.ToArray(); ReflectionCache.SetValue(instance, "m_clips", value2); continue; } string text = StringMatchCache.FindMatch(value); if (text != null && Plugin.CustomMusicList.ContainsKey(text)) { AudioClip[] value3 = Plugin.CustomMusicList[text].Values.ToArray(); ReflectionCache.SetValue(instance, "m_clips", value3); continue; } AudioClip[] value4 = ReflectionCache.GetValue<AudioClip[]>(instance, "m_clips"); if (value4 == null) { continue; } for (int j = 0; j < value4.Length; j++) { if ((Object)(object)value4[j] != (Object)null && Plugin.CustomMusic.ContainsKey(((Object)value4[j]).name)) { value4[j] = Plugin.CustomMusic[((Object)value4[j]).name]; } } } } } [HarmonyPatch(typeof(MusicMan), "UpdateMusic")] public static class MusicMan_UpdateMusic_Patch { private static void Prefix(ref object ___m_queuedMusic, AudioSource ___m_musicSource) { if (!Plugin.ModEnabled.Value) { return; } if (___m_queuedMusic != null) { ReflectionCache.SetValue(___m_queuedMusic, "m_volume", Plugin.MusicVolume.Value); } if ((Object)(object)___m_musicSource != (Object)null && ___m_musicSource.loop) { if (!___m_musicSource.isPlaying) { ___m_musicSource.loop = false; } else if ((Object)(object)___m_musicSource.clip != (Object)null && ___m_musicSource.clip.length - ___m_musicSource.time < 0.5f) { ___m_musicSource.loop = false; } } } } [HarmonyPatch(typeof(MusicLocation), "Awake")] public static class MusicLocation_Awake_Patch { private static void Postfix(ref AudioSource ___m_audioSource, ref float ___m_baseVolume) { if (!Plugin.ModEnabled.Value || (Object)(object)___m_audioSource == (Object)null || (Object)(object)___m_audioSource.clip == (Object)null) { return; } string name = ((Object)___m_audioSource.clip).name; if (!Plugin.CustomMusic.ContainsKey(name)) { return; } AudioClip val = Plugin.CustomMusic[name]; if (!((Object)(object)val == (Object)null) && !(val.length <= 0f)) { bool isPlaying = ___m_audioSource.isPlaying; if (isPlaying) { ___m_audioSource.Stop(); } ___m_audioSource.clip = val; ___m_audioSource.time = 0f; ___m_baseVolume *= Plugin.LocationVolMultiplier.Value; if (isPlaying) { ___m_audioSource.Play(); } } } } [HarmonyPatch(typeof(AudioMan), "Awake")] public static class AudioMan_Awake_Patch { private static void Postfix(AudioMan __instance, IList ___m_randomAmbients, AudioSource ___m_oceanAmbientSource, AudioSource ___m_windLoopSource) { if (Plugin.ModEnabled.Value) { for (int i = 0; i < ___m_randomAmbients.Count; i++) { object? obj = ___m_randomAmbients[i]; string value = ReflectionCache.GetValue<string>(obj, "m_name"); ReplaceAmbientClips(obj, "m_randomAmbientClips"); ReplaceAmbientClips(obj, "m_randomAmbientClipsDay"); ReplaceAmbientClips(obj, "m_randomAmbientClipsNight"); ReplaceAmbientList(obj, value, "_day", "m_randomAmbientClipsDay"); ReplaceAmbientList(obj, value, "_night", "m_randomAmbientClipsNight"); ReplaceAmbientList(obj, value, "", "m_randomAmbientClips"); } if (Plugin.CustomAmbient.ContainsKey("ocean")) { ___m_oceanAmbientSource.clip = Plugin.CustomAmbient["ocean"]; } if (Plugin.CustomAmbient.ContainsKey("wind")) { ___m_windLoopSource.clip = Plugin.CustomAmbient["wind"]; } } } private static void ReplaceAmbientClips(object ambient, string fieldName) { IList value = ReflectionCache.GetValue<IList>(ambient, fieldName); if (value == null) { return; } for (int i = 0; i < value.Count; i++) { object? obj = value[i]; AudioClip val = (AudioClip)((obj is AudioClip) ? obj : null); if ((Object)(object)val != (Object)null && Plugin.CustomAmbient.ContainsKey(((Object)val).name)) { value[i] = Plugin.CustomAmbient[((Object)val).name]; } } } private static void ReplaceAmbientList(object ambient, string ambientName, string suffix, string fieldName) { string key = ambientName + suffix; if (!Plugin.CustomAmbientList.ContainsKey(key)) { return; } IList value = ReflectionCache.GetValue<IList>(ambient, fieldName); if (value == null) { return; } List<AudioClip> list = Plugin.CustomAmbientList[key].Values.ToList(); value.Clear(); foreach (AudioClip item in list) { value.Add(item); } } } [HarmonyPatch(typeof(AudioMan), "QueueAmbientLoop")] public static class AudioMan_QueueAmbientLoop_Patch { private static void Prefix(ref float ___m_queuedAmbientVol, ref float ___m_ambientVol, ref float vol) { if (Plugin.ModEnabled.Value) { vol = Plugin.AmbientVolume.Value; ___m_ambientVol = Plugin.AmbientVolume.Value; ___m_queuedAmbientVol = Plugin.AmbientVolume.Value; } } } [HarmonyPatch(typeof(ZSFX), "Awake")] public static class ZSFX_Awake_Patch { private static void Postfix(ZSFX __instance) { if (!Plugin.ModEnabled.Value) { return; } string zSFXName = AudioUtils.GetZSFXName(__instance); if (Plugin.CustomSFXList.TryGetValue(zSFXName, out var value)) { __instance.m_audioClips = (from x in value orderby x.Key select x.Value).ToArray(); } else { if (__instance.m_audioClips == null) { return; } for (int i = 0; i < __instance.m_audioClips.Length; i++) { if ((Object)(object)__instance.m_audioClips[i] != (Object)null && Plugin.CustomSFX.ContainsKey(((Object)__instance.m_audioClips[i]).name)) { __instance.m_audioClips[i] = Plugin.CustomSFX[((Object)__instance.m_audioClips[i]).name]; } } } } } [HarmonyPatch(typeof(TeleportWorld), "Awake")] public static class TeleportWorld_Awake_Patch { private static void Postfix(TeleportWorld __instance) { if (!Plugin.ModEnabled.Value || !Plugin.CustomSFX.ContainsKey("portal")) { return; } AudioSource componentInChildren = ((Component)__instance).GetComponentInChildren<AudioSource>(); if (!((Object)(object)componentInChildren != (Object)null)) { return; } AudioClip val = Plugin.CustomSFX["portal"]; if (!((Object)(object)val == (Object)null) && !(val.length <= 0f)) { bool isPlaying = componentInChildren.isPlaying; componentInChildren.Stop(); componentInChildren.clip = val; componentInChildren.time = 0f; if (isPlaying || componentInChildren.playOnAwake) { componentInChildren.Play(); } } } } [HarmonyPatch(typeof(Fireplace), "Start")] public static class Fireplace_Start_Patch { private static void Postfix(Fireplace __instance) { if (!Plugin.ModEnabled.Value) { return; } string name = ((Object)__instance).name; AudioSource val = null; AudioClip val2 = null; if (name.Contains("groundtorch") && Plugin.CustomSFX.ContainsKey("groundtorch")) { GameObject enabledObject = __instance.m_enabledObject; val = ((enabledObject != null) ? enabledObject.GetComponentInChildren<AudioSource>() : null); val2 = Plugin.CustomSFX["groundtorch"]; } else if (name.Contains("walltorch") && Plugin.CustomSFX.ContainsKey("walltorch")) { GameObject enabledObjectHigh = __instance.m_enabledObjectHigh; val = ((enabledObjectHigh != null) ? enabledObjectHigh.GetComponentInChildren<AudioSource>() : null); if ((Object)(object)val == (Object)null) { GameObject enabledObject2 = __instance.m_enabledObject; val = ((enabledObject2 != null) ? enabledObject2.GetComponentInChildren<AudioSource>() : null); } val2 = Plugin.CustomSFX["walltorch"]; } else if (name.Contains("fire_pit") && Plugin.CustomSFX.ContainsKey("fire_pit")) { GameObject enabledObjectHigh2 = __instance.m_enabledObjectHigh; val = ((enabledObjectHigh2 != null) ? enabledObjectHigh2.GetComponentInChildren<AudioSource>() : null); val2 = Plugin.CustomSFX["fire_pit"]; } else if (name.Contains("bonfire") && Plugin.CustomSFX.ContainsKey("bonfire")) { GameObject enabledObjectHigh3 = __instance.m_enabledObjectHigh; val = ((enabledObjectHigh3 != null) ? enabledObjectHigh3.GetComponentInChildren<AudioSource>() : null); val2 = Plugin.CustomSFX["bonfire"]; } else if (name.Contains("hearth") && Plugin.CustomSFX.ContainsKey("hearth")) { GameObject enabledObjectHigh4 = __instance.m_enabledObjectHigh; val = ((enabledObjectHigh4 != null) ? enabledObjectHigh4.GetComponentInChildren<AudioSource>() : null); val2 = Plugin.CustomSFX["hearth"]; } if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null && val2.length > 0f) { SafeReplaceClip(val, val2); } } private static void SafeReplaceClip(AudioSource source, AudioClip newClip) { bool isPlaying = source.isPlaying; source.Stop(); source.clip = newClip; source.time = 0f; if (isPlaying || source.playOnAwake) { source.Play(); } } } [HarmonyPatch(typeof(EnvMan), "Awake")] public static class EnvMan_Awake_Patch { private static void Postfix(EnvMan __instance) { if (!Plugin.ModEnabled.Value) { return; } for (int i = 0; i < __instance.m_environments.Count; i++) { string name = __instance.m_environments[i].m_name; foreach (string key in Plugin.CustomMusicList.Keys) { string text = name.ToLower(); string text2 = key.ToLower(); if (text == text2 || text.Contains(text2) || text2.Contains(text)) { string name2 = __instance.m_environments[i].m_name; __instance.m_environments[i].m_name = key; AddMusicToEnvironment(__instance, i, ""); __instance.m_environments[i].m_name = name2; break; } } AddMusicToEnvironment(__instance, i, "Morning"); AddMusicToEnvironment(__instance, i, "Day"); AddMusicToEnvironment(__instance, i, "Evening"); AddMusicToEnvironment(__instance, i, "Night"); } } private static void AddMusicToEnvironment(EnvMan envMan, int index, string timeOfDay) { string text = envMan.m_environments[index].m_name + timeOfDay; if (Plugin.CustomMusicList.ContainsKey(text)) { switch (timeOfDay) { case "Morning": envMan.m_environments[index].m_musicMorning = text; break; case "Day": envMan.m_environments[index].m_musicDay = text; break; case "Evening": envMan.m_environments[index].m_musicEvening = text; break; case "Night": envMan.m_environments[index].m_musicNight = text; break; } object obj = Activator.CreateInstance(((object)MusicMan.instance.m_music[0]).GetType()); ReflectionCache.SetValue(obj, "m_name", text); ReflectionCache.SetValue(obj, "m_clips", (from x in Plugin.CustomMusicList[text] orderby x.Key select x.Value).ToArray()); ReflectionCache.SetValue(obj, "m_loop", true); ReflectionCache.SetValue(obj, "m_ambientMusic", true); ReflectionCache.SetValue(obj, "m_resume", true); ((IList)MusicMan.instance.m_music).Add(obj); } } } [HarmonyPatch(typeof(Terminal), "InputText")] public static class Terminal_InputText_Patch { private static bool Prefix(Terminal __instance) { if (!Plugin.ModEnabled.Value) { return true; } FieldInfo field = ((object)__instance).GetType().GetField("m_input"); if (field == null) { return true; } object value = field.GetValue(__instance); if (value == null) { return true; } PropertyInfo property = value.GetType().GetProperty("text"); if (property == null) { return true; } string text = (string)property.GetValue(value); switch (text.ToLower()) { case "dmcatmosthere reset": case "atmosphere reset": ((BaseUnityPlugin)Plugin.instance).Config.Reload(); ((BaseUnityPlugin)Plugin.instance).Config.Save(); AddOutput(__instance, text); AddOutput(__instance, "<color=#00BFFF>DMC Atmosphere config reloaded</color>"); return false; case "dmcatmosthere music": case "atmosphere 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 "dmcatmosthere env": case "atmosphere 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("dmc.DMCAtmosthere", "DMC Atmosphere", "1.0.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.LogInfo((object)"<color=#00BFFF>DMC Atmosphere v1.0.0 loaded</color>"); } catch (Exception ex) { Log.LogError((object)("<color=#00BFFF>DMC Atmosphere critical error: " + ex.Message + "</color>")); } } } public static class AudioUtils { public static string GetZSFXName(ZSFX zsfx) { string name = ((Object)zsfx).name; char[] anyOf = new char[2] { '(', ' ' }; int num = name.IndexOfAny(anyOf); if (num == -1) { return name; } return name.Remove(num); } public static void SafeSetVolume(AudioSource source, float volume) { if ((Object)(object)source != (Object)null) { source.volume = Mathf.Clamp01(volume); } } } public static class ReflectionCache { private static readonly Dictionary<string, FieldInfo> _fieldCache = new Dictionary<string, FieldInfo>(); private static readonly Dictionary<string, PropertyInfo> _propertyCache = new Dictionary<string, PropertyInfo>(); public static FieldInfo GetField(Type type, string fieldName) { string key = type.FullName + "." + fieldName; if (!_fieldCache.ContainsKey(key)) { _fieldCache[key] = type.GetField(fieldName); } return _fieldCache[key]; } public static void SetValue(object instance, string fieldName, object value) { GetField(instance.GetType(), fieldName)?.SetValue(instance, value); } public static T GetValue<T>(object instance, string fieldName) { FieldInfo field = GetField(instance.GetType(), fieldName); if (!(field != null)) { return default(T); } return (T)field.GetValue(instance); } } public static class StringMatchCache { private static readonly Dictionary<string, string> _lowerCaseCache = new Dictionary<string, string>(); private static readonly Dictionary<string, string> _musicMappingCache = new Dictionary<string, string>(); private static bool _initialized = false; public static string GetLowerCase(string input) { if (string.IsNullOrEmpty(input)) { return string.Empty; } if (!_lowerCaseCache.ContainsKey(input)) { _lowerCaseCache[input] = input.ToLower(); } return _lowerCaseCache[input]; } public static void Initialize() { if (!_initialized) { AddMapping("menu", "menu", "start", "intro", "mainmenu", "main_menu", "title"); AddMapping("meadows", "meadow", "meadows"); AddMapping("blackforest", "forest", "dark", "blackforest"); AddMapping("swamp", "swamp"); _initialized = true; } } private static void AddMapping(string target, params string[] sources) { foreach (string text in sources) { _musicMappingCache[text + "->" + target] = target; } } public static string FindMatch(string musicName) { if (string.IsNullOrEmpty(musicName)) { return null; } if (Plugin.CustomMusicList.ContainsKey(musicName)) { return musicName; } if (_musicMappingCache.ContainsKey(musicName)) { return _musicMappingCache[musicName]; } string lowerCase = GetLowerCase(musicName); foreach (string key in Plugin.CustomMusicList.Keys) { string lowerCase2 = GetLowerCase(key); if (lowerCase.Contains(lowerCase2) || lowerCase2.Contains(lowerCase)) { _musicMappingCache[musicName] = key; return key; } } return null; } } }