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 Dropship Audio")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Custom Dropship Audio")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("39e6477f-12f9-439e-b179-914cf8dd5841")] [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 CustomDropshipAudio; public enum PlaybackMode { Random, PreventSongRepeat, Shuffle, Reshuffle } [BepInPlugin("com.donobus.CustomDropshipAudio", "Custom Dropship 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)"=== CustomDropshipAudio Loaded ==="); MusicFolderPath = ((BaseUnityPlugin)this).Config.Bind("General", "MusicFolderPath", "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Lethal Company\\Custom Dropship Audio", "The absolute path to the folder containing your custom .mp3 files."); ReloadKeybind = ((BaseUnityPlugin)this).Config.Bind("General", "ReloadKeybind", new KeyboardShortcut((KeyCode)286, 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 dropship 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 Dropship."); 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.CustomDropshipAudio"); 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) ItemDropshipPatch.EnsureMessageHandlerRegistered(); KeyboardShortcut value = ReloadKeybind.Value; if (((KeyboardShortcut)(ref value)).IsDown() && !isReloading) { Debug.Log((object)"[CustomDropshipAudio] Reload keybind pressed! Reloading audio files..."); ((MonoBehaviour)this).StartCoroutine(ReloadMusicRoutine()); } } private IEnumerator ReloadMusicRoutine() { isReloading = true; CustomMusicClips.Clear(); CustomMusicClipsByFileName.Clear(); ItemDropshipPatch.ResetStateForReload(); yield return ((MonoBehaviour)this).StartCoroutine(LoadAllCustomMusic()); isReloading = false; Debug.Log((object)"[CustomDropshipAudio] Reload complete!"); } private IEnumerator LoadAllCustomMusic() { string folderPath = MusicFolderPath.Value; if (!Directory.Exists(folderPath)) { Debug.LogWarning((object)"[CustomDropshipAudio] Folder path is invalid or does not exist."); yield break; } string[] mp3Files = GetValidMp3Files(folderPath); if (mp3Files.Length == 0) { Debug.LogWarning((object)"[CustomDropshipAudio] 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 = "CustomDropshipSong_" + fileNameNoExt; CustomMusicClips.Add(clip); if (!CustomMusicClipsByFileName.ContainsKey(fileNameNoExt)) { CustomMusicClipsByFileName.Add(fileNameNoExt, clip); } else { Debug.LogWarning((object)("[CustomDropshipAudio] Duplicate file name '" + fileNameNoExt + "' (case-sensitive match). Host Sync matching will only ever use the first one loaded.")); } Debug.Log((object)("[CustomDropshipAudio] Pre-loaded successfully: " + Path.GetFileName(file))); } else { Debug.LogError((object)("[CustomDropshipAudio] Failed to load " + Path.GetFileName(file) + ": " + uwr.error)); } } finally { ((IDisposable)uwr)?.Dispose(); } } Debug.Log((object)$"[CustomDropshipAudio] Total custom songs loaded into rotation: {CustomMusicClips.Count}"); if (CustomMusicClips.Count > 0) { ItemDropshipPatch.GenerateShuffleDeck(checkBoundaryRepeat: false); } } private string[] GetValidMp3Files(string path) { try { return Directory.GetFiles(path, "*.mp3"); } catch { return new string[0]; } } } [HarmonyPatch(typeof(ItemDropship))] public class ItemDropshipPatch { private static AudioClip activeSong = null; private static AudioClip lastPlayedSong = null; private static List musicSources = new List(); private static bool hasCachedSources = false; private static bool wasLanded = false; private static Random rng = new Random(); private static string hostSyncedSongName = null; private static bool receivedHostSync = false; private static bool hostAlreadySelected = false; private static bool hostHasBroadcastedSync = false; private static List shuffleDeck = new List(); private static int shufflePointer = 0; public const string ClipNamePrefix = "CustomDropshipSong_"; private const string SyncMessageName = "CustomDropshipAudio_SongSync"; private static NetworkManager registeredOnManager = null; public static void ResetStateForReload() { activeSong = null; lastPlayedSong = null; hostAlreadySelected = false; receivedHostSync = false; hostSyncedSongName = null; hostHasBroadcastedSync = false; foreach (AudioSource musicSource in musicSources) { if ((Object)(object)musicSource != (Object)null) { musicSource.Stop(); musicSource.clip = null; } } musicSources.Clear(); hasCachedSources = false; shuffleDeck.Clear(); shufflePointer = 0; Debug.Log((object)"[CustomDropshipAudio] Patch variables reset for incoming reload."); } [HarmonyPatch("Start")] [HarmonyPrefix] private static void StartPrefix() { musicSources.Clear(); hasCachedSources = false; wasLanded = false; hostAlreadySelected = false; hostHasBroadcastedSync = false; EnsureMessageHandlerRegistered(); if (Plugin.CustomMusicClips.Count > 0 && (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost) { ChooseSongLocally(); } } [HarmonyPatch("ShipLandedAnimationEvent")] [HarmonyPostfix] private static void ShipLandedPostfix(ItemDropship __instance) { EnforceCustomMusic(); } [HarmonyPatch("Update")] [HarmonyPostfix] private static void UpdatePostfix(ItemDropship __instance) { if (Plugin.CustomMusicClips.Count == 0) { 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 (!hasCachedSources) { AudioSource[] componentsInChildren = ((Component)__instance).GetComponentsInChildren(true); AudioSource[] array = componentsInChildren; foreach (AudioSource val in array) { if ((Object)(object)val == (Object)null) { continue; } string text = (((Object)(object)val.clip != (Object)null) ? ((Object)val.clip).name.ToLower() : ""); if (text.Contains("icecream") || text.Contains("dropshipmusic") || text.StartsWith("CustomDropshipSong_".ToLower())) { if (val.isPlaying && text.Contains("icecream")) { val.Stop(); } if ((Object)(object)val.clip != (Object)null && (text.Contains("icecream") || text.Contains("dropshipmusic"))) { val.clip = null; } if (!musicSources.Contains(val)) { musicSources.Add(val); } } } if (musicSources.Count > 0) { hasCachedSources = true; if (__instance.shipLanded) { EnforceCustomMusic(); } } } if (musicSources.Count == 0) { return; } if (wasLanded && !__instance.shipLanded) { foreach (AudioSource musicSource in musicSources) { if ((Object)(object)musicSource != (Object)null && (Object)(object)musicSource.clip == (Object)(object)activeSong && (Object)(object)activeSong != (Object)null) { musicSource.volume = 0f; musicSource.Stop(); } } activeSong = null; receivedHostSync = false; hostSyncedSongName = null; hostAlreadySelected = false; hostHasBroadcastedSync = false; } wasLanded = __instance.shipLanded; if (__instance.shipLanded) { EnforceCustomMusic(); return; } foreach (AudioSource musicSource2 in musicSources) { if (!((Object)(object)musicSource2 == (Object)null) && ((Component)musicSource2).gameObject.activeInHierarchy && ((Behaviour)musicSource2).enabled && (Object)(object)musicSource2.clip != (Object)null && ((Object)musicSource2.clip).name.ToLower().StartsWith("CustomDropshipSong_".ToLower())) { musicSource2.volume = 0f; if (musicSource2.isPlaying) { musicSource2.Stop(); } } } } 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[CustomDropshipAudio] --- NEW 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 EnforceCustomMusic() { if (Plugin.CustomMusicClips.Count == 0 || musicSources.Count == 0) { return; } if (!Plugin.EnableHostSync.Value && receivedHostSync) { Debug.Log((object)"[CustomDropshipAudio] 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)"Client has host sync data but no active clip. Reapplying."); ApplyHostSyncedSong(hostSyncedSongName); } else { Debug.Log((object)"Waiting for host song selection..."); } return; } ChooseSongLocally(); } ApplyClipToSources(); } private static void ChooseSongLocally() { if (Plugin.CustomMusicClips.Count == 0 || (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)"[CustomDropshipAudio] Deck exhausted. Reshuffling playlist with repeat boundary guards!"); GenerateShuffleDeck(checkBoundaryRepeat: true); } else { Debug.Log((object)"[CustomDropshipAudio] Deck exhausted. Standard Shuffle looping back to start of fixed deck."); shufflePointer = 0; } } if (shuffleDeck.Count > 0) { num = shuffleDeck[shufflePointer]; activeSong = Plugin.CustomMusicClips[num]; shufflePointer++; Debug.Log((object)$"[CustomDropshipAudio] {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)("[CustomDropshipAudio] 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; Debug.Log((object)$"[CustomDropshipAudio] {GetRoleTag()} CHOSEN SONG ({num + 1} of {Plugin.CustomMusicClips.Count}): {((Object)activeSong).name}"); } private static void ApplyClipToSources() { if ((Object)(object)activeSong == (Object)null) { return; } foreach (AudioSource musicSource in musicSources) { if (!((Object)(object)musicSource == (Object)null) && ((Component)musicSource).gameObject.activeInHierarchy && ((Behaviour)musicSource).enabled) { if ((Object)(object)musicSource.clip != (Object)(object)activeSong) { musicSource.clip = activeSong; musicSource.time = 0f; } musicSource.loop = true; musicSource.volume = Plugin.MusicVolume.Value; musicSource.dopplerLevel = (Plugin.EnableDoppler.Value ? 1f : 0f); if (!musicSource.isPlaying) { musicSource.Play(); } } } } 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("CustomDropshipSong_", StringComparison.Ordinal)) { return ((Object)clip).name.Substring("CustomDropshipSong_".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("CustomDropshipAudio_SongSync", new HandleNamedMessageDelegate(OnSongSyncMessageReceived)); registeredOnManager = NetworkManager.Singleton; Debug.Log((object)("[CustomDropshipAudio] " + GetRoleTag() + " 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("CustomDropshipAudio_SongSync", val, (NetworkDelivery)2); } finally { ((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose(); } Debug.Log((object)("[CustomDropshipAudio] " + GetRoleTag() + " 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)("[CustomDropshipAudio] " + GetRoleTag() + " Host Sync: received broadcast for '" + text + "'.")); if ((Object)(object)NetworkManager.Singleton == (Object)null || NetworkManager.Singleton.IsHost) { return; } if (!Plugin.EnableHostSync.Value) { Debug.Log((object)("[CustomDropshipAudio] " + GetRoleTag() + " Host Sync: EnableHostSync is off locally, ignoring broadcast.")); } else if (text == "") { Debug.Log((object)("[CustomDropshipAudio] " + GetRoleTag() + " 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)("[CustomDropshipAudio] " + GetRoleTag() + " Using host synced song '" + hostFileName + "'.")); } else { Debug.Log((object)("[CustomDropshipAudio] " + GetRoleTag() + " Missing '" + hostFileName + "', using local selection.")); receivedHostSync = false; activeSong = null; ChooseSongLocally(); } ApplyClipToSources(); } }