Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Boombox v0.1.0
plugins/tech.rethunk.mortar.Boombox.dll
Decompiled 2 days agousing 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"; } }