Decompiled source of CustomDropshipAudio v1.1.3

Custom Dropship Audio.dll

Decompiled a day ago
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<string> MusicFolderPath;

	public static List<AudioClip> CustomMusicClips = new List<AudioClip>();

	public static Dictionary<string, AudioClip> CustomMusicClipsByFileName = new Dictionary<string, AudioClip>(StringComparer.Ordinal);

	public static ConfigEntry<float> MusicVolume;

	public static ConfigEntry<bool> EnableDoppler;

	public static ConfigEntry<PlaybackMode> AudioPlaybackMode;

	public static ConfigEntry<bool> EnableHostSync;

	public static ConfigEntry<KeyboardShortcut> 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<string>("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<KeyboardShortcut>("General", "ReloadKeybind", new KeyboardShortcut((KeyCode)286, Array.Empty<KeyCode>()), "Press this key in-game to instantly reload the music folder without restarting the game.");
		MusicVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Audio Settings", "MusicVolume", 1f, "The volume of the custom dropship music (0.0 to 1.0).");
		EnableDoppler = ((BaseUnityPlugin)this).Config.Bind<bool>("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<PlaybackMode>("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<bool>("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<AudioSource> musicSources = new List<AudioSource>();

	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<int> shuffleDeck = new List<int>();

	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("<HOST_SYNC_DISABLED>");
			}
			hostHasBroadcastedSync = true;
		}
		if (!hasCachedSources)
		{
			AudioSource[] componentsInChildren = ((Component)__instance).GetComponentsInChildren<AudioSource>(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 == "<HOST_SYNC_DISABLED>")
		{
			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();
	}
}