using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Logging; using HarmonyLib; 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: AssemblyTitle("BM_CustomSounds")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BM_CustomSounds")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("e9cb9f1f-ebdc-4507-b444-1da2e74702d4")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace BM_CustomSounds; public static class AudioLoader { private static ManualLogSource logger; private static bool loading; private static bool loaded; public static void Initialize(ManualLogSource log) { logger = log; loading = false; loaded = false; } public static IEnumerator LoadAllSounds(string directory, StartOfRound round) { if (loading) { yield break; } if (loaded) { ResetForRound(round); yield break; } loading = true; SoundRegistry.Clear(); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); logger.LogInfo((object)("Created sound directory: " + directory)); loading = false; loaded = true; yield break; } string[] files = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories); Array.Sort(files, (IComparer?)StringComparer.OrdinalIgnoreCase); int supportedFiles = 0; int loadedFiles = 0; string[] array = files; foreach (string path in array) { AudioType audioType = GetAudioType(path); if ((int)audioType != 0) { supportedFiles++; AudioClip loadedClip = null; yield return LoadClip(path, audioType, delegate(AudioClip clip) { loadedClip = clip; }); if (!((Object)(object)loadedClip == (Object)null)) { string fileName = Path.GetFileNameWithoutExtension(path); string groupName = SoundRegistry.GetGroupName(fileName); ((Object)loadedClip).name = fileName; SoundRegistry.RegisterClip(groupName, loadedClip); loadedFiles++; } } } loading = false; loaded = true; if (supportedFiles == 0) { logger.LogInfo((object)"No custom sounds found. Vanilla audio will be used."); yield break; } logger.LogWarning((object)"### BM_CUSTOMSOUNDS AUDIO READY ###"); logger.LogInfo((object)("Loaded " + loadedFiles + "/" + supportedFiles + " custom audio file(s).")); SoundRegistry.LogSummary(); ResetForRound(round); } private static void ResetForRound(StartOfRound round) { int num = 7319; if ((Object)(object)round != (Object)null) { num += round.randomMapSeed; } SoundRandomizer.Reset(num); SoundRegistry.ResetShuffleBags(); logger.LogInfo((object)("Custom sound randomizer initialized. Seed=" + num)); } private static IEnumerator LoadClip(string path, AudioType audioType, Action callback) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) string uri; try { uri = new Uri(path).AbsoluteUri; } catch (Exception ex) { Exception ex2 = ex; logger.LogError((object)("Invalid audio path: " + path + " | " + ex2.Message)); callback(null); yield break; } UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(uri, audioType); try { yield return request.SendWebRequest(); if ((int)request.result != 1) { logger.LogError((object)("Failed to load '" + Path.GetFileName(path) + "': " + request.error)); callback(null); yield break; } AudioClip clip = DownloadHandlerAudioClip.GetContent(request); if ((Object)(object)clip == (Object)null) { logger.LogError((object)("AudioClip was null: " + path)); callback(null); yield break; } callback(clip); } finally { ((IDisposable)request)?.Dispose(); } } private static AudioType GetAudioType(string path) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) return (AudioType)(Path.GetExtension(path).ToLowerInvariant() switch { ".ogg" => 14, ".wav" => 20, ".mp3" => 13, _ => 0, }); } } public static class Patches { [HarmonyPatch(typeof(StartOfRound), "Start")] private static class StartOfRoundPatch { [HarmonyPostfix] private static void Postfix(StartOfRound __instance) { if (!((Object)(object)__instance == (Object)null)) { Plugin.Log.LogWarning((object)"### BM_CUSTOMSOUNDS STARTOFROUND HIT ###"); ((MonoBehaviour)__instance).StartCoroutine(AudioLoader.LoadAllSounds(Plugin.SoundDirectory, __instance)); } } } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { })] private static class AudioSourcePlayPatch { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { ReplaceSourceClip(__instance); } } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { typeof(ulong) })] private static class AudioSourcePlayUlongPatch { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { ReplaceSourceClip(__instance); } } [HarmonyPatch(typeof(AudioSource), "Play", new Type[] { typeof(double) })] private static class AudioSourcePlayDoublePatch { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { ReplaceSourceClip(__instance); } } [HarmonyPatch(typeof(AudioSource), "PlayDelayed", new Type[] { typeof(float) })] private static class AudioSourcePlayDelayedPatch { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { ReplaceSourceClip(__instance); } } [HarmonyPatch(typeof(AudioSource), "PlayScheduled", new Type[] { typeof(double) })] private static class AudioSourcePlayScheduledPatch { [HarmonyPrefix] private static void Prefix(AudioSource __instance) { ReplaceSourceClip(__instance); } } [HarmonyPatch(typeof(AudioSource), "PlayOneShotHelper", new Type[] { typeof(AudioSource), typeof(AudioClip), typeof(float) })] private static class AudioSourcePlayOneShotHelperPatch { [HarmonyPrefix] private static void Prefix(AudioSource source, ref AudioClip clip, float volumeScale) { if (!((Object)(object)clip == (Object)null)) { clip = SoundRegistry.GetReplacement(clip); } } } private static void ReplaceSourceClip(AudioSource source) { if (!((Object)(object)source == (Object)null) && !((Object)(object)source.clip == (Object)null)) { AudioClip replacement = SoundRegistry.GetReplacement(source.clip); if (!((Object)(object)replacement == (Object)null) && !((Object)(object)replacement == (Object)(object)source.clip)) { source.clip = replacement; } } } } [BepInPlugin("com.brox.BM_CustomSounds", "BM_CustomSounds", "0.2.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.brox.BM_CustomSounds"; public const string ModName = "BM_CustomSounds"; public const string ModVersion = "0.2.0"; internal static ManualLogSource Log; private static bool initialized; internal static string SoundDirectory => Path.Combine(Paths.PluginPath, "Brox-BM_CustomSounds", "sounds"); private void Awake() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown if (!initialized) { initialized = true; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"BM_CustomSounds v0.2.0 initializing..."); SoundRegistry.Initialize(Log); AudioLoader.Initialize(Log); SoundRegistry.Clear(); Harmony val = new Harmony("com.brox.BM_CustomSounds"); val.PatchAll(typeof(Plugin).Assembly); Log.LogWarning((object)"### BM_CUSTOMSOUNDS HARMONY PATCHES INSTALLED ###"); Log.LogInfo((object)"BM_CustomSounds loaded."); } } } public class SoundGroup { private readonly List clips = new List(); private readonly Queue shuffleQueue = new Queue(); private AudioClip lastPlayed; public string Name { get; private set; } public int Count => clips.Count; public SoundGroup(string name) { Name = name; } public void AddClip(AudioClip clip) { if (!((Object)(object)clip == (Object)null)) { clips.Add(clip); } } public AudioClip GetNextClip(Random random) { if (clips.Count == 0) { return null; } if (clips.Count == 1) { lastPlayed = clips[0]; return clips[0]; } if (shuffleQueue.Count == 0) { RebuildShuffleQueue(random); } return lastPlayed = shuffleQueue.Dequeue(); } public void ResetShuffle(Random random) { shuffleQueue.Clear(); lastPlayed = null; if (clips.Count > 1) { RebuildShuffleQueue(random); } } private void RebuildShuffleQueue(Random random) { List list = new List(clips); for (int num = list.Count - 1; num > 0; num--) { int index = random.Next(num + 1); AudioClip value = list[num]; list[num] = list[index]; list[index] = value; } if ((Object)(object)lastPlayed != (Object)null && list.Count > 1 && (Object)(object)list[0] == (Object)(object)lastPlayed) { int index2 = random.Next(1, list.Count); AudioClip value2 = list[0]; list[0] = list[index2]; list[index2] = value2; } foreach (AudioClip item in list) { shuffleQueue.Enqueue(item); } } } public static class SoundRandomizer { private static Random random = new Random(); public static Random Random => random; public static void Reset() { random = new Random(); } public static void Reset(int seed) { random = new Random(seed); } } public static class SoundRegistry { private static readonly Dictionary groups = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary replacementGroups = new Dictionary(); private static ManualLogSource logger; private static readonly Regex VariantSuffix = new Regex("_(\\d+)$", RegexOptions.Compiled); public static int GroupCount => groups.Count; public static void Initialize(ManualLogSource log) { logger = log; } public static void Clear() { groups.Clear(); replacementGroups.Clear(); } public static string GetGroupName(string fileNameWithoutExtension) { if (string.IsNullOrEmpty(fileNameWithoutExtension)) { return fileNameWithoutExtension; } Match match = VariantSuffix.Match(fileNameWithoutExtension); if (!match.Success) { return fileNameWithoutExtension; } return fileNameWithoutExtension.Substring(0, match.Index); } public static void RegisterClip(string groupName, AudioClip clip) { if (!string.IsNullOrEmpty(groupName) && !((Object)(object)clip == (Object)null)) { if (!groups.TryGetValue(groupName, out var value)) { value = new SoundGroup(groupName); groups[groupName] = value; } value.AddClip(clip); replacementGroups[clip] = groupName; } } public static bool HasReplacement(string originalClipName) { if (string.IsNullOrEmpty(originalClipName)) { return false; } return groups.ContainsKey(originalClipName); } public static AudioClip GetReplacement(AudioClip original) { if ((Object)(object)original == (Object)null) { return null; } string key = ((Object)original).name; if (replacementGroups.TryGetValue(original, out var value)) { key = value; } if (!groups.TryGetValue(key, out var value2)) { return original; } AudioClip nextClip = value2.GetNextClip(SoundRandomizer.Random); if ((Object)(object)nextClip == (Object)null) { return original; } return nextClip; } public static void ResetShuffleBags() { foreach (KeyValuePair group in groups) { group.Value.ResetShuffle(SoundRandomizer.Random); } if (logger != null) { logger.LogInfo((object)"Sound shuffle bags reset."); } } public static void LogSummary() { if (logger == null) { return; } logger.LogInfo((object)("Loaded " + groups.Count + " custom sound group(s).")); foreach (KeyValuePair group in groups) { logger.LogInfo((object)(" " + group.Key + ": " + group.Value.Count + " variant(s)")); } } }