Decompiled source of BigTunes v1.0.2
BepInEx/plugins/BigTunes/BigTunes.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using BepInEx.Unity.IL2CPP.Utils; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Mirror; using UnityEngine; using UnityEngine.Networking; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] [assembly: AssemblyCompany("BigTunes")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("BigTunes")] [assembly: AssemblyTitle("BigTunes")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public NullableAttribute(byte b) { } public NullableAttribute(byte[] b) { } } [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public NullableContextAttribute(byte b) { } } } namespace BigTunes { internal static class AudioCache { private static string _dir; internal static string Dir { get { if (_dir == null) { _dir = Path.Combine(BigTunesPlugin.DataDir, "cache"); Directory.CreateDirectory(_dir); } return _dir; } } internal static string PathFor(string videoId) { return Path.Combine(Dir, videoId + ".mp3"); } internal static void Touch(string videoId) { try { string path = PathFor(videoId); if (File.Exists(path)) { File.SetLastWriteTimeUtc(path, DateTime.UtcNow); } } catch (Exception ex) { BigTunesPlugin.Verbose("Cache touch failed: " + ex.Message); } } internal static void EvictIfNeeded() { try { long num = (long)Math.Max(0, BigTunesPlugin.CacheLimitMB.Value) * 1024L * 1024; if (num == 0L) { return; } List<FileInfo> list = (from f in new DirectoryInfo(Dir).GetFiles("*.mp3") orderby f.LastWriteTimeUtc select f).ToList(); long num2 = list.Sum((FileInfo f) => f.Length); if (num2 <= num) { return; } foreach (FileInfo item in list) { if (num2 > num) { long length = item.Length; try { item.Delete(); num2 -= length; BigTunesPlugin.Verbose("Evicted " + item.Name + " from cache."); } catch (Exception ex) { BigTunesPlugin.Verbose("Could not evict " + item.Name + ": " + ex.Message); } continue; } break; } } catch (Exception ex2) { BigTunesPlugin.Logger.LogWarning((object)("Cache eviction failed: " + ex2.Message)); } } } public class BigTunesBehaviour : MonoBehaviour { private struct Toast { public string Text; public float Expires; } internal static BigTunesBehaviour Instance; private static readonly ConcurrentQueue<Action> MainThreadWork = new ConcurrentQueue<Action>(); private static readonly List<Toast> Toasts = new List<Toast>(); private GUIStyle _style; private float _nextSyncReport; public BigTunesBehaviour(nint ptr) : base((IntPtr)ptr) { } internal static void OnMainThread(Action action) { if (action != null) { MainThreadWork.Enqueue(action); } } internal static void Say(string text, float seconds = 6f) { BigTunesPlugin.Logger.LogInfo((object)text); OnMainThread(delegate { Toasts.Add(new Toast { Text = text, Expires = Time.realtimeSinceStartup + seconds }); while (Toasts.Count > 5) { Toasts.RemoveAt(0); } }); } public void Awake() { Instance = this; BigTunesPlugin.Logger.LogInfo((object)"BigTunes runtime host created."); } public void Update() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown Action result; bool flag = default(bool); while (MainThreadWork.TryDequeue(out result)) { try { result(); } catch (Exception ex) { ManualLogSource logger = BigTunesPlugin.Logger; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(25, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Main-thread work failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex); } logger.LogError(val); } } try { TuneQueue.Tick(); } catch (Exception ex2) { ManualLogSource logger2 = BigTunesPlugin.Logger; BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(19, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Queue tick failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(ex2); } logger2.LogError(val); } if (Time.realtimeSinceStartup >= _nextSyncReport) { _nextSyncReport = Time.realtimeSinceStartup + 5f; try { SyncNet.SayHelloWhenReady(); } catch (Exception ex3) { BigTunesPlugin.Verbose("Hello failed: " + ex3.Message); } try { SyncNet.ReportWhenReady(); } catch (Exception ex4) { BigTunesPlugin.Verbose("Sync report failed: " + ex4.Message); } } } public void LateUpdate() { try { RadioBridge.UpdateDistanceVolume(); } catch (Exception ex) { BigTunesPlugin.Verbose("Volume update failed: " + ex.Message); } } public void OnGUI() { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) if (BigTunesPlugin.ShowOverlay == null || !BigTunesPlugin.ShowOverlay.Value) { return; } float now = Time.realtimeSinceStartup; Toasts.RemoveAll((Toast t) => t.Expires < now); string text = TuneQueue.StatusLine(); if (Toasts.Count == 0 && text == null) { return; } if (_style == null) { _style = new GUIStyle(GUI.skin.label) { fontSize = 15, alignment = (TextAnchor)0, wordWrap = false }; } _style.normal.textColor = Color.white; float num = 8f; if (text != null) { DrawShadowed(new Rect(10f, num, 900f, 22f), text); num += 22f; } foreach (Toast toast in Toasts) { DrawShadowed(new Rect(10f, num, 900f, 22f), toast.Text); num += 20f; } } private void DrawShadowed(Rect rect, string text) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) Rect val = new Rect(((Rect)(ref rect)).x + 1f, ((Rect)(ref rect)).y + 1f, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height); Color textColor = _style.normal.textColor; _style.normal.textColor = new Color(0f, 0f, 0f, 0.75f); GUI.Label(val, text, _style); _style.normal.textColor = textColor; GUI.Label(rect, text, _style); } internal static void Run(IEnumerator routine) { if ((Object)(object)Instance == (Object)null) { BigTunesPlugin.Logger.LogWarning((object)"No runtime host yet; coroutine dropped."); } else { MonoBehaviourExtensions.StartCoroutine((MonoBehaviour)(object)Instance, routine); } } } internal static class ClipLoader { private const int Chunk = 1048576; private const float Knee = 0.7f; internal static void Load(string filePath, Action<AudioClip> onLoaded) { BigTunesBehaviour.Run(LoadRoutine(filePath, onLoaded)); } private static IEnumerator LoadRoutine(string filePath, Action<AudioClip> onLoaded) { string absoluteUri = new Uri(filePath).AbsoluteUri; UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(absoluteUri, (AudioType)13); DownloadHandlerAudioClip val = ((Il2CppObjectBase)request.downloadHandler).TryCast<DownloadHandlerAudioClip>(); if (val != null) { val.streamAudio = false; } yield return request.SendWebRequest(); AudioClip clip = null; if ((int)request.result == 1) { try { clip = DownloadHandlerAudioClip.GetContent(request); if ((Object)(object)clip != (Object)null) { ((Object)clip).name = Path.GetFileNameWithoutExtension(filePath); } } catch (Exception ex) { BigTunesPlugin.Logger.LogError((object)("Decoding the clip failed: " + ex)); } } else { ManualLogSource logger = BigTunesPlugin.Logger; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(17, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Loading "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(filePath); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(request.error); } logger.LogError(val2); } request.Dispose(); if ((Object)(object)clip != (Object)null) { float num = Mathf.Max(1f, (float)BigTunesPlugin.MusicVolume.Value / 100f); if (num > 1.001f) { yield return Amplify(clip, num); } } onLoaded?.Invoke(clip); } private static IEnumerator Amplify(AudioClip clip, float boost) { int channels; int frames; try { channels = clip.channels; frames = clip.samples; } catch (Exception ex) { BigTunesPlugin.Verbose("[gain] could not read the clip: " + ex.Message); yield break; } if (channels <= 0 || frames <= 0) { yield break; } int framesPerPass = Mathf.Max(1, 1048576 / channels); float[] buffer = new float[framesPerPass * channels]; float started = Time.realtimeSinceStartup; int clipped = 0; for (int offset = 0; offset < frames; offset += framesPerPass) { int num = Mathf.Min(framesPerPass, frames - offset); int num2 = num * channels; float[] array = ((num == framesPerPass) ? buffer : new float[num2]); bool data; try { data = clip.GetData(Il2CppStructArray<float>.op_Implicit(array), offset); } catch (Exception ex2) { BigTunesPlugin.Verbose("[gain] GetData failed: " + ex2.Message); yield break; } if (!data) { yield break; } for (int i = 0; i < num2; i++) { float num3 = array[i] * boost; float num4 = ((num3 < 0f) ? (0f - num3) : num3); if (num4 > 0.7f) { float num5 = 0.7f + 0.3f * (float)Math.Tanh((num4 - 0.7f) / 0.3f); num3 = ((num3 < 0f) ? (0f - num5) : num5); clipped++; } array[i] = num3; } try { clip.SetData(Il2CppStructArray<float>.op_Implicit(array), offset); } catch (Exception ex3) { BigTunesPlugin.Verbose("[gain] SetData failed: " + ex3.Message); yield break; } yield return null; } BigTunesPlugin.Verbose($"[gain] {((Object)clip).name} amplified x{boost:F2} in {(Time.realtimeSinceStartup - started) * 1000f:F0}ms, {(float)clipped * 100f / (float)Mathf.Max(1, frames * channels):F1}% of samples past the knee."); } } [HarmonyPatch(typeof(TextChatSource), "AddMessage")] internal static class ChatAddMessagePatch { [HarmonyPrefix] private static bool Prefix(TextChatMessage __0) { //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown try { string text = null; try { text = ((__0 != null) ? __0.message : null); } catch { } if (string.IsNullOrWhiteSpace(text)) { return true; } if (BigTunesPlugin.VerboseLogging.Value) { string text2 = "?"; try { text2 = (((Object)(object)__0.sendingPlayer != (Object)null) ? ((Object)__0.sendingPlayer).name : "null"); } catch (Exception ex) { text2 = "threw " + ex.GetType().Name; } ManualLogSource logger = BigTunesPlugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(33, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[chat in] from="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" sync="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(SyncNet.IsSyncLine(text)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("cmd="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(Commands.IsCommandLine(text)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" raw=<"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(">"); } logger.LogInfo(val); } if (SyncNet.IsSyncLine(text)) { bool flag2 = !SyncNet.IsLocalSender(__0.sendingPlayer); if (flag2) { SyncNet.NoteRemoteSendObserved(); } return !SyncNet.Handle(text, flag2); } string sender = Commands.NameOf(__0.sendingPlayer); if (Commands.IsCommandLine(text)) { if (Commands.IsSharedCommand(text)) { Commands.HandleShared(text, sender, SyncNet.Now, __0.sendingPlayer); return true; } if (!SyncNet.IsLocalSender(__0.sendingPlayer)) { BigTunesPlugin.Verbose("[chat in] another player's personal command, ignored."); return false; } return !Commands.TryHandleLine(text, sender); } return true; } catch (Exception ex2) { BigTunesPlugin.Logger.LogError((object)("AddMessage handling failed: " + ex2)); return true; } } } [HarmonyPatch(typeof(TextChatSource), "SetOutput")] internal static class ChatOutputPatch { [HarmonyPrefix] private static bool Prefix(ref string __0) { try { if (string.IsNullOrEmpty(__0)) { return true; } string value = BigTunesPlugin.CommandPrefix.Value; if (string.IsNullOrEmpty(value) || (!__0.Contains(value) && !SyncNet.IsSyncLine(__0))) { return true; } List<string> list = new List<string>(); string[] array = __0.Split('\n'); foreach (string text in array) { if (!SyncNet.IsSyncLine(text) && (!Commands.IsCommandLine(text) || Commands.IsSharedCommand(text))) { list.Add(text); } } if (list.Count == 0) { return false; } __0 = string.Join("\n", list); } catch (Exception ex) { BigTunesPlugin.Logger.LogError((object)("SetOutput filtering failed: " + ex)); } return true; } } [HarmonyPatch(typeof(PlayerNetworking), "CmdSendTextChatMessage")] internal static class ChatCommandPatch { [HarmonyPrefix] private static bool Prefix(PlayerNetworking __instance, string __0) { try { BigTunesPlugin.Verbose("[chat out] CmdSendTextChatMessage(<" + __0 + ">)"); if (!Commands.IsCommandLine(__0)) { return true; } string text = "you"; try { text = (string.IsNullOrEmpty(__instance.username) ? text : __instance.username); } catch { } Commands.TryHandleLine(__0, text); return false; } catch (Exception ex) { BigTunesPlugin.Logger.LogError((object)("Chat command handling failed: " + ex)); return true; } } } [HarmonyPatch(typeof(RadioVoiceAssigner), "BroadcastTextChatOverThisChannel")] internal static class ChatBroadcastPatch { [HarmonyPrefix] private static bool Prefix(TextChatMessage __0) { try { string text = null; try { text = ((__0 != null) ? __0.message : null); } catch { } if (string.IsNullOrWhiteSpace(text)) { return true; } BigTunesPlugin.Verbose("[chat out] BroadcastTextChatOverThisChannel(<" + text + ">)"); if (SyncNet.IsSyncLine(text)) { return true; } if (!Commands.IsCommandLine(text)) { return true; } BigTunesPlugin.Verbose("[chat out] kept a command off the network: " + text); return false; } catch (Exception ex) { BigTunesPlugin.Logger.LogError((object)("Broadcast filtering failed: " + ex)); return true; } } } [HarmonyPatch(typeof(PlayerTexter), "CompleteInput")] internal static class TexterCompleteInputPatch { [HarmonyPrefix] private static bool Prefix(PlayerTexter __instance, string __0, ref bool __1) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) try { if (string.IsNullOrWhiteSpace(__0)) { return true; } BigTunesPlugin.Verbose("[chat send] CompleteInput(<" + __0 + ">)"); if (SyncNet.IsSyncLine(__0)) { return true; } if (!Commands.IsCommandLine(__0)) { return true; } if (!SyncNet.CanSend) { return true; } string sender = Commands.NameOf(LocalCharacter(__instance)); string[] array = __0.Trim().Substring(BigTunesPlugin.CommandPrefix.Value.Length).Trim() .Split(new char[1] { ' ' }, 2); string text = array[0].ToLowerInvariant(); string rest = ((array.Length > 1) ? array[1].Trim() : ""); if (Commands.Shared.Contains(text)) { double now = SyncNet.Now; Vector3 val = LocalPosition(__instance); SyncNet.AnnounceCommand(text, rest, now, val); Commands.RunShared(text, rest, sender, now, val); } else { Commands.TryHandleLine(__0, sender); } __1 = true; BigTunesPlugin.Verbose("[chat send] " + text + " kept off the wire entirely."); return false; } catch (Exception ex) { BigTunesPlugin.Logger.LogError((object)("CompleteInput handling failed: " + ex)); return true; } } private static PlayerCharacter LocalCharacter(PlayerTexter texter) { try { return (texter != null) ? texter.playerCharacter : null; } catch { return null; } } private static Vector3 LocalPosition(PlayerTexter texter) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) try { PlayerCharacter val = LocalCharacter(texter); if ((Object)(object)val != (Object)null) { return ((Component)val).transform.position; } } catch { } return RadioBridge.ListenerPosition(); } } [HarmonyPatch(typeof(PlayerTexter), "ReceieveMessage")] internal static class TexterReceiveProbe { [HarmonyPrefix] private static void Prefix(string __0) { BigTunesPlugin.Verbose("[chat recv] ReceieveMessage(<" + __0 + ">)"); } } [HarmonyPatch(typeof(PlayerTexter), "TrySendTextChat")] internal static class TexterSendProbe { [HarmonyPrefix] private static void Prefix() { BigTunesPlugin.Verbose("[chat out] PlayerTexter.TrySendTextChat()"); } } [HarmonyPatch(typeof(PlayerNetworking), "InvokeUserCode_CmdSendTextChatMessage__String")] internal static class ChatServerRelayPatch { [HarmonyPrefix] private static void Prefix(NetworkReader __1) { try { BigTunesPlugin.Verbose("[chat out] server relay: " + PeekString(__1)); } catch { } } internal static string PeekString(NetworkReader reader) { if (reader == null) { return "<no reader>"; } int position = reader.Position; try { return NetworkReaderExtensions.ReadString(reader) ?? "<null>"; } catch (Exception ex) { return "<unreadable: " + ex.GetType().Name + ">"; } finally { try { reader.Position = position; } catch { } } } } [HarmonyPatch(typeof(PlayerNetworking), "InvokeUserCode_RpcTextChatMessage__String")] internal static class ChatClientReceiveProbe { [HarmonyPrefix] private static void Prefix(NetworkReader __1) { try { BigTunesPlugin.Verbose("[chat in] rpc: " + ChatServerRelayPatch.PeekString(__1)); } catch { } } } [HarmonyPatch(typeof(MusicPlayer), "Sync")] internal static class MusicPlayerSyncPatch { [HarmonyPrefix] private static bool Prefix(MusicPlayer __instance) { return !RadioBridge.IsOwned(__instance); } } [HarmonyPatch(typeof(MusicPlayer), "ManualUpdate")] internal static class MusicPlayerUpdatePatch { [HarmonyPrefix] private static bool Prefix(MusicPlayer __instance) { return !RadioBridge.IsOwned(__instance); } } [HarmonyPatch(typeof(MusicPlayer), "SetDuration")] internal static class MusicPlayerDurationPatch { [HarmonyPrefix] private static bool Prefix(MusicPlayer __instance) { return !RadioBridge.IsOwned(__instance); } } [HarmonyPatch(typeof(MusicPlayer), "SetSyncPitch")] internal static class MusicPlayerPitchPatch { [HarmonyPrefix] private static bool Prefix(MusicPlayer __instance) { return !RadioBridge.IsOwned(__instance); } } [HarmonyPatch(typeof(MusicPlayer), "SetAsset")] internal static class MusicPlayerAssetPatch { [HarmonyPostfix] private static void Postfix(MusicPlayer __instance) { try { if (RadioBridge.IsOwned(__instance)) { TuneQueue.OnRadioRetuned(RadioBridge.RadioOf(__instance)); } } catch { } } } [HarmonyPatch(typeof(FmRadioPlayer), "SyncToTimeOfTheDay")] internal static class RadioTimeOfDayPatch { [HarmonyPostfix] private static void Postfix(FmRadioPlayer __instance) { try { if (RadioBridge.IsOwnedRadio(__instance)) { TuneQueue.OnRadioRetuned(__instance); } } catch { } } } [HarmonyPatch(typeof(MusicGroup), "SteerGroup")] internal static class MusicGroupSteerPatch { [HarmonyPrefix] private static bool Prefix() { return !RadioBridge.BlockGroupSteering("SteerGroup", block: true); } } [HarmonyPatch(typeof(MusicGroup), "SyncGroup")] internal static class MusicGroupSyncPatch { [HarmonyPrefix] private static bool Prefix() { return !RadioBridge.BlockGroupSteering("SyncGroup", block: true); } } [HarmonyPatch(typeof(FmRadioPlayer), "SetMusicPlayer")] internal static class RadioRetunePatch { [HarmonyPostfix] private static void Postfix() { try { TuneQueue.OnRadioRetuned(null); } catch (Exception ex) { BigTunesPlugin.Verbose("Retune hook failed: " + ex.Message); } } } internal static class Commands { private static string _lastHandled; private static DateTime _lastHandledAt; private static readonly HashSet<string> Verbs = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "play", "p", "skip", "s", "stop", "pause", "resume", "mute", "unmute", "restart", "queue", "q", "clear", "vol", "volume", "help", "h" }; internal static readonly HashSet<string> Shared = new HashSet<string> { "play", "p", "skip", "s", "stop", "pause", "resume", "clear", "restart" }; private static readonly Dictionary<string, double> HandledShared = new Dictionary<string, double>(); internal static string NameOf(PlayerCharacter player) { if ((Object)(object)player == (Object)null) { return "you"; } try { PlayerNetworking val = ((Component)player).GetComponent<PlayerNetworking>(); if ((Object)(object)val == (Object)null) { val = ((Component)player).GetComponentInParent<PlayerNetworking>(); } if ((Object)(object)val == (Object)null) { val = ((Component)player).GetComponentInChildren<PlayerNetworking>(); } if ((Object)(object)val != (Object)null && !string.IsNullOrWhiteSpace(val.username)) { return val.username; } } catch { } try { string text = (((Object)player).name ?? "").Replace("PlayerCharacter", "").Trim(); int num = text.LastIndexOf('-'); if (num > 0) { text = text.Substring(0, num); } return (text.Length == 0) ? "someone" : text; } catch { return "someone"; } } private static Vector3? PositionOf(PlayerCharacter player) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) try { return ((Object)(object)player != (Object)null) ? new Vector3?(((Component)player).transform.position) : ((Vector3?)null); } catch { return null; } } internal static bool IsSharedCommand(string line) { if (!IsCommandLine(line)) { return false; } string text = line.Trim().Substring(BigTunesPlugin.CommandPrefix.Value.Length).TrimStart(); return Shared.Contains(text.Split(' ')[0].ToLowerInvariant()); } internal static void HandleShared(string line, string sender, double at, PlayerCharacter from) { //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Expected O, but got Unknown string text = line.Trim(); string key = $"{sender}\u0001{text}\u0001{at:F1}"; double now = SyncNet.Now; if (HandledShared.ContainsKey(key)) { return; } HandledShared[key] = now; if (HandledShared.Count > 64) { foreach (string item in (from e in HandledShared where now - e.Value > 120.0 select e.Key).ToList()) { HandledShared.Remove(item); } } string[] array = text.Substring(BigTunesPlugin.CommandPrefix.Value.Length).Trim().Split(new char[1] { ' ' }, 2); string text2 = array[0].ToLowerInvariant(); string rest = ((array.Length > 1) ? array[1].Trim() : ""); ManualLogSource logger = BigTunesPlugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(19, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[shared] "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" from "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(sender); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" at "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<double>(at, "F2"); } logger.LogInfo(val); RunShared(text2, rest, sender, at, PositionOf(from)); } internal static void RunShared(string verb, string rest, string sender, double at, Vector3? origin) { if (verb == null) { return; } switch (verb.Length) { case 4: { char c = verb[1]; if (c != 'k') { switch (c) { default: return; case 'l': break; case 't': if (verb == "stop") { TuneQueue.Stop(origin); } return; } if (!(verb == "play")) { break; } goto IL_00d5; } if (!(verb == "skip")) { break; } goto IL_00f8; } case 1: { char c = verb[0]; if (c == 'p') { goto IL_00d5; } if (c != 's') { break; } goto IL_00f8; } case 5: switch (verb[0]) { case 'c': if (verb == "clear") { TuneQueue.Clear(origin); } break; case 'p': if (verb == "pause") { TuneQueue.Pause(at, origin); } break; } break; case 7: if (verb == "restart") { TuneQueue.Restart(origin); } break; case 6: if (verb == "resume") { TuneQueue.Resume(at, origin); } break; case 2: case 3: break; IL_00d5: if (rest.Length == 0) { BigTunesBehaviour.Say("BigTunes: type something to play, e.g. /play thunderstruck"); } else { TuneQueue.AcceptSharedPlay(rest, sender, at, origin); } break; IL_00f8: TuneQueue.Skip(origin); break; } } internal static bool IsCommandLine(string line) { if (string.IsNullOrWhiteSpace(line)) { return false; } string value = BigTunesPlugin.CommandPrefix.Value; if (string.IsNullOrEmpty(value)) { return false; } string text = line.TrimStart(); if (!text.StartsWith(value, StringComparison.Ordinal)) { return false; } string item = text.Substring(value.Length).TrimStart().Split(' ')[0]; return Verbs.Contains(item); } internal static bool TryHandleLine(string line, string sender) { if (!IsCommandLine(line)) { return false; } string text = line.Trim(); if (text == _lastHandled && (DateTime.UtcNow - _lastHandledAt).TotalSeconds < 3.0) { return true; } _lastHandled = text; _lastHandledAt = DateTime.UtcNow; return Handle(text.Substring(BigTunesPlugin.CommandPrefix.Value.Length).Trim(), sender); } internal static bool Handle(string body, string sender) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown string[] array = body.Split(new char[1] { ' ' }, 2); string text = array[0].ToLowerInvariant(); string text2 = ((array.Length > 1) ? array[1].Trim() : ""); ManualLogSource logger = BigTunesPlugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(31, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[command] verb='"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' rest='"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' from="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(sender); } logger.LogInfo(val); switch (text) { case "play": case "p": if (text2.Length == 0) { BigTunesBehaviour.Say("BigTunes: type something to play, e.g. /play thunderstruck"); return true; } TuneQueue.Enqueue(text2, sender); return true; case "skip": case "s": TuneQueue.Skip(); return true; case "stop": TuneQueue.Stop(); return true; case "pause": TuneQueue.Pause(SyncNet.Now); return true; case "resume": TuneQueue.Resume(SyncNet.Now); return true; case "mute": case "unmute": RadioBridge.Muted = text == "mute" && !RadioBridge.Muted; BigTunesBehaviour.Say(RadioBridge.Muted ? "BigTunes: muted for you. The music keeps playing for everyone else." : "BigTunes: unmuted."); return true; case "q": case "queue": BigTunesBehaviour.Say(TuneQueue.Describe(), 10f); return true; case "clear": TuneQueue.Clear(); return true; case "restart": TuneQueue.Restart(); return true; case "volume": case "vol": HandleVolume(text2); return true; case "help": case "h": BigTunesBehaviour.Say(HelpText(), 25f); return true; default: return false; } } private static string HelpText() { return "BigTunes commands\n/play <search or link> queue a track from YouTube\n/skip skip the current track\n/restart start this track again from the beginning\n/stop stop, and drop everything queued\n/clear drop what is waiting, keep this one playing\n/pause /resume hold the track, for everyone\n/mute /unmute silence the music for you alone\n/queue what is playing, and what is next\n/vol <0-200> volume, over 100 boosts\n\nEvery command acts on the speaker nearest to whoever typed it - the\nradio you carry, or a fixed one you are standing at."; } private static void HandleVolume(string rest) { rest = rest.Trim().TrimEnd('%'); if (rest.Length == 0) { BigTunesBehaviour.Say($"BigTunes: volume is {BigTunesPlugin.MusicVolume.Value}% (use /vol 80)"); return; } if (!int.TryParse(rest, out var result)) { BigTunesBehaviour.Say("BigTunes: give a number, e.g. /vol 80"); return; } BigTunesPlugin.MusicVolume.Value = Math.Clamp(result, 0, 200); int value = BigTunesPlugin.MusicVolume.Value; BigTunesBehaviour.Say((value > 100) ? $"BigTunes: volume {value}% (boosting)" : $"BigTunes: volume {value}%"); } } [BepInPlugin("com.nicholasxmiller.bigwalk.bigtunes", "BigTunes", "1.0.2")] public class BigTunesPlugin : BasePlugin { public const string PluginGuid = "com.nicholasxmiller.bigwalk.bigtunes"; public const string PluginName = "BigTunes"; public const string PluginVersion = "1.0.2"; internal static ManualLogSource Logger; internal static BigTunesPlugin Instance; internal static ConfigEntry<int> MusicVolume; internal static ConfigEntry<int> MaxSongMinutes; internal static ConfigEntry<int> CacheLimitMB; internal static ConfigEntry<string> CommandPrefix; internal static ConfigEntry<bool> ShowOverlay; internal static ConfigEntry<bool> VerboseLogging; internal static ConfigEntry<int> SpeakerRange; internal static ConfigEntry<int> SpeakerSetVolume; internal static ConfigEntry<int> AudibleDistance; internal static ConfigEntry<bool> Sync; internal static string DataDir { get; private set; } public override void Load() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Expected O, but got Unknown //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Expected O, but got Unknown //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Expected O, but got Unknown //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Expected O, but got Unknown //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Expected O, but got Unknown //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0233: Expected O, but got Unknown //IL_0233: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Expected O, but got Unknown //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Expected O, but got Unknown //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Expected O, but got Unknown //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Expected O, but got Unknown //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Expected O, but got Unknown //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Expected O, but got Unknown //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Expected O, but got Unknown //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02db: Expected O, but got Unknown //IL_02db: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Expected O, but got Unknown //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_0305: Expected O, but got Unknown //IL_0305: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Expected O, but got Unknown //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_032f: Expected O, but got Unknown //IL_032f: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Expected O, but got Unknown //IL_0368: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Expected O, but got Unknown //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Expected O, but got Unknown //IL_0406: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Expected O, but got Unknown //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Expected O, but got Unknown Instance = this; Logger = ((BasePlugin)this).Log; MusicVolume = ((BasePlugin)this).Config.Bind<int>("Audio", "MusicVolume", 50, new ConfigDescription("Loudness of queued music. 100 is as loud as a speaker can be asked to play; the engine clamps a source there and no setting gets past it. Above 100 the audio itself is amplified instead, which does work - at the usual price, since music is already mastered near full scale and the peaks have to be compressed to fit. 150 is mild. 200 is doing real work to the sound. Applied per track as it loads, so a change takes effect on the next one; the cached file on disk is never altered.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 200), Array.Empty<object>())); AudibleDistance = ((BasePlugin)this).Config.Bind<int>("Audio", "AudibleDistance", 20, "How far the music carries, in metres, fading smoothly to nothing at that range. The engine's own falloff is built for broadcast towers and cuts out abruptly, so this replaces it. 0 leaves the vanilla behaviour alone."); MaxSongMinutes = ((BasePlugin)this).Config.Bind<int>("Downloads", "MaxSongMinutes", 12, "Songs longer than this many minutes are rejected at queue time."); CacheLimitMB = ((BasePlugin)this).Config.Bind<int>("Downloads", "CacheLimitMB", 500, "Maximum size of the on-disk audio cache; least-recently-played files are evicted first."); CommandPrefix = ((BasePlugin)this).Config.Bind<string>("Commands", "CommandPrefix", "/", "Prefix for chat commands. Messages starting with it are swallowed and never sent to other players."); Sync = ((BasePlugin)this).Config.Bind<bool>("Multiplayer", "Sync", true, "Share playback with other players who have BigTunes. A track you start plays for them too, from the same point in the song. Turn it off to keep your music to yourself: you will still hear everything you queue, and nobody else will."); ShowOverlay = ((BasePlugin)this).Config.Bind<bool>("Interface", "ShowOverlay", true, "Show a small corner overlay with the current track, queue length and command feedback."); VerboseLogging = ((BasePlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "Log every step of resolving, downloading and playback to the BepInEx log. Off by default because it is noisy; turn it on before reporting a problem and the log will say exactly which speaker was chosen and why."); SpeakerRange = ((BasePlugin)this).Config.Bind<int>("Audio", "SpeakerRange", 15, "Stand this close to one of the world's fixed speakers - the lighthouse, the speaker stage, the music garden - and a track you queue plays out of that instead of the radio you are carrying."); SpeakerSetVolume = ((BasePlugin)this).Config.Bind<int>("Audio", "SpeakerSetVolume", 50, new ConfigDescription("Loudness of a wired set of speakers, as a percentage of MusicVolume. Nine speakers playing the same track sum to far more than one, so a level that suits the radio in your hand is overwhelming at the music garden. 100 means no difference; lower trims the sets only and leaves handhelds alone.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 100), Array.Empty<object>())); string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); DataDir = Path.Combine(string.IsNullOrEmpty(directoryName) ? Paths.PluginPath : directoryName, "BigTunesData"); Directory.CreateDirectory(DataDir); Harmony val = new Harmony("com.nicholasxmiller.bigwalk.bigtunes"); PatchOrReport(val, typeof(ChatAddMessagePatch), "TextChatSource.AddMessage"); PatchOrReport(val, typeof(ChatOutputPatch), "TextChatSource.SetOutput"); PatchOrReport(val, typeof(ChatCommandPatch), "CmdSendTextChatMessage"); PatchOrReport(val, typeof(ChatBroadcastPatch), "RadioVoiceAssigner.BroadcastTextChatOverThisChannel"); PatchOrReport(val, typeof(TexterSendProbe), "PlayerTexter.TrySendTextChat"); PatchOrReport(val, typeof(TexterCompleteInputPatch), "PlayerTexter.CompleteInput"); PatchOrReport(val, typeof(TexterReceiveProbe), "PlayerTexter.ReceieveMessage"); PatchOrReport(val, typeof(ChatServerRelayPatch), "InvokeUserCode_CmdSendTextChatMessage"); PatchOrReport(val, typeof(ChatClientReceiveProbe), "InvokeUserCode_RpcTextChatMessage"); PatchOrReport(val, typeof(RadioRetunePatch), "FmRadioPlayer.SetMusicPlayer"); PatchOrReport(val, typeof(MusicPlayerSyncPatch), "MusicPlayer.Sync"); PatchOrReport(val, typeof(MusicPlayerUpdatePatch), "MusicPlayer.ManualUpdate"); PatchOrReport(val, typeof(MusicPlayerDurationPatch), "MusicPlayer.SetDuration"); PatchOrReport(val, typeof(MusicPlayerPitchPatch), "MusicPlayer.SetSyncPitch"); PatchOrReport(val, typeof(MusicPlayerAssetPatch), "MusicPlayer.SetAsset"); PatchOrReport(val, typeof(RadioTimeOfDayPatch), "FmRadioPlayer.SyncToTimeOfTheDay"); PatchOrReport(val, typeof(MusicGroupSteerPatch), "MusicGroup.SteerGroup"); PatchOrReport(val, typeof(MusicGroupSyncPatch), "MusicGroup.SyncGroup"); int num = 0; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val2; foreach (MethodBase patchedMethod in val.GetPatchedMethods()) { num++; ManualLogSource logger = Logger; val2 = new BepInExInfoLogInterpolatedStringHandler(12, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" patched: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(patchedMethod.DeclaringType?.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("."); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(patchedMethod.Name); } logger.LogInfo(val2); } ManualLogSource logger2 = Logger; val2 = new BepInExInfoLogInterpolatedStringHandler(24, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Harmony patches active: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num); } logger2.LogInfo(val2); ClassInjector.RegisterTypeInIl2Cpp<BigTunesBehaviour>(); GameObject val3 = new GameObject("BigTunes") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val3); val3.AddComponent<BigTunesBehaviour>(); ManualLogSource logger3 = Logger; val2 = new BepInExInfoLogInterpolatedStringHandler(23, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("BigTunes"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>("1.0.2"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" loaded. Data folder: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(DataDir); } logger3.LogInfo(val2); Logger.LogInfo((object)"Type /help in chat for the command list."); ToolChain.EnsureBeforeGameStarts(); } private static void PatchOrReport(Harmony harmony, Type patchClass, string label) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown bool flag = default(bool); try { harmony.PatchAll(patchClass); ManualLogSource logger = Logger; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(9, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Patched "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(label); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } logger.LogInfo(val); } catch (Exception ex) { ManualLogSource logger2 = Logger; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(18, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("FAILED to patch "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(label); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message); } logger2.LogError(val2); } } internal static void Verbose(string message) { if (VerboseLogging != null && VerboseLogging.Value) { Logger.LogInfo((object)message); } } } internal static class RadioBridge { private struct Baseline { public AudioSourceController Controller; public AudioSource Source; public float Scriptable; public float Volume; public AudioRolloffMode Mode; public bool CurveReplaced; } private static readonly HashSet<nint> Owned = new HashSet<nint>(); private static readonly Dictionary<nint, int> Misses = new Dictionary<nint, int>(); internal static bool Muted; internal static int Failures; private static readonly Dictionary<nint, AudioSource> Voices = new Dictionary<nint, AudioSource>(); private static readonly Dictionary<string, float> GroupReports = new Dictionary<string, float>(); private static readonly Dictionary<nint, bool> InSetCache = new Dictionary<nint, bool>(); private static readonly Dictionary<nint, Baseline> Baselines = new Dictionary<nint, Baseline>(); private static readonly Dictionary<nint, float> AppliedLevel = new Dictionary<nint, float>(); private static List<FmRadioPlayer> _radioCache; private static float _radioCacheAt = -99f; private static nint _stationField = IntPtr.Zero; private static int _stationOffset = -1; private static bool _stationFieldTried; private static float _nextStationComplaint; private static readonly Dictionary<nint, float> PoweredAt = new Dictionary<nint, float>(); private static AudioListener _listener; private static List<Speaker> _playableCache; private static float _playableCacheAt = -999f; private static readonly HashSet<nint> Silent = new HashSet<nint>(); private static bool _filtersReported; private static bool _audioReported; internal static float EarshotDistance { get; private set; } = float.MaxValue; internal static bool ShouldShowStatus() { try { if (EarshotDistance <= (float)BigTunesPlugin.AudibleDistance.Value) { return true; } foreach (FmRadioPlayer item in FindRadios()) { if ((Object)(object)item != (Object)null && IsHeld(item)) { return true; } } } catch { } return false; } internal static bool BlockGroupSteering(string method, bool block) { try { if (Owned.Count == 0) { return false; } bool flag = block; if (BigTunesPlugin.VerboseLogging != null && BigTunesPlugin.VerboseLogging.Value && !GroupReports.ContainsKey(method)) { GroupReports[method] = 1f; BigTunesPlugin.Logger.LogInfo((object)("[group] MusicGroup." + method + " fired while we hold the player - " + (flag ? "blocked." : "allowed."))); } return flag; } catch { return false; } } internal static bool IsOwned(MusicPlayer player) { try { return (Object)(object)player != (Object)null && Owned.Contains(((Il2CppObjectBase)player).Pointer); } catch { return false; } } internal static FmRadioPlayer RadioOf(MusicPlayer player) { if ((Object)(object)player == (Object)null) { return null; } foreach (FmRadioPlayer item in FindRadios()) { if ((Object)(object)item == (Object)null) { continue; } try { MusicPlayer val = ActiveMusicPlayer(item, allowOff: true); if ((Object)(object)val != (Object)null && ((Il2CppObjectBase)val).Pointer == ((Il2CppObjectBase)player).Pointer) { return item; } } catch { } } return null; } internal static bool IsOwnedRadio(FmRadioPlayer radio) { try { if ((Object)(object)radio == (Object)null) { return false; } return IsOwned(ActiveMusicPlayer(radio, allowOff: true)); } catch { return false; } } private static void Claim(MusicPlayer player) { try { if ((Object)(object)player != (Object)null) { Owned.Add(((Il2CppObjectBase)player).Pointer); } } catch { } } private static bool InSet(Speaker speaker) { nint key = speaker.Key; if (key == IntPtr.Zero) { return false; } if (InSetCache.TryGetValue(key, out var value)) { return value; } bool flag = false; try { MusicPlayer val = speaker.Player(allowOff: true); if ((Object)(object)val != (Object)null) { flag = Speaker.SetContaining(val).Count > 1; } } catch { } InSetCache[key] = flag; return flag; } internal static string WhyNot(IReadOnlyList<Speaker> speakers) { if (speakers == null || speakers.Count == 0) { return "no speaker resolved at all - the queue has nothing to play through."; } List<string> list = new List<string>(); foreach (Speaker speaker in speakers) { if (!speaker.Exists) { list.Add("a speaker that no longer exists"); continue; } string text = "?"; try { text = StationIndex(speaker.Radio)?.ToString() ?? "OFF/null"; } catch { } MusicPlayer val = speaker.Player(); if ((Object)(object)val == (Object)null) { list.Add(speaker.Path + ": station=" + text + ", no music player behind that station"); continue; } string text2 = "?"; string text3 = "?"; string text4 = "?"; try { text2 = (((Object)(object)val.Asset != (Object)null) ? "loaded" : "NOT LOADED"); } catch { text2 = "threw"; } try { text3 = (((Object)(object)val.MusicConfig != (Object)null) ? "ok" : "MISSING"); } catch { text3 = "threw"; } try { text4 = (((Object)(object)val.ASC != (Object)null) ? "ok" : "none"); } catch { text4 = "threw"; } list.Add($"{PathOf((Component)(object)val)}: station={text} asset={text2} musicConfig={text3} controller={text4} powered={(((Object)(object)speaker.Radio != (Object)null) ? IsPoweredOn(speaker.Radio).ToString() : "n/a")}"); } return string.Join("\n[retune] ", list); } internal static void Release(Speaker speaker) { if (!speaker.Exists) { return; } try { MusicPlayer val = speaker.Player(allowOff: true); if ((Object)(object)val != (Object)null) { Restore(((Il2CppObjectBase)val).Pointer); Owned.Remove(((Il2CppObjectBase)val).Pointer); Misses.Remove(((Il2CppObjectBase)val).Pointer); } } catch { } Voices.Remove(speaker.Key); } private static AudioSource LiveVoice(Speaker speaker, AudioClip expected) { try { if (!Voices.TryGetValue(speaker.Key, out var value)) { return null; } if ((Object)(object)value == (Object)null || !value.isPlaying || (Object)(object)value.clip == (Object)null) { return null; } if ((Object)(object)expected != (Object)null && ((Object)value.clip).GetInstanceID() != ((Object)expected).GetInstanceID()) { return null; } return value; } catch { return null; } } internal static void HoldPosition(Speaker speaker, AudioClip expected, float position, float tolerance = 1.5f) { AudioSource val = LiveVoice(speaker, expected); if ((Object)(object)val == (Object)null) { return; } try { float time = val.time; if (!(Mathf.Abs(time - position) <= tolerance)) { float num = (((Object)(object)val.clip != (Object)null) ? val.clip.length : 0f); val.time = Mathf.Clamp(position, 0f, Mathf.Max(0f, num - 0.5f)); BigTunesPlugin.Verbose($"[seek] corrected {time:F1}s -> {position:F1}s"); } } catch { } } internal static float CurrentPlayhead(Speaker speaker, AudioClip expected) { AudioSource val = LiveVoice(speaker, expected); if ((Object)(object)val != (Object)null) { try { return val.time; } catch { return -1f; } } try { MusicPlayer val2 = speaker.Player(); if (IsOwned(val2)) { AudioSourceController aSC = val2.ASC; AudioSource val3 = ((aSC != null) ? aSC.AudioSource : null); if ((Object)(object)val3 != (Object)null && val3.isPlaying && ((Object)(object)expected == (Object)null || (Object)(object)val3.clip == (Object)null || ((Object)val3.clip).GetInstanceID() == ((Object)expected).GetInstanceID())) { Voices[speaker.Key] = val3; return val3.time; } } } catch { } return -1f; } internal static void UpdateDistanceVolume() { int value = BigTunesPlugin.AudibleDistance.Value; float num = Mathf.Clamp((float)BigTunesPlugin.MusicVolume.Value / 100f, 0f, 2f); float num2 = ((num <= 1f) ? Mathf.Pow(num, 1.6f) : num); float num3 = float.MaxValue; if (value <= 0) { EarshotDistance = 0f; return; } float num4 = Mathf.Clamp01((float)BigTunesPlugin.SpeakerSetVolume.Value / 100f); foreach (Speaker item in AllPlayableSpeakers()) { try { if (!item.Exists) { continue; } MusicPlayer val = item.Player(); if (!IsOwned(val)) { continue; } float num5 = num2; if (!item.IsRadio && InSet(item)) { num5 *= num4; } AudioSourceController aSC = val.ASC; if ((Object)(object)aSC == (Object)null) { continue; } float num6 = DistanceToListener((Component)(object)val); if (num6 < 0f) { continue; } if (num6 < num3) { num3 = num6; } float num7 = Mathf.Clamp01(num6 / (float)value); float num8 = ((num7 >= 1f) ? 0f : Mathf.Pow(10f, -1.8f * Mathf.Pow(num7, 3.1f))); float num9 = num5 * num8; if (Muted) { num9 = 0f; } AudioSource audioSource = aSC.AudioSource; Remember(val, aSC, audioSource); aSC.ScriptableVolume = Mathf.Clamp(num9, 0f, 4f); if ((Object)(object)audioSource != (Object)null && (Object)(object)audioSource.clip != (Object)null) { audioSource.volume = Mathf.Clamp01(num9); if (ApplyLevelCurve(audioSource, Mathf.Clamp01(num9))) { MarkCurveReplaced(val); } } } catch { } } EarshotDistance = num3; } private static void Remember(MusicPlayer player, AudioSourceController asc, AudioSource source) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)player == (Object)null) && !((Object)(object)asc == (Object)null)) { nint pointer = ((Il2CppObjectBase)player).Pointer; if (!Baselines.ContainsKey(pointer)) { Baselines[pointer] = new Baseline { Controller = asc, Source = source, Scriptable = asc.ScriptableVolume, Volume = (((Object)(object)source != (Object)null) ? source.volume : 1f), Mode = (AudioRolloffMode)((!((Object)(object)source != (Object)null)) ? 2 : ((int)source.rolloffMode)), CurveReplaced = false }; } } } catch { } } private static void Restore(nint key) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (!Baselines.TryGetValue(key, out var value)) { return; } Baselines.Remove(key); try { if ((Object)(object)value.Controller != (Object)null) { value.Controller.ScriptableVolume = value.Scriptable; } AudioSource source = value.Source; if (!((Object)(object)source == (Object)null)) { source.volume = value.Volume; source.rolloffMode = (AudioRolloffMode)((!value.CurveReplaced) ? ((int)value.Mode) : 0); } } catch { } } private static void RestoreAll() { foreach (nint item in new List<nint>(Baselines.Keys)) { Restore(item); } Baselines.Clear(); AppliedLevel.Clear(); } private static bool ApplyLevelCurve(AudioSource source, float level) { try { nint pointer = ((Il2CppObjectBase)source).Pointer; if (AppliedLevel.TryGetValue(pointer, out var value) && Mathf.Abs(value - level) < 0.02f) { return true; } source.rolloffMode = (AudioRolloffMode)2; source.SetCustomCurve((AudioSourceCurveType)0, AnimationCurve.Constant(0f, 1f, level)); AppliedLevel[pointer] = level; return true; } catch (Exception ex) { BigTunesPlugin.Verbose("[level] could not set the rolloff curve: " + ex.Message); return false; } } private static void MarkCurveReplaced(MusicPlayer player) { try { if (!((Object)(object)player == (Object)null)) { nint pointer = ((Il2CppObjectBase)player).Pointer; if (Baselines.TryGetValue(pointer, out var value) && !value.CurveReplaced) { value.CurveReplaced = true; Baselines[pointer] = value; } } } catch { } } internal static string PathOf(Component component) { try { List<string> list = new List<string>(); Transform val = component.transform; while ((Object)(object)val != (Object)null && list.Count < 8) { list.Insert(0, ((Object)val).name); val = val.parent; } return string.Join("/", list); } catch { return ((Object)(object)component != (Object)null) ? ((Object)component).name : "?"; } } internal static List<FmRadioPlayer> FindRadios() { if (_radioCache != null && Time.realtimeSinceStartup - _radioCacheAt < 1f) { return _radioCache; } List<FmRadioPlayer> result = (_radioCache = ScanRadios()); _radioCacheAt = Time.realtimeSinceStartup; return result; } private static List<FmRadioPlayer> ScanRadios() { List<FmRadioPlayer> list = new List<FmRadioPlayer>(); try { Il2CppArrayBase<FmRadioPlayer> val = Object.FindObjectsOfType<FmRadioPlayer>(true); if (val != null) { foreach (FmRadioPlayer item in val) { if ((Object)(object)item != (Object)null) { list.Add(item); } } } } catch (Exception ex) { BigTunesPlugin.Logger.LogError((object)("FindObjectsOfType<FmRadioPlayer> failed: " + ex)); } return list; } private static void FindStationField() { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (_stationFieldTried) { return; } _stationFieldTried = true; try { _stationField = IL2CPP.GetIl2CppField(Il2CppClassPointerStore<FmRadioPlayer>.NativeClassPtr, "_stationIndex"); if (_stationField != IntPtr.Zero) { _stationOffset = (int)IL2CPP.il2cpp_field_get_offset((IntPtr)_stationField); } ManualLogSource logger = BigTunesPlugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(39, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[station] _stationIndex field offset = "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(_stationOffset); } logger.LogInfo(val); } catch (Exception ex) { BigTunesPlugin.Logger.LogWarning((object)("Could not locate _stationIndex: " + ex.Message)); } } internal static int? StationIndex(FmRadioPlayer radio) { if ((Object)(object)radio == (Object)null) { return null; } FindStationField(); if (_stationOffset >= 0) { try { nint num = (nint)((Il2CppObjectBase)radio).Pointer + _stationOffset; if (Marshal.ReadByte(num) == 0) { return null; } return Marshal.ReadInt32(num + 4); } catch (Exception ex) { if (_nextStationComplaint == 0f) { _nextStationComplaint = 1f; BigTunesPlugin.Verbose("Raw _stationIndex read failed: " + ex.Message); } } } try { Nullable<int> stationIndex = radio._stationIndex; return stationIndex.HasValue ? new int?(stationIndex.Value) : ((int?)null); } catch { return null; } } internal static MusicPlayer ActiveMusicPlayer(FmRadioPlayer radio, bool allowOff = false) { int? num = StationIndex(radio); if (!num.HasValue && !allowOff) { return null; } try { Il2CppReferenceArray<MusicPlayerData> musicPlayers = radio.musicPlayers; if (musicPlayers == null || ((Il2CppArrayBase<MusicPlayerData>)(object)musicPlayers).Length == 0) { return null; } int num2 = num.GetValueOrDefault(); if (num2 < 0 || num2 >= ((Il2CppArrayBase<MusicPlayerData>)(object)musicPlayers).Length) { num2 = 0; } return ((Il2CppArrayBase<MusicPlayerData>)(object)musicPlayers)[num2].musicPlayer; } catch (Exception ex) { BigTunesPlugin.Verbose("Could not read musicPlayers: " + ex.Message); return null; } } internal static bool IsHeld(FmRadioPlayer radio) { try { string text = PathOf((Component)(object)radio); return text.Contains("grasperHand") || text.Contains("PlayerCharacter"); } catch { return false; } } internal static bool IsPoweredOn(FmRadioPlayer radio) { if (LooksPoweredOn(radio)) { try { PoweredAt[((Il2CppObjectBase)radio).Pointer] = Time.realtimeSinceStartup; } catch { } return true; } try { if (PoweredAt.TryGetValue(((Il2CppObjectBase)radio).Pointer, out var value) && Time.realtimeSinceStartup - value < 4f) { return true; } } catch { } return false; } private static bool LooksPoweredOn(FmRadioPlayer radio) { if (StationIndex(radio).HasValue) { return true; } try { MusicPlayer obj = ActiveMusicPlayer(radio, allowOff: true); object obj2; if (obj == null) { obj2 = null; } else { AudioSourceController aSC = obj.ASC; obj2 = ((aSC != null) ? aSC.AudioSource : null); } AudioSource val = (AudioSource)obj2; return (Object)(object)val != (Object)null && val.isPlaying; } catch { return false; } } internal static bool StillVoicing(Speaker speaker, AudioClip clip) { return (Object)(object)LiveVoice(speaker, clip) != (Object)null; } internal static Speaker NearestSpeaker(Vector3 from) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) FmRadioPlayer val = null; FmRadioPlayer val2 = null; float num = float.MaxValue; float num2 = float.MaxValue; foreach (FmRadioPlayer item in FindRadios()) { if (!((Object)(object)item == (Object)null)) { float num3; try { num3 = Vector3.Distance(from, ((Component)item).transform.position); } catch { continue; } bool num4 = IsHeld(item); if (num3 < num) { num = num3; val = item; } if (!num4 && num3 < num2) { num2 = num3; val2 = item; } } } MusicPlayer val3 = null; float num5 = float.MaxValue; foreach (MusicPlayer item2 in Speaker.AllFixed()) { if ((Object)(object)item2 == (Object)null) { continue; } try { if (!((Object)(object)item2.Asset == (Object)null) && !((Object)(object)item2.MusicConfig == (Object)null)) { float num6 = Vector3.Distance(from, ((Component)item2).transform.position); if (num6 < num5) { num5 = num6; val3 = item2; } } } catch { } } int num7 = Mathf.Max(0, BigTunesPlugin.SpeakerRange.Value); if ((Object)(object)val3 != (Object)null && num5 <= (float)num7 && num5 <= num2) { return Speaker.Fixed(val3); } if ((Object)(object)val2 != (Object)null && num2 <= (float)num7) { return Speaker.OfRadio(val2); } if (!((Object)(object)val != (Object)null)) { return default(Speaker); } return Speaker.OfRadio(val); } internal static Vector3 ListenerPosition() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_listener == (Object)null) { _listener = Object.FindObjectOfType<AudioListener>(); } if ((Object)(object)_listener != (Object)null) { return ((Component)_listener).transform.position; } } catch { _listener = null; } return Vector3.zero; } internal static List<Speaker> AllPlayableSpeakers() { if (_playableCache != null && Time.realtimeSinceStartup - _playableCacheAt < 1f) { return _playableCache; } List<Speaker> list = new List<Speaker>(); foreach (FmRadioPlayer item in FindRadios()) { if (!((Object)(object)item == (Object)null) && (!IsHeld(item) || IsPoweredOn(item))) { list.Add(Speaker.OfRadio(item)); } } foreach (MusicPlayer item2 in Speaker.AllFixed()) { if (!((Object)(object)item2 == (Object)null)) { list.Add(Speaker.Fixed(item2)); } } _playableCache = list; _playableCacheAt = Time.realtimeSinceStartup; return list; } private static float DistanceToListener(Component c) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)c == (Object)null) { return -1f; } return Vector3.Distance(ListenerPosition(), c.transform.position); } catch { return -1f; } } internal static int PlayOn(IReadOnlyList<Speaker> speakers, AudioClip clip, float startAt = 0f, bool allowOff = false, bool quiet = false) { //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Expected O, but got Unknown if ((Object)(object)clip == (Object)null || speakers == null) { return 0; } int num = 0; bool flag = default(bool); foreach (Speaker speaker in speakers) { if (!speaker.Exists) { continue; } MusicPlayer val = speaker.Player(allowOff); if ((Object)(object)val == (Object)null) { continue; } AudioAsset val2 = null; try { val2 = val.Asset; } catch { } if ((Object)(object)val2 == (Object)null) { if (Silent.Add(((Il2CppObjectBase)val).Pointer)) { BigTunesPlugin.Verbose("[play] " + PathOf((Component)(object)val) + " has no station loaded yet - it will join when it does."); } continue; } Silent.Remove(((Il2CppObjectBase)val).Pointer); try { if ((Object)(object)val.MusicConfig == (Object)null) { if (Silent.Add(((Il2CppObjectBase)val).Pointer)) { BigTunesPlugin.Verbose("[play] " + PathOf((Component)(object)val) + " has no music config yet - skipped."); } continue; } } catch { continue; } try { val.Play(clip); AudioSourceController val3 = null; try { val3 = val.ASC; } catch { } if ((Object)(object)val3 == (Object)null) { if (Silent.Add(((Il2CppObjectBase)val).Pointer)) { BigTunesPlugin.Verbose($"[play] {PathOf((Component)(object)val)} took the clip but got no audio source (pool empty at {DistanceToListener((Component)(object)val):F0}m) - will retry."); } continue; } Claim(val); num++; Failures = 0; Tune(speaker, val, once: true); Seek(val, clip, startAt); try { AudioSource audioSource = val3.AudioSource; if ((Object)(object)audioSource != (Object)null) { Voices[speaker.Key] = audioSource; } else { Voices.Remove(speaker.Key); } } catch { Voices.Remove(speaker.Key); } _audioReported = false; Silent.Remove(((Il2CppObjectBase)val).Pointer); ManualLogSource logger = BigTunesPlugin.Logger; BepInExInfoLogInterpolatedStringHandler val4 = new BepInExInfoLogInterpolatedStringHandler(43, 5, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("[play] "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<string>(PathOf((Component)(object)val)); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" dist="); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<float>(DistanceToListener((Component)(object)val), "F1"); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("m "); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("asc=ok active="); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<bool>(((Behaviour)val).isActiveAndEnabled); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("clip="); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<float>(clip.length, "F0"); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("s state="); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<AudioDataLoadState>(clip.loadState); } logger.LogInfo(val4); } catch (Exception ex) { Failures++; if (Failures <= 2) { BigTunesPlugin.Logger.LogWarning((object)("MusicPlayer.Play failed: " + ex.Message)); } else if (Failures == 3) { BigTunesPlugin.Logger.LogWarning((object)"MusicPlayer.Play keeps failing; further attempts will be quiet."); } } } if (num == 0 && !quiet) { List<FmRadioPlayer> list = FindRadios(); int num2 = 0; foreach (FmRadioPlayer item in list) { if (!((Object)(object)item == (Object)null) && IsPoweredOn(item)) { num2++; } } int count = Speaker.AllFixed().Count; BigTunesPlugin.Verbose($"[play] no target: {list.Count} radio(s) visible, {num2} switched on, {count} fixed speaker(s) in the world, {speakers.Count} asked for."); } else if (!quiet) { ManualLogSource logger2 = BigTunesPlugin.Logger; BepInExInfoLogInterpolatedStringHandler val4 = new BepInExInfoLogInterpolatedStringHandler(34, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("[play] pushed clip into "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" radio(s)."); } logger2.LogInfo(val4); } return num; } internal static void Tune(Speaker speaker, MusicPlayer player, bool once = false) { try { if (once && (Object)(object)speaker.Radio != (Object)null) { StopStatic(speaker.Radio); } player.DistortionDryWet = 0f; player.CloseDistDistortion = 0f; player.CloseDistBassBoost = 0f; player.OcclusionFiltering = 0f; player.DistanceFiltering = 0f; AudioSourceController aSC = player.ASC; if ((Object)(object)aSC == (Object)null) { return; } aSC.BypassFilters = true; try { AudioSource audioSource = aSC.AudioSource; if ((Object)(object)audioSource != (Object)null) { audioSource.loop = false; } } catch { } if (once) { aSC.ScriptableVolume = Mathf.Clamp((float)BigTunesPlugin.MusicVolume.Value / 100f, 0f, 4f); } AudioSource audioSource2 = aSC.AudioSource; if ((Object)(object)audioSource2 != (Object)null) { ClearFilters(audioSource2); } } catch (Exception ex) { BigTunesPlugin.Verbose("Tune failed: " + ex.Message); } } internal static void ReportAudioPath(AudioSource source, string when) { ReportAudioPath(source, when, null); } internal static void ReportAudioPath(AudioSource source, string when, AudioSourceController asc) { //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Invalid comparison between Unknown and I4 if ((Object)(object)source == (Object)null) { return; } try { AudioClip clip = source.clip; string value = "none"; try { value = (((Object)(object)source.outputAudioMixerGroup != (Object)null) ? ((Object)source.outputAudioMixerGroup).name : "none"); } catch { } BigTunesPlugin.Verbose($"[audio {when}] clip={(((Object)(object)clip != (Object)null) ? clip.channels : (-1))}ch {(((Object)(object)clip != (Object)null) ? clip.frequency : 0)}Hz spatialBlend={source.spatialBlend:F2} pan={source.panStereo:F2} vol={source.volume:F2} pitch={source.pitch:F2} bypass(fx={source.bypassEffects},listener={source.bypassListenerEffects},reverb={source.bypassReverbZones}) mixer={value} rolloff={source.rolloffMode} min={source.minDistance:F0} max={source.maxDistance:F0}"); if (!((Object)(object)asc != (Object)null)) { return; } float num = -1f; try { if ((int)source.rolloffMode == 2) { num = source.GetCustomCurve((AudioSourceCurveType)0).Evaluate(Mathf.Clamp01(DistanceToListener((Component)(object)source) / Mathf.Max(1f, source.maxDistance))); } } catch { num = -1f; } try { BigTunesPlugin.Verbose($"[chain] final={asc.FinalVolume:F3} settings={asc.AudioSettingsVol:F3} atten={asc.AttenuationVol:F3} rtpc={asc.RTPCVol:F3} fade={asc.FadeVol:F3} hibernation={asc.HibernationVol:F3} scriptable={asc.ScriptableVolume:F3} | source.volume={source.volume:F3} " + ((num < 0f) ? "curve=unreadable" : $"curve@here={num:F3}")); } catch (Exception ex) { BigTunesPlugin.Verbose("[chain] unreadable: " + ex.Message); } } catch (Exception ex2) { BigTunesPlugin.Verbose("Audio report failed: " + ex2.Message); } } internal static void MaybeReportAudioPath(Speaker speaker, AudioClip expected, float playhead) { if (!_audioReported && !(playhead < 3f)) { _audioReported = true; AudioSource source = LiveVoice(speaker, expected); AudioSourceController asc = null; try { MusicPlayer obj = speaker.Player(allowOff: true); asc = ((obj != null) ? obj.ASC : null); } catch { } ReportAudioPath(source, "settled", asc); } } private static void ClearFilters(AudioSource source) { try { source.bypassEffects = true; source.bypassListenerEffects = true; source.bypassReverbZones = true; GameObject gameObject = ((Component)source).gameObject; string text = ""; AudioLowPassFilter component = gameObject.GetComponent<AudioLowPassFilter>(); if ((Object)(object)component != (Object)null) { text += $" lowpass({component.cutoffFrequency:F0}Hz)"; component.cutoffFrequency = 22000f; } AudioHighPassFilter component2 = gameObject.GetComponent<AudioHighPassFilter>(); if ((Object)(object)component2 != (Object)null) { text += $" highpass({component2.cutoffFrequency:F0}Hz)"; component2.cutoffFrequency = 10f; } AudioDistortionFilter component3 = gameObject.GetComponent<AudioDistortionFilter>(); if ((Object)(object)component3 != (Object)null) { text += " distortion"; component3.distortionLevel = 0f; } AudioEchoFilter component4 = gameObject.GetComponent<AudioEchoFilter>(); if ((Object)(object)component4 != (Object)null) { text += " echo"; component4.wetMix = 0f; } if (!_filtersReported) { _filtersReported = true; BigTunesPlugin.Verbose((text.Length > 0) ? ("[filters] cleared:" + text) : "[filters] none on the source - muffling is elsewhere."); } } catch (Exception ex) { if (!_filtersReported) { _filtersReported = true; BigTunesPlugin.Verbose("Could not clear filters: " + ex.Message); } } } private static void Seek(MusicPlayer player, AudioClip clip, float startAt) { try { AudioSourceController aSC = player.ASC; AudioSource val = ((aSC != null) ? aSC.AudioSource : null); if (!((Object)(object)val == (Object)null)) { val.time = Mathf.Clamp(startAt, 0f, Mathf.Max(0f, clip.length - 1f)); } } catch (Exception ex) { BigTunesPlugin.Verbose("Seek failed: " + ex.Message); } } private static void StopStatic(FmRadioPlayer radio) { try { Il2CppReferenceArray<AudioSourceController> staticSources = radio._staticSources; if (staticSources == null) { return; } for (int i = 0; i < ((Il2CppArrayBase<AudioSourceController>)(object)staticSources).Length; i++) { AudioSourceController val = ((Il2CppArrayBase<AudioSourceController>)(object)staticSources)[i]; if ((Object)(object)val != (Object)null) { val.FadeOut(0.2f); } } } catch (Exception ex) { BigTunesPlugin.Verbose("Could not stop static: " + ex.Message); } } internal static void ReapplyTuning(IReadOnlyList<Speaker> speakers) { bool allowOff = false; HashSet<nint> hashSet = new HashSet<nint>(); foreach (Speaker item in speakers ?? Array.Empty<Speaker>()) { if (!item.Exists) { continue; } MusicPlayer val = item.Player(allowOff); if (!((Object)(object)val == (Object)null) && IsOwned(val)) { try { hashSet.Add(((Il2CppObjectBase)val).Pointer); } catch { } Tune(item, val); } } List<nint> list = new List<nint>(); foreach (nint item2 in Owned) { if (!hashSet.Contains(item2)) { int value; int num = ((!Misses.TryGetValue(item2, out value)) ? 1 : (value + 1)); Misses[item2] = num; if (num >= 3) { list.Add(item2); } } } foreach (nint item3 in list) { Restore(item3); Owned.Remove(item3); Misses.Remove(item3); } foreach (nint item4 in hashSet) { Owned.Add(item4); Misses[item4] = 0; } } internal static void StopOn(IReadOnlyList<Speaker> speakers, float fadeSeconds = 0.25f) { if (speakers == null) { return; } foreach (Speaker speaker in speakers) { if (!speaker.Exists) { continue; } Release(speaker); MusicPlayer val = speaker.Player(); if (!((Object)(object)val == (Object)null)) { try { val.Stop(fadeSeconds); } catch (Exception ex) { BigTunesPlugin.Verbose("MusicPlayer.Stop failed: " + ex.Message); } } } } internal static void SetPaused(IReadOnlyList<Speaker> speakers, bool paused) { if (speakers == null) { return; } foreach (Speaker speaker in speakers) { if (!speaker.Exists) { continue; } MusicPlayer val = speaker.Player(); if ((Object)(object)val == (Object)null) { continue; } try { AudioSourceController aSC = val.ASC; if ((Object)(object)aSC != (Object)null) { aSC.Pause(paused); } } catch (Exception ex) { BigTunesPlugin.Verbose("AudioSourceController.Pause failed: " + ex.Message); } } } internal static string Describe() { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) List<FmRadioPlayer> list = FindRadios(); if (list.Count == 0) { return "BigTunes: no FmRadioPlayer in the scene (hold or place a radio first)."; } Speaker speaker = NearestSpeaker((Vector3)(((??)TuneQueue.LastOrigin) ?? ListenerPosition())); List<string> list2 = new List<string>(); for (int i = 0; i < list.Count; i++) { FmRadioPlayer val = list[i]; int? num = StationIndex(val); int value = -1; try { value = ((Il2CppArrayBase<MusicPlayerData>)(object)val.musicPlayers)?.Length ?? (-1); } catch { } MusicPlayer val2 = ActiveMusicPlayer(val); float value2 = DistanceToListener((Component)(object)val); bool flag = (Object)(object)speaker.Radio == (Object)(object)val; list2.Add($"radio[{i}]{(flag ? " <== TARGET" : "")}{(IsHeld(val) ? " HELD" : "")} station={num?.ToString() ?? "OFF"} players={value} dist={value2:F0}m active={(((Object)(object)val2 != (Object)null) ? "yes" : "none")} at {PathOf((Component)(object)val)}"); } return string.Join("\n", list2); } } internal enum QueueState { Idle, Preparing, Playing } internal sealed class RadioQueue { internal Vector3 Origin; private Speaker _speaker; private bool _radioResolved; private readonly List<Speaker> _targets = new List<Speaker>(); private nint _anchorKey = IntPtr.Zero; private readonly List<Track> _pending = new List<Track>(); private Track _current; private QueueState _state; private bool _retriedCurrent; private AudioClip _pendingClip; private float _nextRetry; private AudioClip _activeClip; private bool _reassertQueued; private float _playhead; private int _reassertAttempts; private float _nextReassert; private float _nextTopUp; private int _waitAttempts; private readonly Dictionary<nint, float> _lastPush = new Dictionary<nint, float>(); private AudioClip _nextClip; private Track _nextClipFor; private bool _preloading; private bool _audible; private bool _followEngine; private int _corrections; private float _correctionWindow; private float _holdUntil; private double _startedAtNetwork = -1.0; private double _pausedAtNetwork = -1.0; private float _silentSince = -1f; private bool _announcedStart; private string _heldStartFor; private double _heldStartAt = -1.0; private int _resolving; private bool _abandoned; internal QueueState State => _state; internal bool Idle { get { if (_state == QueueState.Idle && _pending.Count == 0) { return _resolving == 0; } return false; } } internal Track Current => _current; internal double StartedAt => _startedAtNetwork; internal int Waiting => _pending.Count; internal Speaker Radio { get { //IL_003a: Unknown result type (might be due to invalid IL or missing references) try { if (_speaker.Exists) { return _speaker; } } catch { _speaker = default(Speaker); } if (_radioResolved) { return default(Speaker); } Speaker speaker = RadioBridge.NearestSpeaker(Origin); if (!speaker.Exists) { return default(Speaker); } if (TuneQueue.ClaimedByAnother(speaker, this)) { return default(Speaker); } _speaker = speaker; _radioResolved = true; return _speaker; } } private bool RadioLost { get { if (!_radioResolved) { return false; } try { return !_speaker.Exists; } catch { return true; } } } private Speaker Anchor { get { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList<Speaker> readOnlyList = Targets(); if (readOnlyList.Count == 0) { return default(Speaker); } if (_anchorKey != IntPtr.Zero) { foreach (Speaker item in readOnlyList) { try { if (item.Exists && item.Key == _anchorKey && RadioBridge.StillVoicing(item, _activeClip)) { return item; } } catch { } } } Speaker result = default(Speaker); float num = float.MaxValue; Vector3 val = RadioBridge.ListenerPosition(); foreach (Speaker item2 in readOnlyList) { if (!item2.Exists || ((Object)(object)_activeClip != (Object)null && !RadioBridge.StillVoicing(item2, _activeClip))) { continue; } try { float num2 = Vector3.Distance(val, item2.Position); if (num2 < num) { num = num2; result = item2; } } catch { } } if (result.Exists) { _anchorKey = result.Key; return result; } return readOnlyList[0]; } } internal bool IsPaused => _pausedAtNetwork > 0.0; internal RadioQueue(Vector3 origin, Speaker speaker) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) Origin = origin; _speaker = speaker; _radioResolved = speaker.Exists; } internal void Abandon() { _abandoned = true; _pending.Clear(); } internal IReadOnlyList<Speaker> Targets() { _targets.Clear(); Speaker radio = Radio; if (!radio.Exists) { return _targets; } MusicPlayer val = radio.Player(allowOff: true); if (!radio.IsRadio && (Object)(object)val != (Object)null) { _targets.AddRange(Speaker.SetContaining(val)); return _targets; } _targets.Add(radio); return _targets; } internal string Where() { Speaker radio = Radio; if (radio.Exists) { return radio.Path; } return "a radio that has not loaded in yet"; } internal void Enqueue(string query, string requestedBy) { BigTunesBehaviour.Say("BigTunes: looking up \"" + query + "\"...", 4f); Reserve(query, requestedBy, SyncNet.Now, shared: false); } internal void AcceptShared(string query, string requestedBy, double at) { if ((_current == null || !(_current.Query == query) || !(Math.Abs(_current.QueuedAt - at) < 2.0)) && !_pending.Any((Track t) => t.Query == query && Math.Abs(t.QueuedAt - at) < 2.0)) { BigTunesBehaviour.Say($"BigTunes: {requestedBy} put on \"{query}\"...", 4f); SyncNet.Probe(); Reserve(query, requestedBy, at, shared: true); } } private void Reserve(string query, string requestedBy, double at, bool shared) { Track shell = new Track { Query = query, Title = query, RequestedBy = requestedBy, QueuedAt = at, FromRemote = shared }; Insert(shell); if (_pending.Count > 1 || _current != null) { BigTunesBehaviour.Say($"BigTunes: queued \"{query}\" (#{_pending.Count} in line)"); } _resolving++; Task.Run(async delegate { Track found = await TrackResolver.ResolveAsync(query, requestedBy).ConfigureAwait(continueOnCapturedContext: false); BigTunesBehaviour.OnMainThread(delegate { _resolving--; if (!_abandoned) { if (found == null) { BigTunesBehaviour.Say("BigTunes: could not find \"" + query + "\""); _pending.Remove(shell); if (shell == _current) { _current = null; _state = QueueState.Idle; } } else { shell.VideoId = found.VideoId; shell.Title = found.Title; shell.Duration = found.Duration; shell.FilePath = found.FilePath; shell.Resolved = true; if (shell == _current && _heldStartAt > 0.0 && _heldStartFor == shell.VideoId) { double heldStartAt = _heldStartAt; _heldStartAt = -1.0; _heldStartFor = null; AcceptStartTime(shell.VideoId, heldStartAt); } if (shell == _current && _state == QueueState.Preparing) { PrepareAndPlay(shell); } } } }); if (found != null) { await TrackResolver.EnsureAudioAsync(shell).ConfigureAwait(continueOnCapturedContext: false); } }); } private void Insert(Track track) { int num = _pending.Count; while (num > 0 && Follows(_pending[num - 1], track)) { num--; } _pending.Insert(num, track); } private static bool Follows(Track a, Track b) { if (a.QueuedAt != b.QueuedAt) { return a.QueuedAt > b.QueuedAt; } return string.CompareOrdinal(a.RequestedBy ?? "", b.RequestedBy ?? "") > 0; } internal void AcceptAnnounced(string videoId, string title, double startedAt) { if (_current != null && _current.VideoId == videoId && _state != QueueState.Idle && Math.Abs(startedAt - _startedAtNetwork) < 10.0) { BigTunesPlugin.Verbose("[sync] already playing that, ignoring."); return; } Track track = new Track { VideoId = videoId, Title = (string.IsNullOrWhiteSpace(title) ? videoId : title), RequestedBy = "a friend", FromRemote = true, QueuedAt = startedAt, Resolved = true }; SyncNet.ApplyingRemote = true; try { _pending.Clear(); StopCurrent(); _current = track; _state = QueueState.Preparing; _retriedCurrent = false; _startedAtNetwork = startedAt; PrepareAndPlay(track); } finally { SyncNet.ApplyingRemote = false; } } internal bool AcceptStartTime(string videoId, double startedAt) { //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown if (_current == null) { return false; } if (string.IsNullOrEmpty(_current.VideoId)) { if (_heldStartAt <= 0.0 || startedAt < _heldStartAt) { _heldStartFor = videoId; _heldStartAt = startedAt; BigTunesPlugin.Verbose($"[sync] holding announced start {startedAt:F2} for {videoId} " + "until the lookup comes back."); } return true; } if (_current.VideoId != videoId) { return false; } if (Math.Abs(_startedAtNetwork - startedAt) < 0.25) { return true; } if (_announcedStart && startedAt >= _startedAtNetwork) { BigTunesPlugin.Verbose($"[sync] ignoring a later start ({startedAt:F2} vs {_startedAtNetwork:F2})"); return true; } ManualLogSource logger = BigTunesPlugin.Logger; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(39, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[sync] adopting announced start "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<double>(startedAt, "F2"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" (was "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<double>(_startedAtNetwork, "F2"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")"); } logger.LogInfo(val); _startedAtNetwork = startedAt; _announcedStart = true; if ((Object)(object)_activeClip != (Object)null) { _playhead = OffsetNow(_activeClip); if (_state == QueueState.Playing) { RadioBridge.HoldPosition(Anchor, _activeClip, _playhead, 0.5f); } else if (RadioBridge.PlayOn(Targets(), _activeClip, _playhead) > 0) { BeginPlaying(_current); } } return true; } private static string Clock(float seconds) { TimeSpan timeSpan = TimeSpan.FromSeconds(Mathf.Max(0f, seconds)); if (timeSpan.Hours <= 0) { return timeSpan.ToString("m\\:ss"); } return timeSpan.ToString("h\\:mm\\:ss"); } internal void Pause(double at) { if (_state != QueueState.Playing) { BigTunesBehaviour.Say("BigTunes: nothing playing here to pause."); return; } if (IsPaused) { Resume(at); return; } _pausedAtNetwork = ((at > 0.0) ? at : SyncNet.Now); _playhead = OffsetNow(_activeClip); RadioBridge.SetPaused(Targets(), paused: true); BigTunesBehaviour.Say($"BigTunes: paused {_current?.Title} at {Clock(_playhead)}."); } internal void Resume(double at) { if (!IsPaused) { BigTunesBehaviour.Say("BigTunes: nothing is paused here."); return; } double num = ((at > 0.0) ? at : SyncNet.Now) - _pausedAtNetwork; if (num > 0.0) { _startedAtNetwork += num; } _pausedAtNetwork = -1.0; RadioBridge.SetPaused(Targets(), paused: false); _holdUntil = Time.realtimeSinceStartup + 2f; _playhead = OffsetNow(_activeClip); BigTunesBehaviour.Say("BigTunes: resumed " + _current?.Title + "."); } internal void Skip() { if (_current == null && _pending.Count == 0) { BigTunesBehaviour.Say("BigTunes: nothing playing here."); return; } BigTunesBehaviour.Say((_current != null) ? ("BigTunes: skipped " + _current.Title) : "BigTunes: skipped."); double now = SyncNet.Now; StopCurrent(); if (_pending.Count > 0) { StartNext(now); } } internal void Clear() { if (_pending.Count == 0) { BigTunesBehaviour.Say("BigTunes: nothing waiting in this queue."); return; } int count = _pending.Count; _pending.Clear(); BigTunesBehaviour.Say($"BigTunes: cleared {count} queued track(s) - this one keeps playing."); } internal void Stop() { bool num = _current != null || _pending.Count > 0; int count = _pending.Count; _pending.Clear(); StopCurrent(); SyncNet.AnnounceStop(); BigTunesBehaviour.Say(num ? ("BigTunes: stopped" + ((count > 0) ? $" and dropped {count} queued track(s)." : ".")) : "BigTunes: nothing playing here."); } internal void Restart() { if (_current == null || (Object)(object)_activeClip == (Object)null) { BigTunesBehaviour.Say("BigTunes: nothing playing here to restart."); return; } _startedAtNetwork = SyncNet.Now; _playhead = 0f; _followEngine = false; _corrections = 0; _holdUntil = Time.realtimeSinceStartup + 2f; RadioBridge.PlayOn(Targets(), _activeClip); BigTunesBehaviour.Say("BigTunes: restarted " + _current.Title); } internal void Tick() { if (_abandoned) { return; } if (RadioLost && (_state != QueueState.Idle || _pending.Count > 0)) { BigTunesPlugin.Verbose("[queue] its radio has gone; dropping the queue."); _pending.Clear(); StopCurrent(); return; } switch (_state) { case QueueState.Idle: if (_pending.Count > 0) { StartNext(0.0); } break; case QueueState.Preparing: if ((Object)(object)_pendingClip != (Object)null && _current != null && Time.realtimeSinceStartup >= _nextRetry) { _waitAttempts++; _nextRetry = Time.realtimeSinceStartup + ((_waitAttempts < 6) ? 0.5f : 2f); float num = OffsetNow(_pendingClip); if (RadioBridge.PlayOn(Targets(), _pendingClip, num, allowOff: false, _waitAttempts > 3) > 0) { _playhead = num; _waitAttempts = 0; BeginPlaying(_current); } } break; case QueueState.Playing: TickPlaying(); break; } } private void TickPlaying() { //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) if (IsPaused) { return; } Speaker anchor = Anchor; float num = RadioBridge.CurrentPlayhead(anchor, _activeClip); _audible = num >= 0f; if (_audible) { if (_followEngine) { _playhead = num; } else if (Time.realtimeSinceStartup < _holdUntil) { _playhead += Time.deltaTime; RadioBridge.HoldPosition(anchor, _activeClip, _playhead, 0.4f); } else if (Mathf.Abs(num - _playhead) > 5f) { RadioBridge.HoldPosition(anchor, _activeClip, _playhead, 0.4f); _holdUntil = Time.realtimeSinceStartup + 1f; NoteCorrection(); } else { _playhead = num; } RadioBridge.MaybeReportAudioPath(anchor, _activeClip, _playhead); _silentSince = -1f; } else if ((Object)(object)_activeClip != (Object)null && !_reassertQueued) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_silentSince < 0f) { _silentSince = realtimeSinceStartup; } else { _silentSince = -1f; _reassertQueued = true; BigTunesPlugin.Verbose("[retune] the track stopped coming out of the speaker - putting it back."); } } if (Targets().Count > 1 && (Object)(object)_activeClip != (Object)null && Time.realtimeSinceStartup >= _nextTopUp) { _nextTopUp = Time.realtimeSinceStartup + 2f; _reassertQueued = false; float num2 = OffsetNow(_activeClip); Vector3 val = RadioBridge.ListenerPosition(); float num3 = Mathf.Max(20f, (float)BigTunesPlugin.AudibleDistance.Value * 3f); float realtimeSinceStartup2 = Time.realtimeSinceStartup; Speaker[] array = new Speaker[1]; foreach (Speaker item in Targets()) { if (!item.Exists) { continue; } float num4; try { num4 = Vector3.Distance(val, item.Position); } catch { continue; } if (num4 > num3 || RadioBridge.StillVoicing(item, _activeClip)) { continue; } nint key = item.Key; if (key != IntPtr.Zero && (!_lastPush.TryGetValue(key, out var value) || !(realtimeSinceStartup2 - value < 15f))) { _lastPush[key] = realtimeSinceStartup2; array[0] = item; if (RadioBridge.PlayOn(array, _activeClip, num2, allowOff: false, quiet: true) > 0) { _playhead = num2; _holdUntil = Time.realtimeSinceStartup + 2f; BigTunesPlugin.Verbose($"[speakers] {item.Path} joined at {num2:F0}s ({num4:F0}m)"); } } } } if (_reassertQueued && (Object)(object)_activeClip != (Object)null && Time.realtimeSinceStartup >= _nextReassert) { _nextReassert = Time.realtimeSinceStartup + 1f; _reassertQueued = false; if (RadioBridge.StillVoicing(anchor, _activeClip)) { _reassertAttempts = 0; } else if (RadioBridge.PlayOn(Targets(), _activeClip, _playhead) > 0) { _holdUntil = Time.realtimeSinceStartup + 2f; _reassertAttempts = 0; BigTunesPlugin.Verbose($"[retune] resumed at {_playhead:F0}s"); } else { _reassertQueued = true; if (_reassertAttempts == 0) { BigTunesPlugin.Logger.LogInfo((object)("[retune] " + RadioBridge.WhyNot(Targets()))); } if (++_reassertAttempts == 20) { BigTunesBehaviour.Say("BigTunes: waiting for that station to load before the song comes back. Switch to a station that is playing if it does not.", 10f); } } } if (!_preloading && (Object)(object)_nextClip == (Object)null && _pending.Count > 0 && _current != null && _current.Duration > 0f && _startedAtNetwork > 0.0 && SyncNet.Now - _startedAtNetwork >= (double)(_current.Duration - 20f)) { Track next = _pending[0]; if (next.Resolved && !string.IsNul