Decompiled source of Boombox v0.1.0

plugins/tech.rethunk.mortar.Boombox.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using ConfigSync;
using Microsoft.CodeAnalysis;
using MonoMod.RuntimeDetour;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Rethunk Tech")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Rethunk Tech")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+d755f24186e98e4b24243c8777cb7eb1eb9f79f6")]
[assembly: AssemblyProduct("tech.rethunk.mortar.Boombox")]
[assembly: AssemblyTitle("Boombox")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Rethunk-Tech/mortar-lc-boombox")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Boombox
{
	[BepInPlugin("tech.rethunk.mortar.Boombox", "Boombox", "0.1.0")]
	[BepInProcess("Lethal Company.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		private sealed class Track
		{
			public int Index = -1;

			public float Time;

			public readonly List<int> History = new List<int>();

			public readonly List<int> Order = new List<int>();

			public int Position;
		}

		private sealed class Loader : MonoBehaviour
		{
			private float untilSync = 5f;

			private void Update()
			{
				untilSync -= Time.unscaledDeltaTime;
				if (untilSync <= 0f)
				{
					untilSync = 5f;
					SendState();
				}
			}
		}

		[CompilerGenerated]
		private static class <>O
		{
			public static Action<Action<BoomboxItem, bool, bool>, BoomboxItem, bool, bool> <0>__StartMusic;

			public static Action<Action<BoomboxItem>, BoomboxItem> <1>__Pocketed;

			public static Action<Action<GrabbableObject>, GrabbableObject> <2>__Equipped;

			public static Action<Action<GrabbableObject>, GrabbableObject> <3>__Dropped;

			public static Action<Action<GrabbableObject, bool>, GrabbableObject, bool> <4>__Interacted;

			public static Action<Action<StartOfRound>, StartOfRound> <5>__ShipLoaded;

			public static Action<IReadOnlyCollection<string>> <6>__Reload;

			public static Func<string, bool> <7>__Exists;

			public static HandleNamedMessageDelegate <8>__Received;
		}

		private const string Message = "tech.rethunk.mortar.Boombox";

		private const float SyncEvery = 5f;

		private const float Drift = 1f;

		private static readonly List<Hook> hooks = new List<Hook>();

		private static string[] files = Array.Empty<string>();

		private static readonly ConditionalWeakTable<BoomboxItem, Dictionary<string, AudioClip>> loaded = new ConditionalWeakTable<BoomboxItem, Dictionary<string, AudioClip>>();

		private static readonly ConditionalWeakTable<BoomboxItem, Track> tracks = new ConditionalWeakTable<BoomboxItem, Track>();

		private static SyncedEntry<bool> mixGame = null;

		private static SyncedEntry<bool> nextSong = null;

		private static SyncedEntry<bool> newOnToggle = null;

		private static Loader runner = null;

		internal static ManualLogSource Log { get; private set; } = null;

		private void Awake()
		{
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Expected O, but got Unknown
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Expected O, but got Unknown
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0164: Expected O, but got Unknown
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Expected O, but got Unknown
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Expected O, but got Unknown
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_0220: Expected O, but got Unknown
			//IL_0259: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			if (Chainloader.PluginInfos.ContainsKey("com.steven.lethalcompany.boomboxmusic"))
			{
				Log.LogWarning((object)"Custom Boombox Music is installed, so it plays the songs instead");
				return;
			}
			SyncedConfig val = SyncedConfig.For((BaseUnityPlugin)(object)this);
			mixGame = val.Share<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Boombox", "Include game songs", false, "Keep the boombox's own songs in the list with the packs' songs."), (Sync)1);
			nextSong = val.Share<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Boombox", "Next song on each press", false, "Play the list in order instead of a random song."), (Sync)1);
			newOnToggle = val.Share<bool>(((BaseUnityPlugin)this).Config.Bind<bool>("Boombox", "New song when turned on", false, "Start a different song each time it's turned on. Off carries on with the song where it stopped."), (Sync)1);
			hooks.Add(new Hook((MethodBase)typeof(BoomboxItem).GetMethod("StartMusic", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (Delegate)new Action<Action<BoomboxItem, bool, bool>, BoomboxItem, bool, bool>(StartMusic)));
			hooks.Add(new Hook((MethodBase)typeof(BoomboxItem).GetMethod("PocketItem"), (Delegate)new Action<Action<BoomboxItem>, BoomboxItem>(Pocketed)));
			hooks.Add(new Hook((MethodBase)typeof(GrabbableObject).GetMethod("EquipItem"), (Delegate)new Action<Action<GrabbableObject>, GrabbableObject>(Equipped)));
			hooks.Add(new Hook((MethodBase)typeof(GrabbableObject).GetMethod("DiscardItem"), (Delegate)new Action<Action<GrabbableObject>, GrabbableObject>(Dropped)));
			hooks.Add(new Hook((MethodBase)typeof(GrabbableObject).GetMethod("ItemInteractLeftRight"), (Delegate)new Action<Action<GrabbableObject, bool>, GrabbableObject, bool>(Interacted)));
			hooks.Add(new Hook((MethodBase)typeof(StartOfRound).GetMethod("Start", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), (Delegate)new Action<Action<StartOfRound>, StartOfRound>(ShipLoaded)));
			files = Files();
			Log.LogInfo((object)string.Format("{0} {1} found {2} songs", "Boombox", "0.1.0", files.Length));
			GameObject val2 = new GameObject("Boombox songs");
			Object.DontDestroyOnLoad((Object)(object)val2);
			((Object)val2).hideFlags = (HideFlags)61;
			runner = val2.AddComponent<Loader>();
			Watch.Start(val2, Reload, Paths.PluginPath, Path.Combine(Paths.BepInExRootPath, "Custom Songs"));
		}

		private static string[] Files()
		{
			IEnumerable<string> source = (from dir in Directory.GetDirectories(Paths.PluginPath, "mortar", SearchOption.AllDirectories)
				select Path.Combine(dir, "boombox")).Concat(Directory.GetDirectories(Paths.PluginPath, "Custom Songs", SearchOption.AllDirectories)).Append(Path.Combine(Paths.BepInExRootPath, "Custom Songs", "Boombox Music")).Where(Directory.Exists);
			return (from f in source.SelectMany((string dir) => Directory.GetFiles(dir, "*", SearchOption.AllDirectories))
				where (int)Type(f) > 0
				select f).Distinct().OrderBy<string, string>((string f) => Path.GetFileName(f), StringComparer.Ordinal).ToArray();
		}

		private static void Reload(IReadOnlyCollection<string> changed)
		{
			if (!changed.Any((string f) => (int)Type(f) != 0 || Path.GetExtension(f) == ""))
			{
				return;
			}
			List<(BoomboxItem, Track, string)> list = new List<(BoomboxItem, Track, string)>();
			BoomboxItem[] array = Object.FindObjectsOfType<BoomboxItem>();
			foreach (BoomboxItem val in array)
			{
				if (tracks.TryGetValue(val, out Track value) && value.Index >= 0 && value.Index < Count(val))
				{
					list.Add((val, value, KeyOf(val, value.Index)));
				}
			}
			files = Files();
			BoomboxItem[] array2 = Object.FindObjectsOfType<BoomboxItem>();
			foreach (BoomboxItem key in array2)
			{
				if (!loaded.TryGetValue(key, out Dictionary<string, AudioClip> value2))
				{
					continue;
				}
				foreach (string item4 in changed)
				{
					value2.Remove(item4);
				}
			}
			foreach (var item5 in list)
			{
				BoomboxItem item = item5.Item1;
				Track item2 = item5.Item2;
				string item3 = item5.Item3;
				item2.Index = IndexOf(item, item3);
				item2.History.Clear();
				item2.Order.Clear();
			}
			Log.LogInfo((object)$"Found {files.Length} songs");
		}

		private static AudioType Type(string file)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			return (AudioType)(Path.GetExtension(file).ToLowerInvariant() switch
			{
				".mp3" => 13, 
				".ogg" => 14, 
				".wav" => 20, 
				_ => 0, 
			});
		}

		private static string FileUrl(string file)
		{
			return "file:///" + file.Replace('\\', '/').Replace("%", "%25").Replace("#", "%23");
		}

		private static int GameSongs(BoomboxItem boombox)
		{
			if (!mixGame.Value && files.Length != 0)
			{
				return 0;
			}
			return boombox.musicAudios.Length;
		}

		private static int Count(BoomboxItem boombox)
		{
			return GameSongs(boombox) + files.Length;
		}

		private static string KeyOf(BoomboxItem boombox, int index)
		{
			int num = GameSongs(boombox);
			if (index >= num)
			{
				return Path.GetFileName(files[index - num]);
			}
			return "game:" + ((Object)boombox.musicAudios[index]).name;
		}

		private static int IndexOf(BoomboxItem boombox, string key)
		{
			for (int i = 0; i < Count(boombox); i++)
			{
				if (KeyOf(boombox, i) == key)
				{
					return i;
				}
			}
			return -1;
		}

		private static void StartMusic(Action<BoomboxItem, bool, bool> orig, BoomboxItem self, bool startMusic, bool pitchDown)
		{
			Track orCreateValue = tracks.GetOrCreateValue(self);
			if (!startMusic)
			{
				if (self.isPlayingMusic)
				{
					orCreateValue.Time = self.boomboxAudio.time;
				}
				orig(self, startMusic, pitchDown);
				Name(self, null);
				return;
			}
			self.boomboxAudio.pitch = 1f;
			((GrabbableObject)self).isBeingUsed = true;
			self.isPlayingMusic = true;
			if (orCreateValue.Index >= 0 && orCreateValue.Index < Count(self) && !newOnToggle.Value)
			{
				Play(self, orCreateValue.Index, orCreateValue.Time);
			}
			else
			{
				Next(self);
			}
		}

		private static void Next(BoomboxItem boombox)
		{
			int num = Count(boombox);
			if (num != 0)
			{
				Track orCreateValue = tracks.GetOrCreateValue(boombox);
				if (orCreateValue.Index >= 0)
				{
					orCreateValue.History.Add(orCreateValue.Index);
				}
				int index = (nextSong.Value ? ((orCreateValue.Index + 1) % num) : Shuffled(boombox, orCreateValue, num));
				Play(boombox, index, 0f);
			}
		}

		private static int Shuffled(BoomboxItem boombox, Track track, int count)
		{
			if (track.Order.Count != count || track.Position >= count)
			{
				track.Order.Clear();
				track.Order.AddRange(Enumerable.Range(0, count));
				for (int num = count - 1; num > 0; num--)
				{
					int num2 = boombox.musicRandomizer.Next(0, num + 1);
					List<int> order = track.Order;
					int index = num;
					List<int> order2 = track.Order;
					int index2 = num2;
					int value = track.Order[num2];
					int value2 = track.Order[num];
					order[index] = value;
					order2[index2] = value2;
				}
				if (count > 1 && track.Order[0] == track.Index)
				{
					List<int> order3 = track.Order;
					List<int> order = track.Order;
					int index2 = count - 1;
					int index = track.Order[count - 1];
					int value2 = track.Order[0];
					order3[0] = index;
					order[index2] = value2;
				}
				track.Position = 0;
			}
			return track.Order[track.Position++];
		}

		private static void Previous(BoomboxItem boombox)
		{
			Track orCreateValue = tracks.GetOrCreateValue(boombox);
			int num = Count(boombox);
			if (orCreateValue.History.Count == 0)
			{
				if (num > 0)
				{
					Play(boombox, ((orCreateValue.Index - 1) % num + num) % num, 0f);
				}
			}
			else
			{
				List<int> history = orCreateValue.History;
				int index = history[history.Count - 1];
				orCreateValue.History.RemoveAt(orCreateValue.History.Count - 1);
				Play(boombox, index, 0f);
			}
		}

		private static void Play(BoomboxItem boombox, int index, float time)
		{
			Track orCreateValue = tracks.GetOrCreateValue(boombox);
			orCreateValue.Index = index;
			orCreateValue.Time = time;
			int num = GameSongs(boombox);
			if (index < num)
			{
				Start(boombox, boombox.musicAudios[index], time);
				return;
			}
			string text = files[index - num];
			if (loaded.GetOrCreateValue(boombox).TryGetValue(text, out AudioClip value))
			{
				Start(boombox, value, time);
			}
			else
			{
				((MonoBehaviour)runner).StartCoroutine(Load(boombox, text, index));
			}
		}

		private static IEnumerator Load(BoomboxItem boombox, string file, int index)
		{
			UnityWebRequest request = UnityWebRequestMultimedia.GetAudioClip(FileUrl(file), Type(file));
			try
			{
				((DownloadHandlerAudioClip)request.downloadHandler).streamAudio = true;
				yield return request.SendWebRequest();
				if ((int)request.result != 1)
				{
					Log.LogError((object)(file + ": " + request.error));
				}
				else if (!((Object)(object)boombox == (Object)null))
				{
					Dictionary<string, AudioClip> orCreateValue = loaded.GetOrCreateValue(boombox);
					if (!orCreateValue.TryGetValue(file, out var value))
					{
						value = DownloadHandlerAudioClip.GetContent(request);
						((Object)value).name = Tags.Title(file) ?? Path.GetFileNameWithoutExtension(file);
						orCreateValue[file] = value;
					}
					if ((Object)(object)boombox != (Object)null && boombox.isPlayingMusic && tracks.TryGetValue(boombox, out Track value2) && value2.Index == index)
					{
						Start(boombox, value, value2.Time);
					}
				}
			}
			finally
			{
				((IDisposable)request)?.Dispose();
			}
		}

		private static void Start(BoomboxItem boombox, AudioClip clip, float time)
		{
			boombox.boomboxAudio.clip = clip;
			boombox.boomboxAudio.time = Mathf.Clamp(time, 0f, Mathf.Max(0f, clip.length - 0.1f));
			boombox.boomboxAudio.Play();
			Name(boombox, ((Object)clip).name);
		}

		private static void Name(BoomboxItem boombox, string? song)
		{
			((GrabbableObject)boombox).customGrabTooltip = ((song == null) ? null : ("Grab : [E]\n" + song));
			if ((Object)(object)((GrabbableObject)boombox).playerHeldBy != (Object)null && (Object)(object)((GrabbableObject)boombox).playerHeldBy == (Object)(object)GameNetworkManager.Instance.localPlayerController)
			{
				Tips(boombox);
			}
		}

		private static void Tips(BoomboxItem boombox)
		{
			string[] toolTips = ((GrabbableObject)boombox).itemProperties.toolTips;
			int num = 0;
			string[] array = new string[2 + toolTips.Length];
			ReadOnlySpan<string> readOnlySpan = new ReadOnlySpan<string>(toolTips);
			readOnlySpan.CopyTo(new Span<string>(array).Slice(num, readOnlySpan.Length));
			num += readOnlySpan.Length;
			array[num] = "Previous song : [Q]";
			num++;
			array[num] = "Next song : [E]";
			string[] array2 = array;
			if (boombox.isPlayingMusic && (Object)(object)boombox.boomboxAudio.clip != (Object)null)
			{
				array = array2;
				num = 0;
				toolTips = new string[1 + array.Length];
				readOnlySpan = new ReadOnlySpan<string>(array);
				readOnlySpan.CopyTo(new Span<string>(toolTips).Slice(num, readOnlySpan.Length));
				num += readOnlySpan.Length;
				toolTips[num] = ((Object)boombox.boomboxAudio.clip).name;
				array2 = toolTips;
			}
			HUDManager.Instance.ChangeControlTipMultiple(array2, true, ((GrabbableObject)boombox).itemProperties);
		}

		private static void Equipped(Action<GrabbableObject> orig, GrabbableObject self)
		{
			orig(self);
			BoomboxItem val = (BoomboxItem)(object)((self is BoomboxItem) ? self : null);
			if (val != null && (Object)(object)((GrabbableObject)val).playerHeldBy != (Object)null)
			{
				((GrabbableObject)val).itemProperties.syncInteractLRFunction = true;
				((GrabbableObject)val).playerHeldBy.equippedUsableItemQE = true;
				if ((Object)(object)((GrabbableObject)val).playerHeldBy == (Object)(object)GameNetworkManager.Instance.localPlayerController)
				{
					Tips(val);
				}
			}
		}

		private static void Dropped(Action<GrabbableObject> orig, GrabbableObject self)
		{
			if (self is BoomboxItem && (Object)(object)self.playerHeldBy != (Object)null)
			{
				self.playerHeldBy.equippedUsableItemQE = false;
			}
			orig(self);
		}

		private static void Pocketed(Action<BoomboxItem> orig, BoomboxItem self)
		{
			if ((Object)(object)((GrabbableObject)self).playerHeldBy != (Object)null)
			{
				((GrabbableObject)self).playerHeldBy.equippedUsableItemQE = false;
			}
			orig(self);
		}

		private static void Interacted(Action<GrabbableObject, bool> orig, GrabbableObject self, bool right)
		{
			orig(self, right);
			BoomboxItem val = (BoomboxItem)(object)((self is BoomboxItem) ? self : null);
			if (val == null)
			{
				return;
			}
			((GrabbableObject)val).isBeingUsed = val.isPlayingMusic;
			if (val.isPlayingMusic)
			{
				if (right)
				{
					Next(val);
				}
				else
				{
					Previous(val);
				}
			}
		}

		private static void ShipLoaded(Action<StartOfRound> orig, StartOfRound self)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			orig(self);
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton == null)
			{
				return;
			}
			CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
			if (customMessagingManager != null)
			{
				object obj = <>O.<8>__Received;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = Received;
					<>O.<8>__Received = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("tech.rethunk.mortar.Boombox", (HandleNamedMessageDelegate)obj);
			}
		}

		private unsafe static void SendState()
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.CustomMessagingManager == null || singleton.ConnectedClientsIds.Count < 2)
			{
				return;
			}
			Track value;
			BoomboxItem[] array = (from b in Object.FindObjectsOfType<BoomboxItem>()
				where b.isPlayingMusic && ((NetworkBehaviour)b).IsSpawned && tracks.TryGetValue(b, out value) && value.Index >= 0 && value.Index < Count(b)
				select b).ToArray();
			if (array.Length == 0)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, 4096);
			try
			{
				int num = array.Length;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref num, default(ForPrimitives));
				BoomboxItem[] array2 = array;
				foreach (BoomboxItem val2 in array2)
				{
					ulong networkObjectId = ((NetworkBehaviour)val2).NetworkObjectId;
					((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
					((FastBufferWriter)(ref val)).WriteValueSafe(KeyOf(val2, tracks.GetOrCreateValue(val2).Index), false);
					float time = val2.boomboxAudio.time;
					((FastBufferWriter)(ref val)).WriteValueSafe<float>(ref time, default(ForPrimitives));
				}
				foreach (ulong connectedClientsId in singleton.ConnectedClientsIds)
				{
					if (connectedClientsId != 0L)
					{
						singleton.CustomMessagingManager.SendNamedMessage("tech.rethunk.mortar.Boombox", connectedClientsId, val, (NetworkDelivery)3);
					}
				}
			}
			finally
			{
				((IDisposable)(*(FastBufferWriter*)(&val))/*cast due to .constrained prefix*/).Dispose();
			}
		}

		private static void Received(ulong sender, FastBufferReader reader)
		{
			//IL_0015: 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_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			NetworkManager singleton = NetworkManager.Singleton;
			if (singleton.IsServer)
			{
				return;
			}
			int num = default(int);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
			ulong key = default(ulong);
			string key2 = default(string);
			float num2 = default(float);
			BoomboxItem val = default(BoomboxItem);
			for (int i = 0; i < num; i++)
			{
				((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref key, default(ForPrimitives));
				((FastBufferReader)(ref reader)).ReadValueSafe(ref key2, false);
				((FastBufferReader)(ref reader)).ReadValueSafe<float>(ref num2, default(ForPrimitives));
				if (singleton.SpawnManager.SpawnedObjects.TryGetValue(key, out var value) && ((Component)value).TryGetComponent<BoomboxItem>(ref val) && val.isPlayingMusic)
				{
					Track orCreateValue = tracks.GetOrCreateValue(val);
					int num3 = IndexOf(val, key2);
					if (num3 < 0)
					{
						orCreateValue.Index = -1;
						val.boomboxAudio.Stop();
						Name(val, null);
					}
					else if (orCreateValue.Index != num3)
					{
						Play(val, num3, num2);
					}
					else if ((Object)(object)val.boomboxAudio.clip != (Object)null && Mathf.Abs(val.boomboxAudio.time - num2) > 1f)
					{
						val.boomboxAudio.time = Mathf.Clamp(num2, 0f, Mathf.Max(0f, val.boomboxAudio.clip.length - 0.1f));
					}
				}
			}
		}
	}
	internal static class Tags
	{
		private const int ReadLimit = 262144;

		public static string? Title(string file)
		{
			try
			{
				byte[] b = Head(file);
				string text;
				string text2;
				if (Path.GetExtension(file).Equals(".ogg", StringComparison.OrdinalIgnoreCase))
				{
					(text, text2) = Vorbis(b);
				}
				else
				{
					(text, text2) = Id3(b);
				}
				if (string.IsNullOrWhiteSpace(text2))
				{
					return null;
				}
				return string.IsNullOrWhiteSpace(text) ? text2.Trim() : (text.Trim() + " - " + text2.Trim());
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static byte[] Head(string file)
		{
			using FileStream fileStream = File.OpenRead(file);
			byte[] array = new byte[Math.Min(fileStream.Length, 262144L)];
			int num;
			for (int i = 0; i < array.Length; i += num)
			{
				num = fileStream.Read(array, i, array.Length - i);
				if (num == 0)
				{
					break;
				}
			}
			return array;
		}

		private static (string?, string?) Id3(byte[] b)
		{
			if (b.Length < 10 || b[0] != 73 || b[1] != 68 || b[2] != 51)
			{
				return (null, null);
			}
			int num = b[3];
			int num2 = Math.Min(b.Length, 10 + Syncsafe(b, 6));
			int count = ((num == 2) ? 3 : 4);
			int num3 = ((num == 2) ? 6 : 10);
			string item = null;
			string item2 = null;
			int num4 = 10;
			while (num4 + num3 <= num2)
			{
				string text = Encoding.ASCII.GetString(b, num4, count);
				if (text[0] == '\0')
				{
					break;
				}
				int num5 = num switch
				{
					2 => (b[num4 + 3] << 16) | (b[num4 + 4] << 8) | b[num4 + 5], 
					4 => Syncsafe(b, num4 + 4), 
					_ => (b[num4 + 4] << 24) | (b[num4 + 5] << 16) | (b[num4 + 6] << 8) | b[num4 + 7], 
				};
				int num6 = num4 + num3;
				if (num5 <= 0 || num6 + num5 > num2)
				{
					break;
				}
				if ((text == "TIT2" || text == "TT2") ? true : false)
				{
					item2 = Text(b, num6, num5);
				}
				else if ((text == "TPE1" || text == "TP1") ? true : false)
				{
					item = Text(b, num6, num5);
				}
				num4 = num6 + num5;
			}
			return (item, item2);
		}

		private static int Syncsafe(byte[] b, int at)
		{
			return (b[at] << 21) | (b[at + 1] << 14) | (b[at + 2] << 7) | b[at + 3];
		}

		private static string Text(byte[] b, int at, int size)
		{
			Encoding encoding = b[at] switch
			{
				1 => Encoding.Unicode, 
				2 => Encoding.BigEndianUnicode, 
				3 => Encoding.UTF8, 
				_ => Encoding.GetEncoding("ISO-8859-1"), 
			};
			int num = at + 1;
			int num2 = size - 1;
			if (b[at] == 1 && num2 >= 2)
			{
				encoding = ((b[num] == 254) ? Encoding.BigEndianUnicode : Encoding.Unicode);
				num += 2;
				num2 -= 2;
			}
			return encoding.GetString(b, num, num2).TrimEnd('\0');
		}

		private static (string?, string?) Vorbis(byte[] b)
		{
			byte[] array = new byte[7] { 3, 118, 111, 114, 98, 105, 115 };
			int num = IndexOf(b, array);
			if (num < 0)
			{
				return (null, null);
			}
			num += array.Length;
			num += 4 + BitConverter.ToInt32(b, num);
			int num2 = BitConverter.ToInt32(b, num);
			num += 4;
			string item = null;
			string item2 = null;
			for (int i = 0; i < num2; i++)
			{
				if (num + 4 > b.Length)
				{
					break;
				}
				int num3 = BitConverter.ToInt32(b, num);
				num += 4;
				if (num3 < 0 || num + num3 > b.Length)
				{
					break;
				}
				string text = Encoding.UTF8.GetString(b, num, num3);
				num += num3;
				int num4 = text.IndexOf('=');
				if (num4 > 0)
				{
					string text2 = text.Substring(0, num4).ToUpperInvariant();
					if (text2 == "TITLE")
					{
						item2 = text.Substring(num4 + 1);
					}
					else if (text2 == "ARTIST")
					{
						item = text.Substring(num4 + 1);
					}
				}
			}
			return (item, item2);
		}

		private static int IndexOf(byte[] b, byte[] marker)
		{
			for (int i = 0; i + marker.Length <= b.Length; i++)
			{
				int j;
				for (j = 0; j < marker.Length && b[i + j] == marker[j]; j++)
				{
				}
				if (j == marker.Length)
				{
					return i;
				}
			}
			return -1;
		}
	}
	internal sealed class Watch : MonoBehaviour
	{
		private static readonly TimeSpan quiet = TimeSpan.FromSeconds(1.0);

		private readonly List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();

		private readonly object gate = new object();

		private readonly HashSet<string> changed = new HashSet<string>();

		private DateTime last;

		private Action<IReadOnlyCollection<string>> onChange = delegate
		{
		};

		public static void Start(GameObject host, Action<IReadOnlyCollection<string>> onChange, params string[] dirs)
		{
			Watch watch = host.AddComponent<Watch>();
			watch.onChange = onChange;
			foreach (string item in dirs.Where(Directory.Exists))
			{
				try
				{
					FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(item)
					{
						IncludeSubdirectories = true
					};
					fileSystemWatcher.Created += delegate(object _, FileSystemEventArgs e)
					{
						watch.Note(e.FullPath);
					};
					fileSystemWatcher.Changed += delegate(object _, FileSystemEventArgs e)
					{
						watch.Note(e.FullPath);
					};
					fileSystemWatcher.Deleted += delegate(object _, FileSystemEventArgs e)
					{
						watch.Note(e.FullPath);
					};
					fileSystemWatcher.Renamed += delegate(object _, RenamedEventArgs e)
					{
						watch.Note(e.OldFullPath);
						watch.Note(e.FullPath);
					};
					fileSystemWatcher.EnableRaisingEvents = true;
					watch.watchers.Add(fileSystemWatcher);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Can't watch " + item + " for new files, so they need a restart: " + ex.Message));
				}
			}
		}

		private void Note(string path)
		{
			lock (gate)
			{
				changed.Add(path);
				last = DateTime.UtcNow;
			}
		}

		private void Update()
		{
			string[] obj;
			lock (gate)
			{
				if (changed.Count == 0 || DateTime.UtcNow - last < quiet)
				{
					return;
				}
				obj = changed.ToArray();
				changed.Clear();
			}
			try
			{
				onChange((IReadOnlyCollection<string>)(object)obj);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Couldn't pick up changed files: {arg}");
			}
		}

		private void OnDestroy()
		{
			foreach (FileSystemWatcher watcher in watchers)
			{
				watcher.Dispose();
			}
		}
	}
	internal static class MyPluginInfo
	{
		public const string Id = "tech.rethunk.mortar.Boombox";

		public const string Name = "Boombox";

		public const string Version = "0.1.0";
	}
}