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 BepInEx; using BepInEx.Configuration; using HarmonyLib; using Unity.Collections; using Unity.Netcode; 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("Custom Elevator Audio")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Custom Elevator Audio")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("bb9da844-988b-4075-bc3b-20396c69ea66")] [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 CustomElevatorAudio; public enum PlaybackMode { Random, PreventSongRepeat, Shuffle, Reshuffle } [BepInPlugin("com.donobus.CustomElevatorAudio", "Custom Elevator Audio", "1.0.0")] public class Plugin : BaseUnityPlugin { private Harmony harmony; public static ConfigEntry MusicFolderPath; public static List CustomMusicClips = new List(); public static Dictionary CustomMusicClipsByFileName = new Dictionary(StringComparer.Ordinal); public static ConfigEntry MusicVolume; public static ConfigEntry EnableDoppler; public static ConfigEntry AudioPlaybackMode; public static ConfigEntry EnableHostSync; public static ConfigEntry ReloadKeybind; private bool isReloading = false; private void Awake() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Expected O, but got Unknown ((BaseUnityPlugin)this).Logger.LogInfo((object)"=== CustomElevatorAudio Loaded ==="); MusicFolderPath = ((BaseUnityPlugin)this).Config.Bind("General", "MusicFolderPath", "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Lethal Company\\Custom Elevator Audio", "The absolute path to the folder containing your custom .mp3 files."); ReloadKeybind = ((BaseUnityPlugin)this).Config.Bind("General", "ReloadKeybind", new KeyboardShortcut((KeyCode)287, Array.Empty()), "Press this key in-game to instantly reload the music folder without restarting the game."); MusicVolume = ((BaseUnityPlugin)this).Config.Bind("Audio Settings", "MusicVolume", 1f, "The volume of the custom elevator music (0.0 to 1.0)."); EnableDoppler = ((BaseUnityPlugin)this).Config.Bind("Audio Settings", "EnableDoppler", true, "Set to false to disable the pitch-bending doppler effect as you move relative to the Elevator."); AudioPlaybackMode = ((BaseUnityPlugin)this).Config.Bind("Audio Settings", "PlaybackMode", PlaybackMode.PreventSongRepeat, "Choose how songs are selected: Random (pure randomness), PreventSongRepeat (no back-to-back repeats), Shuffle (loops one fixed randomized list), or Reshuffle (creates a completely new random order every loop, ensuring no back-to-back repeats across boundaries)."); EnableHostSync = ((BaseUnityPlugin)this).Config.Bind("Audio Settings", "EnableHostSync", false, "If true AND you are the host, your chosen song is broadcast to all clients. If true on a client, that client will try to find a local file with the same name as the host's chosen song and play it; if no matching file exists locally, it falls back to that client's own PlaybackMode setting. Has no effect unless the HOST also has this enabled."); ((MonoBehaviour)this).StartCoroutine(LoadAllCustomMusic()); harmony = new Harmony("com.donobus.CustomElevatorAudio"); harmony.PatchAll(); } private void Update() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) MineshaftElevatorPatch.EnsureMessageHandlerRegistered(); KeyboardShortcut value = ReloadKeybind.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !isReloading) { Debug.Log((object)"[CustomElevatorAudio] Reload keybind pressed! Reloading audio files..."); ((MonoBehaviour)this).StartCoroutine(ReloadMusicRoutine()); } } private IEnumerator ReloadMusicRoutine() { isReloading = true; CustomMusicClips.Clear(); CustomMusicClipsByFileName.Clear(); MineshaftElevatorPatch.ResetStateForReload(); yield return ((MonoBehaviour)this).StartCoroutine(LoadAllCustomMusic()); isReloading = false; Debug.Log((object)"[CustomElevatorAudio] Reload complete!"); } private IEnumerator LoadAllCustomMusic() { string folderPath = MusicFolderPath.Value; if (!Directory.Exists(folderPath)) { Debug.LogWarning((object)"[CustomElevatorAudio] Folder path is invalid or does not exist."); yield break; } string[] mp3Files = GetValidMp3Files(folderPath); if (mp3Files.Length == 0) { Debug.LogWarning((object)"[CustomElevatorAudio] No .mp3 files found in folder."); yield break; } string[] array = mp3Files; foreach (string file in array) { string uri = "file://" + file.Replace("\\", "/"); string fileNameNoExt = Path.GetFileNameWithoutExtension(file); UnityWebRequest uwr = UnityWebRequestMultimedia.GetAudioClip(uri, (AudioType)13); try { yield return uwr.SendWebRequest(); if ((int)uwr.result == 1) { AudioClip clip = DownloadHandlerAudioClip.GetContent(uwr); ((Object)clip).name = "CustomElevatorSong_" + fileNameNoExt; CustomMusicClips.Add(clip); if (!CustomMusicClipsByFileName.ContainsKey(fileNameNoExt)) { CustomMusicClipsByFileName.Add(fileNameNoExt, clip); } else { Debug.LogWarning((object)("[CustomElevatorAudio] Duplicate file name '" + fileNameNoExt + "' (case-sensitive match). Host Sync matching will only ever use the first one loaded.")); } Debug.Log((object)("[CustomElevatorAudio] Pre-loaded successfully: " + Path.GetFileName(file))); } else { Debug.LogError((object)("[CustomElevatorAudio] Failed to load " + Path.GetFileName(file) + ": " + uwr.error)); } } finally { ((IDisposable)uwr)?.Dispose(); } } Debug.Log((object)$"[CustomElevatorAudio] Total custom songs loaded into rotation: {CustomMusicClips.Count}"); if (CustomMusicClips.Count > 0) { MineshaftElevatorPatch.GenerateShuffleDeck(checkBoundaryRepeat: false); } } private string[] GetValidMp3Files(string path) { try { return Directory.GetFiles(path, "*.mp3"); } catch { return new string[0]; } } } [HarmonyPatch(typeof(MineshaftElevatorController))] public class MineshaftElevatorPatch { private static AudioClip activeSong; private static AudioClip lastPlayedSong; private static bool wasPlayingMusic; private static Random rng; private static string hostSyncedSongName; private static bool receivedHostSync; private static bool hostAlreadySelected; private static bool hostHasBroadcastedSync; private static bool hasSelectedSongThisTrip; private static List shuffleDeck; private static int shufflePointer; public const string ClipNamePrefix = "CustomElevatorSong_"; private static FieldRef JingleField; private static FieldRef PlayMusicField; private static bool fieldAccessReady; private const string SyncMessageName = "CustomElevatorAudio_ElevatorSongSync"; private static NetworkManager registeredOnManager; static MineshaftElevatorPatch() { activeSong = null; lastPlayedSong = null; wasPlayingMusic = false; rng = new Random(); hostSyncedSongName = null; receivedHostSync = false; hostAlreadySelected = false; hostHasBroadcastedSync = false; hasSelectedSongThisTrip = false; shuffleDeck = new List(); shufflePointer = 0; fieldAccessReady = false; registeredOnManager = null; try { JingleField = AccessTools.FieldRefAccess("elevatorJingleMusic"); PlayMusicField = AccessTools.FieldRefAccess("playMusic"); fieldAccessReady = true; } catch (Exception arg) { Debug.LogError((object)$"[CustomElevatorAudio] Failed to bind to MineshaftElevatorController's private fields - elevator music support disabled. {arg}"); fieldAccessReady = false; } } public static void ResetStateForReload() { activeSong = null; lastPlayedSong = null; hostAlreadySelected = false; receivedHostSync = false; hostSyncedSongName = null; hostHasBroadcastedSync = false; wasPlayingMusic = false; hasSelectedSongThisTrip = false; shuffleDeck.Clear(); shufflePointer = 0; if (fieldAccessReady) { MineshaftElevatorController[] array = Object.FindObjectsOfType(); foreach (MineshaftElevatorController val in array) { AudioSource val2 = JingleField.Invoke(val); if ((Object)(object)val2 != (Object)null) { val2.Stop(); val2.clip = null; } } } Debug.Log((object)"[CustomElevatorAudio] Elevator patch variables reset for incoming reload."); } [HarmonyPatch("Update")] [HarmonyPostfix] private static void UpdatePostfix(MineshaftElevatorController __instance) { if (!fieldAccessReady || Plugin.CustomMusicClips.Count == 0) { return; } bool flag = PlayMusicField.Invoke(__instance); if ((Object)(object)StartOfRound.Instance != (Object)null && StartOfRound.Instance.inShipPhase) { return; } if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost && !hostHasBroadcastedSync && NetworkManager.Singleton.CustomMessagingManager != null) { if (Plugin.EnableHostSync.Value && (Object)(object)activeSong != (Object)null) { SendSongSyncToClients(GetFileNameForClip(activeSong)); } else if (!Plugin.EnableHostSync.Value) { SendSongSyncToClients(""); } hostHasBroadcastedSync = true; } if (flag && !wasPlayingMusic) { activeSong = null; receivedHostSync = false; hostSyncedSongName = null; hostAlreadySelected = false; hostHasBroadcastedSync = false; hasSelectedSongThisTrip = false; if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost) { ChooseSongLocally(); } } else if (!flag && wasPlayingMusic) { activeSong = null; hasSelectedSongThisTrip = false; hostHasBroadcastedSync = false; } wasPlayingMusic = flag; if (flag) { EnforceElevatorMusic(); return; } AudioSource val = JingleField.Invoke(__instance); if ((Object)(object)val != (Object)null && (Object)(object)val.clip != (Object)null && ((Object)val.clip).name.ToLower().StartsWith("CustomElevatorSong_".ToLower())) { val.volume = 0f; if (val.isPlaying) { val.Stop(); } } } private static void EnforceElevatorMusic() { if (Plugin.CustomMusicClips.Count == 0) { return; } if (!Plugin.EnableHostSync.Value && receivedHostSync) { Debug.Log((object)"[CustomElevatorAudio] EnableHostSync was disabled. Clearing sync state to resume local playback."); receivedHostSync = false; hostSyncedSongName = null; activeSong = null; } if ((Object)(object)activeSong == (Object)null) { if (Plugin.EnableHostSync.Value && (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsClient && !NetworkManager.Singleton.IsHost) { if (receivedHostSync) { Debug.Log((object)"[CustomElevatorAudio] Client has host sync data but no active clip. Reapplying."); ApplyHostSyncedSong(hostSyncedSongName); } else if (!NetworkManager.Singleton.IsConnectedClient) { Debug.Log((object)"[CustomElevatorAudio] Client not fully connected, falling back to local song selection."); ChooseSongLocally(); } else { Debug.Log((object)"[CustomElevatorAudio] Waiting for host song selection..."); } return; } ChooseSongLocally(); } ApplyClipToSources(); } public static void GenerateShuffleDeck(bool checkBoundaryRepeat) { if (Plugin.CustomMusicClips.Count == 0) { return; } shuffleDeck.Clear(); for (int i = 0; i < Plugin.CustomMusicClips.Count; i++) { shuffleDeck.Add(i); } int num = 0; bool flag = false; while (!flag && num < 50) { num++; int num2 = shuffleDeck.Count; while (num2 > 1) { num2--; int index = rng.Next(num2 + 1); int value = shuffleDeck[index]; shuffleDeck[index] = shuffleDeck[num2]; shuffleDeck[num2] = value; } flag = true; if (checkBoundaryRepeat && shuffleDeck.Count >= 2 && (Object)(object)lastPlayedSong != (Object)null) { AudioClip val = Plugin.CustomMusicClips[shuffleDeck[0]]; if ((Object)(object)val == (Object)(object)lastPlayedSong) { flag = false; } } } shufflePointer = 0; string text = $"\n[CustomElevatorAudio] --- NEW ELEVATOR SHUFFLE DECK GENERATED (Valid: {flag}, Attempts: {num}) ---"; for (int j = 0; j < shuffleDeck.Count; j++) { text += $"\n {j + 1}. {((Object)Plugin.CustomMusicClips[shuffleDeck[j]]).name}"; } text += "\n---------------------------------------------------------"; Debug.Log((object)text); } private static void ChooseSongLocally() { if (Plugin.CustomMusicClips.Count == 0 || hasSelectedSongThisTrip || (hostAlreadySelected && (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost)) { return; } int num = 0; PlaybackMode value = Plugin.AudioPlaybackMode.Value; if (value == PlaybackMode.Shuffle || value == PlaybackMode.Reshuffle) { if (shuffleDeck.Count != Plugin.CustomMusicClips.Count) { GenerateShuffleDeck(checkBoundaryRepeat: false); } if (shufflePointer >= shuffleDeck.Count) { if (value == PlaybackMode.Reshuffle) { Debug.Log((object)"[CustomElevatorAudio] Elevator deck exhausted. Reshuffling playlist with repeat boundary guards!"); GenerateShuffleDeck(checkBoundaryRepeat: true); } else { Debug.Log((object)"[CustomElevatorAudio] Elevator deck exhausted. Standard Shuffle looping back to start of fixed deck."); shufflePointer = 0; } } if (shuffleDeck.Count > 0) { num = shuffleDeck[shufflePointer]; AudioClip val = Plugin.CustomMusicClips[num]; if (Plugin.CustomMusicClips.Count >= 2 && (Object)(object)lastPlayedSong != (Object)null && (Object)(object)val == (Object)(object)lastPlayedSong) { Debug.Log((object)$"[CustomElevatorAudio] Elevator {value} Mode: Track index {num} ({((Object)val).name}) matches last played song. Skipping to next deck position."); shufflePointer++; if (shufflePointer >= shuffleDeck.Count) { if (value == PlaybackMode.Reshuffle) { GenerateShuffleDeck(checkBoundaryRepeat: true); } else { shufflePointer = 0; } } num = shuffleDeck[shufflePointer]; val = Plugin.CustomMusicClips[num]; } activeSong = val; shufflePointer++; Debug.Log((object)$"[CustomElevatorAudio] Elevator {value} Mode: Playing track index {num} ({shufflePointer}/{shuffleDeck.Count})"); } else { num = rng.Next(Plugin.CustomMusicClips.Count); activeSong = Plugin.CustomMusicClips[num]; } } else if (value == PlaybackMode.PreventSongRepeat && Plugin.CustomMusicClips.Count >= 2 && (Object)(object)lastPlayedSong != (Object)null) { Debug.Log((object)("[CustomElevatorAudio] Elevator anti-repeat active. Making sure we don't play: " + ((Object)lastPlayedSong).name)); do { num = rng.Next(Plugin.CustomMusicClips.Count); activeSong = Plugin.CustomMusicClips[num]; } while ((Object)(object)activeSong == (Object)(object)lastPlayedSong); } else { num = rng.Next(Plugin.CustomMusicClips.Count); activeSong = Plugin.CustomMusicClips[num]; } lastPlayedSong = activeSong; hostAlreadySelected = true; hasSelectedSongThisTrip = true; Debug.Log((object)$"[CustomElevatorAudio] {GetRoleTag()} ELEVATOR CHOSEN SONG ({num + 1} of {Plugin.CustomMusicClips.Count}): {((Object)activeSong).name}"); } [HarmonyPatch("Update")] [HarmonyPostfix] private static void ApplyClipToJingleSource(MineshaftElevatorController __instance) { if (!fieldAccessReady || (Object)(object)activeSong == (Object)null) { return; } AudioSource val = JingleField.Invoke(__instance); if (!((Object)(object)val == (Object)null) && ((Component)val).gameObject.activeInHierarchy && ((Behaviour)val).enabled) { if ((Object)(object)val.clip != (Object)(object)activeSong) { val.clip = activeSong; val.time = 0f; } val.loop = true; val.volume = Plugin.MusicVolume.Value; val.dopplerLevel = (Plugin.EnableDoppler.Value ? 1f : 0f); if (!val.isPlaying) { val.Play(); } } } private static void ApplyClipToSources(object _dummy = null) { } private static string GetRoleTag() { if ((Object)(object)NetworkManager.Singleton == (Object)null) { return "[NO-NET]"; } if (NetworkManager.Singleton.IsHost) { return "[HOST]"; } if (NetworkManager.Singleton.IsClient) { return "[CLIENT]"; } return "[NO-NET]"; } private static string GetFileNameForClip(AudioClip clip) { if ((Object)(object)clip == (Object)null) { return null; } if (((Object)clip).name.StartsWith("CustomElevatorSong_", StringComparison.Ordinal)) { return ((Object)clip).name.Substring("CustomElevatorSong_".Length); } return ((Object)clip).name; } internal static void EnsureMessageHandlerRegistered() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown if (!((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.CustomMessagingManager != null && !((Object)(object)registeredOnManager == (Object)(object)NetworkManager.Singleton)) { NetworkManager.Singleton.CustomMessagingManager.RegisterNamedMessageHandler("CustomElevatorAudio_ElevatorSongSync", new HandleNamedMessageDelegate(OnSongSyncMessageReceived)); registeredOnManager = NetworkManager.Singleton; Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator Host Sync message handler registered.")); } } private unsafe static void SendSongSyncToClients(string songFileName) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(songFileName) && !((Object)(object)NetworkManager.Singleton == (Object)null) && NetworkManager.Singleton.IsHost && NetworkManager.Singleton.CustomMessagingManager != null) { FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(FastBufferWriter.GetWriteSize(songFileName, false), (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(songFileName, false); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessageToAll("CustomElevatorAudio_ElevatorSongSync", val, (NetworkDelivery)2); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator Host Sync: broadcast song selection '" + songFileName + "' to all clients.")); } } private static void OnSongSyncMessageReceived(ulong senderClientId, FastBufferReader reader) { string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator Host Sync: received broadcast for '" + text + "'.")); if ((Object)(object)NetworkManager.Singleton == (Object)null || NetworkManager.Singleton.IsHost) { return; } if (!Plugin.EnableHostSync.Value) { Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator Host Sync: EnableHostSync is off locally, ignoring broadcast.")); } else if (text == "") { Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator: host explicitly broadcast that their sync is disabled. Proceeding locally.")); receivedHostSync = false; hostSyncedSongName = null; if ((Object)(object)activeSong == (Object)null) { ChooseSongLocally(); } } else { ApplyHostSyncedSong(text); } } private static void ApplyHostSyncedSong(string hostFileName) { hostSyncedSongName = hostFileName; receivedHostSync = true; if (!string.IsNullOrEmpty(hostFileName) && Plugin.CustomMusicClipsByFileName.TryGetValue(hostFileName, out var value)) { activeSong = value; lastPlayedSong = value; Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator: using host synced song '" + hostFileName + "'.")); } else { Debug.Log((object)("[CustomElevatorAudio] " + GetRoleTag() + " Elevator: missing '" + hostFileName + "', using local selection.")); receivedHostSync = false; activeSong = null; ChooseSongLocally(); } } }