Decompiled source of OnTogetherLocalPlaylist v0.6.0

OnTogetherLocalPlaylist.dll

Decompiled 4 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SleepeyDev.OnTogetherLocalPlaylist.Net;
using SleepeyDev.OnTogetherLocalPlaylist.Patches;
using SleepeyDev.OnTogetherLocalPlaylist.Platform;
using SleepeyDev.OnTogetherLocalPlaylist.Playlist;
using SleepeyDev.OnTogetherLocalPlaylist.Share;
using SleepeyDev.OnTogetherLocalPlaylist.UI;
using Steamworks;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.UI;

[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("SleepeyDev")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.6.0.0")]
[assembly: AssemblyInformationalVersion("0.6.0+63ead13a0b394a0e0ee425c279a9e5b8a274470f")]
[assembly: AssemblyProduct("OnTogetherLocalPlaylist")]
[assembly: AssemblyTitle("OnTogetherLocalPlaylist")]
[assembly: AssemblyVersion("0.6.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 SleepeyDev.OnTogetherLocalPlaylist
{
	public static class HarmonyBootstrap
	{
		private static readonly Type[] PatchClasses = new Type[4]
		{
			typeof(SoundManagerPatches),
			typeof(UIManagerPatches),
			typeof(LifecyclePatches),
			typeof(UiBuildPatches)
		};

		public static void ApplyAll()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			Harmony val = new Harmony("com.sleepeydev.ontogether.localplaylist");
			int num = 0;
			Type[] patchClasses = PatchClasses;
			foreach (Type type in patchClasses)
			{
				try
				{
					val.CreateClassProcessor(type).Patch();
					num++;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("Patch class " + type.Name + " failed to apply (that feature is off): " + ex.Message));
				}
			}
			Plugin.Log.LogInfo((object)("Applied " + num + "/" + PatchClasses.Length + " patch classes."));
		}
	}
	public static class MainThread
	{
		private static readonly ConcurrentQueue<Action> Queue = new ConcurrentQueue<Action>();

		public static void Post(Action action)
		{
			if (action != null)
			{
				Queue.Enqueue(action);
			}
		}

		public static void Drain(int maxPerFrame = 64)
		{
			for (int i = 0; i < maxPerFrame; i++)
			{
				if (!Queue.TryDequeue(out var result))
				{
					break;
				}
				try
				{
					result();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("Main-thread action threw: " + ex));
				}
			}
		}
	}
	public static class ModConfig
	{
		private const int CurrentLayoutVersion = 7;

		private static readonly string Sep = Path.DirectorySeparatorChar.ToString();

		public static ConfigEntry<int> LayoutVersion;

		public static ConfigEntry<bool> PlaylistEnabled;

		public static ConfigEntry<string> MusicFolder;

		public static ConfigEntry<string> MixtapeName;

		public static ConfigEntry<bool> IncludeSubfolders;

		public static ConfigEntry<bool> AllowTrackerFormats;

		public static ConfigEntry<int> MaxTracks;

		public static ConfigEntry<bool> SeekBarEnabled;

		public static ConfigEntry<bool> SeekBarOnVanillaMixtapes;

		public static ConfigEntry<float> PanelGrow;

		public static ConfigEntry<float> SeekBarX;

		public static ConfigEntry<float> SeekBarY;

		public static ConfigEntry<float> SeekBarWidth;

		public static ConfigEntry<float> SeekTimeX;

		public static ConfigEntry<float> SeekTimeFontScale;

		public static ConfigEntry<bool> ModPanelEnabled;

		public static ConfigEntry<float> ModPanelHeight;

		public static ConfigEntry<float> ModRowTextRight;

		public static ConfigEntry<float> ModRowButtonRight;

		public static ConfigEntry<float> ModRowButtonWidth;

		public static ConfigEntry<float> ModRowButtonGap;

		public static ConfigEntry<float> ModRowTextWidth;

		public static ConfigEntry<bool> PickFolderByFile;

		public static ConfigEntry<float> ModRowHeight;

		public static ConfigEntry<float> ModRowFontScale;

		public static ConfigEntry<string> PickFolderLabel;

		public static ConfigEntry<string> ReloadLabel;

		public static ConfigEntry<bool> WatchFolder;

		public static ConfigEntry<int> WatchDebounceMs;

		public static ConfigEntry<bool> SharingEnabled;

		public static ConfigEntry<string> LiveTapeName;

		public static ConfigEntry<int> MaxListeners;

		public static ConfigEntry<int> ListenLatencyMs;

		public static ConfigEntry<bool> VerboseLogging;

		public static ConfigEntry<bool> DumpUi;

		public static ConfigEntry<KeyCode> DumpUiKey;

		public static ConfigEntry<bool> ProbeSeek;

		public static void Bind(ConfigFile cfg)
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ec: Expected O, but got Unknown
			//IL_0150: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Expected O, but got Unknown
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Expected O, but got Unknown
			//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ca: Expected O, but got Unknown
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Expected O, but got Unknown
			//IL_0230: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Expected O, but got Unknown
			//IL_0268: Unknown result type (might be due to invalid IL or missing references)
			//IL_0272: Expected O, but got Unknown
			//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Expected O, but got Unknown
			//IL_02f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Expected O, but got Unknown
			//IL_032b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0335: Expected O, but got Unknown
			//IL_0363: Unknown result type (might be due to invalid IL or missing references)
			//IL_036d: Expected O, but got Unknown
			//IL_039b: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a5: Expected O, but got Unknown
			//IL_03d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dd: Expected O, but got Unknown
			//IL_0426: Unknown result type (might be due to invalid IL or missing references)
			//IL_0430: Expected O, but got Unknown
			//IL_045e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0468: Expected O, but got Unknown
			//IL_04ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f9: Expected O, but got Unknown
			//IL_0556: Unknown result type (might be due to invalid IL or missing references)
			//IL_0560: Expected O, but got Unknown
			//IL_058a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0594: Expected O, but got Unknown
			PlaylistEnabled = cfg.Bind<bool>("Playlist", "Enabled", true, "Master switch for the local music mixtape.");
			MusicFolder = cfg.Bind<string>("Playlist", "MusicFolder", "", "Folder scanned for music. Leave empty to disable.\nExample: D:" + Sep + "Music" + Sep + "Focus\nSupported: .mp3 .ogg .wav .aif .aiff\nNetwork paths are not supported by Unity's audio loader - map a drive letter instead.\nThe Folder... button on the mini music player sets this without editing the file.");
			MixtapeName = cfg.Bind<string>("Playlist", "MixtapeName", "Local", "Name shown on the mixtape in the in-game music player.");
			IncludeSubfolders = cfg.Bind<bool>("Playlist", "IncludeSubfolders", true, "Scan subfolders of MusicFolder as well.");
			AllowTrackerFormats = cfg.Bind<bool>("Playlist", "AllowTrackerFormats", false, "Also accept tracker modules (.it .mod .s3m .xm). Off by default: they are rare and\nthe decoder behaviour on Windows standalone is not verified.");
			MaxTracks = cfg.Bind<int>("Playlist", "MaxTracks", 300, new ConfigDescription("Safety cap on how many files are added to the mixtape.\nThe game builds one clickable button per track when the list is shown, so very\nlarge numbers cost a visible hitch the first time the music panel opens.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 5000), Array.Empty<object>()));
			SeekBarEnabled = cfg.Bind<bool>("UI", "SeekBarEnabled", true, "Show a scrub bar on the mini music player, so you can jump to any point in a track.");
			SeekBarOnVanillaMixtapes = cfg.Bind<bool>("UI", "SeekBarOnVanillaMixtapes", true, "Also show the scrub bar while one of the game's own mixtapes is selected.");
			PanelGrow = cfg.Bind<float>("UI", "PanelGrow", 44f, new ConfigDescription("Extra height added to the mini music player so the scrub row has its own space instead\nof being crammed against the bottom edge. The top edge stays put; the panel grows\ndownwards, and the hover box that keeps the panel open is moved to match.\n0 leaves the panel exactly as the game draws it - then move the bar with SeekBarY.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 160f), Array.Empty<object>()));
			SeekBarX = cfg.Bind<float>("UI", "SeekBarX", -14f, new ConfigDescription("Centre of the scrub bar, horizontally. The default lines its left edge up with the\nvolume slider above it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-500f, 500f), Array.Empty<object>()));
			SeekBarY = cfg.Bind<float>("UI", "SeekBarY", -112f, new ConfigDescription("Height of the scrub bar. The panel's visible bottom is at -118 unmodified, or\n-118 minus PanelGrow with the default growth.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-280f, 140f), Array.Empty<object>()));
			SeekBarWidth = cfg.Bind<float>("UI", "SeekBarWidth", 628f, new ConfigDescription("Width of the scrub bar in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(100f, 900f), Array.Empty<object>()));
			SeekTimeX = cfg.Bind<float>("UI", "SeekTimeX", 440f, new ConfigDescription("RIGHT edge of the time readout - the label is right-aligned, like the artist name it\nis cloned from. The panel's visible right edge is at 448.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-500f, 500f), Array.Empty<object>()));
			SeekTimeFontScale = cfg.Bind<float>("UI", "SeekTimeFontScale", 0.72f, new ConfigDescription("Size of the time readout relative to the artist name.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1.5f), Array.Empty<object>()));
			ModPanelEnabled = cfg.Bind<bool>("UI", "ModPanelEnabled", true, "Add a row under the scrub bar with the current music folder, a folder picker and a\nReload button. Turning it off also removes the extra panel height it needs.");
			ModPanelHeight = cfg.Bind<float>("UI", "ModPanelHeight", 46f, new ConfigDescription("How far below the scrub bar that row sits, and how much extra panel height it takes.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(24f, 120f), Array.Empty<object>()));
			ModRowTextRight = cfg.Bind<float>("UI", "ModRowTextRight", -106f, new ConfigDescription("RIGHT edge of the status label, which is left-aligned inside ModRowTextWidth.\nIt has to clear the leftmost button; with four buttons that is around -106.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-500f, 500f), Array.Empty<object>()));
			ModRowTextWidth = cfg.Bind<float>("UI", "ModRowTextWidth", 320f, new ConfigDescription("Width of the status label's box.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(100f, 700f), Array.Empty<object>()));
			ModRowButtonRight = cfg.Bind<float>("UI", "ModRowButtonRight", 430f, new ConfigDescription("RIGHT edge of the Reload button; the folder button sits just left of it.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(-500f, 500f), Array.Empty<object>()));
			ModRowButtonWidth = cfg.Bind<float>("UI", "ModRowButtonWidth", 124f, new ConfigDescription("Width of each button on the row. Four of them plus the status label have to fit\ninside the panel's 896 px, so there is not much room to grow.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(60f, 320f), Array.Empty<object>()));
			ModRowButtonGap = cfg.Bind<float>("UI", "ModRowButtonGap", 10f, new ConfigDescription("Space between the buttons.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 60f), Array.Empty<object>()));
			PickFolderByFile = cfg.Bind<bool>("UI", "PickFolderByFile", false, "Choose the folder by picking any track inside it, instead of selecting the folder itself.\nWindows' folder mode hides files, so a music folder with no subfolders looks empty - if\nthat bothers you more than picking a file does, set this to true.");
			ModRowHeight = cfg.Bind<float>("UI", "ModRowHeight", 38f, new ConfigDescription("Height of the folder and reload buttons.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 90f), Array.Empty<object>()));
			ModRowFontScale = cfg.Bind<float>("UI", "ModRowFontScale", 0.55f, new ConfigDescription("Button caption size relative to the artist name.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.3f, 1.5f), Array.Empty<object>()));
			PickFolderLabel = cfg.Bind<string>("UI", "PickFolderLabel", "Folder...", "Caption of the button that opens the Windows folder dialog.");
			ReloadLabel = cfg.Bind<string>("UI", "ReloadLabel", "Reload", "Caption of the rescan button.");
			WatchFolder = cfg.Bind<bool>("Playlist", "WatchFolder", true, "Notice files added to or removed from the music folder while the game is running.");
			WatchDebounceMs = cfg.Bind<int>("Playlist", "WatchDebounceMs", 1500, new ConfigDescription("How long the folder must be quiet before a change triggers a rescan. This is also what\nkeeps a half-copied file from being read: Windows reports a file as created before its\nbytes have finished arriving. Raise it if you copy over a slow drive.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(250, 10000), Array.Empty<object>()));
			SharingEnabled = cfg.Bind<bool>("Sharing", "Enabled", true, "Allow listening to, and broadcasting to, other players who have this mod.\nNothing is sent to anyone until you press Share, and nothing is received until you\npress Listen. Players without the mod are never contacted at all.");
			LiveTapeName = cfg.Bind<string>("Sharing", "LiveTapeName", "Shared", "Name of the extra mixtape used for listening to someone else.");
			MaxListeners = cfg.Bind<int>("Sharing", "MaxListeners", 6, new ConfigDescription("How many people may listen to you at once. Each one costs roughly your own\nbitrate in upload while their next track transfers.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 16), Array.Empty<object>()));
			ListenLatencyMs = cfg.Bind<int>("Sharing", "ListenLatencyMs", 250, new ConfigDescription("How far behind the broadcaster to sit. Some slack absorbs network jitter; without\nit the listener would keep trying to play audio the broadcaster has not sent yet.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 2000), Array.Empty<object>()));
			VerboseLogging = cfg.Bind<bool>("Diagnostics", "VerboseLogging", false, "Log every scanned file, clip load, eviction and stall repair. Noisy; for bug reports.");
			DumpUi = cfg.Bind<bool>("Diagnostics", "DumpUi", false, "Dump the music-player UI tree to the log when it is built. Attach the log to a bug\nreport if a widget lands in the wrong place.");
			DumpUiKey = cfg.Bind<KeyCode>("Diagnostics", "DumpUiKey", (KeyCode)284, "Key that dumps that tree on demand. F4-F6 and F10-F11 are taken by Day and Night,\nF7-F9 by FrameCare and BlueSage, so F1-F3 are the free ones.");
			ProbeSeek = cfg.Bind<bool>("Diagnostics", "ProbeSeek", true, "Log the cost and read-back accuracy of the first seek. Cheap, and it is what turns\n'seeking does not work' into a report someone can act on.");
			MigrateLayout(cfg);
		}

		private static void MigrateLayout(ConfigFile cfg)
		{
			LayoutVersion = cfg.Bind<int>("UI", "LayoutVersion", 0, "Internal. Bumped when the mod ships new default widget positions, which resets the\nposition values above to those defaults. Set it to 0 by hand to force that reset.");
			if (LayoutVersion.Value < 7)
			{
				ResetToDefault<float>(PanelGrow);
				ResetToDefault<float>(SeekBarX);
				ResetToDefault<float>(SeekBarY);
				ResetToDefault<float>(SeekBarWidth);
				ResetToDefault<float>(SeekTimeX);
				ResetToDefault<float>(SeekTimeFontScale);
				ResetToDefault<float>(ModPanelHeight);
				ResetToDefault<float>(ModRowTextRight);
				ResetToDefault<float>(ModRowButtonRight);
				ResetToDefault<float>(ModRowButtonWidth);
				ResetToDefault<float>(ModRowHeight);
				ResetToDefault<float>(ModRowFontScale);
				ResetToDefault<float>(ModRowButtonGap);
				ResetToDefault<float>(ModRowTextWidth);
				Plugin.Log.LogInfo((object)("Applied the 0.3.1 music player layout (previous version: " + LayoutVersion.Value + "). Your own tweaks to those positions were reset."));
				LayoutVersion.Value = 7;
			}
		}

		private static void ResetToDefault<T>(ConfigEntry<T> entry)
		{
			if (entry != null)
			{
				entry.Value = (T)((ConfigEntryBase)entry).DefaultValue;
			}
		}
	}
	public sealed class ModRoot : MonoBehaviour
	{
		public static ModRoot Instance { get; private set; }

		public static void Create()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (!((Object)(object)Instance != (Object)null))
			{
				GameObject val = new GameObject("OTLP_ModRoot")
				{
					hideFlags = (HideFlags)61
				};
				Object.DontDestroyOnLoad((Object)val);
				Instance = val.AddComponent<ModRoot>();
			}
		}

		public static Coroutine Run(IEnumerator routine)
		{
			if ((Object)(object)Instance == (Object)null || routine == null)
			{
				return null;
			}
			return ((MonoBehaviour)Instance).StartCoroutine(routine);
		}

		public static void Stop(Coroutine routine)
		{
			if ((Object)(object)Instance != (Object)null && routine != null)
			{
				((MonoBehaviour)Instance).StopCoroutine(routine);
			}
		}

		private void Update()
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			MainThread.Drain();
			try
			{
				MixtapeBridge.Tick();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("MixtapeBridge.Tick threw, disabling: " + ex));
				MixtapeBridge.Disable();
			}
			try
			{
				ShareController.Tick();
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError((object)("ShareController.Tick threw, sharing stopped: " + ex2));
				ShareController.Shutdown();
			}
			try
			{
				FolderWatcher.Tick();
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogError((object)("FolderWatcher.Tick threw, watching stopped: " + ex3));
				FolderWatcher.Disarm();
			}
			if (ModConfig.DumpUiKey != null && Input.GetKeyDown(ModConfig.DumpUiKey.Value))
			{
				UiBuilder.DumpNow();
			}
		}
	}
	[BepInPlugin("com.sleepeydev.ontogether.localplaylist", "On-Together Local Playlist", "0.6.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.sleepeydev.ontogether.localplaylist";

		public static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				ModConfig.Bind(((BaseUnityPlugin)this).Config);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Config binding failed - some features will switch themselves off: " + ex));
			}
			ModRoot.Create();
			HarmonyBootstrap.ApplyAll();
			ShareController.Init();
			if (ModConfig.MusicFolder != null)
			{
				ModConfig.MusicFolder.SettingChanged += delegate
				{
					MainThread.Post(delegate
					{
						FolderWatcher.Sync();
						ReloadCoordinator.Request(ReloadReason.FolderChanged);
					});
				};
			}
			Log.LogInfo((object)("Local Playlist " + ((BaseUnityPlugin)this).Info.Metadata.Version?.ToString() + " ready."));
			Refl.LogHealth();
		}
	}
	public static class Refl
	{
		private static readonly List<string> Missing = new List<string>();

		public static readonly FieldRef<SoundManager, AudioSource> MixtapeMusic = Field<SoundManager, AudioSource>("_mixtapeMusic");

		public static readonly FieldRef<SoundManager, int> SongIndex = Field<SoundManager, int>("_songIndex");

		public static readonly FieldRef<SoundManager, int> MixtapeIndex = Field<SoundManager, int>("_mixtapeIndex");

		public static readonly FieldRef<SoundManager, bool> IsPlayingFlag = Field<SoundManager, bool>("_isPlaying");

		public static readonly MethodInfo PlayMixtape = Method(typeof(SoundManager), "PlayMixtape");

		public static readonly FieldRef<UIManager, Slider> MixTapeVolumeSlider = Field<UIManager, Slider>("_mixTapeVolumeSlider");

		public static readonly FieldRef<UIManager, TMP_Text> ArtistNameLabel = Field<UIManager, TMP_Text>("_artistName");

		public static readonly FieldRef<UIManager, Button> SongButtonPrefab = Field<UIManager, Button>("_songButtonPrefab");

		public static readonly FieldRef<UIManager, float> MusicTimer = Field<UIManager, float>("_musicTimer");

		public static readonly FieldRef<UIManager, Transform> MusicCorner1 = Field<UIManager, Transform>("_musicCorner1");

		public static bool PlaylistReady
		{
			get
			{
				if (MixtapeMusic != null && SongIndex != null && MixtapeIndex != null && IsPlayingFlag != null)
				{
					return PlayMixtape != null;
				}
				return false;
			}
		}

		public static bool SeekUiReady
		{
			get
			{
				if (PlaylistReady && MixTapeVolumeSlider != null)
				{
					return ArtistNameLabel != null;
				}
				return false;
			}
		}

		public static bool ModPanelReady
		{
			get
			{
				if (PlaylistReady && MixTapeVolumeSlider != null && ArtistNameLabel != null)
				{
					return SongButtonPrefab != null;
				}
				return false;
			}
		}

		private static FieldRef<TOwner, TField> Field<TOwner, TField>(string name)
		{
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(TOwner), name);
				if (fieldInfo == null || !typeof(TField).IsAssignableFrom(fieldInfo.FieldType))
				{
					Missing.Add(typeof(TOwner).Name + "." + name);
					return null;
				}
				return AccessTools.FieldRefAccess<TOwner, TField>(name);
			}
			catch (Exception)
			{
				Missing.Add(typeof(TOwner).Name + "." + name);
				return null;
			}
		}

		private static MethodInfo Method(Type owner, string name)
		{
			MethodInfo methodInfo = AccessTools.Method(owner, name, (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Missing.Add(owner.Name + "." + name + "()");
			}
			return methodInfo;
		}

		public static void LogHealth()
		{
			if (Missing.Count == 0)
			{
				Plugin.Log.LogInfo((object)"Game handles: all resolved.");
				return;
			}
			Plugin.Log.LogWarning((object)("Game handles missing (the game was probably updated): " + string.Join(", ", Missing.ToArray())));
			Plugin.Log.LogWarning((object)("Feature availability - local playlist: " + (PlaylistReady ? "on" : "OFF") + ", seek bar: " + (SeekUiReady ? "on" : "OFF") + ", folder row: " + (ModPanelReady ? "on" : "OFF")));
		}
	}
}
namespace SleepeyDev.OnTogetherLocalPlaylist.UI
{
	public sealed class DragWatcher : MonoBehaviour, IPointerDownHandler, IEventSystemHandler, IPointerUpHandler
	{
		public bool IsDragging { get; private set; }

		public event Action Down;

		public event Action Up;

		public static DragWatcher Attach(GameObject go)
		{
			if ((Object)(object)go == (Object)null)
			{
				return null;
			}
			DragWatcher component = go.GetComponent<DragWatcher>();
			if (!((Object)(object)component != (Object)null))
			{
				return go.AddComponent<DragWatcher>();
			}
			return component;
		}

		public void OnPointerDown(PointerEventData eventData)
		{
			if (!IsDragging)
			{
				IsDragging = true;
				Fire(this.Down);
			}
		}

		public void OnPointerUp(PointerEventData eventData)
		{
			Release();
		}

		private void Update()
		{
			if (IsDragging && !Input.GetMouseButton(0))
			{
				Release();
			}
		}

		private void OnDisable()
		{
			if (IsDragging)
			{
				Release();
			}
		}

		private void Release()
		{
			if (IsDragging)
			{
				IsDragging = false;
				Fire(this.Up);
			}
		}

		private void Fire(Action a)
		{
			if (a == null)
			{
				return;
			}
			try
			{
				a();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("DragWatcher handler threw: " + ex));
			}
		}
	}
	public static class GameUi
	{
		public const string Prefix = "OTLP_";

		public static Transform Existing(Transform parent, string name)
		{
			if (!((Object)(object)parent == (Object)null))
			{
				return parent.Find(name);
			}
			return null;
		}

		public static Slider CloneSlider(Slider donor, Transform parent, string name)
		{
			if ((Object)(object)donor == (Object)null || (Object)(object)parent == (Object)null)
			{
				return null;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)donor).gameObject, parent, false);
			((Object)val).name = name;
			Slider component = val.GetComponent<Slider>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				return null;
			}
			ClearListeners<float>((UnityEvent<float>)(object)component.onValueChanged);
			StripLocalizers(val);
			val.SetActive(true);
			return component;
		}

		public static GameObject CloneObject(GameObject donor, Transform parent, string name)
		{
			if ((Object)(object)donor == (Object)null || (Object)(object)parent == (Object)null)
			{
				return null;
			}
			GameObject obj = Object.Instantiate<GameObject>(donor, parent, false);
			((Object)obj).name = name;
			StripLocalizers(obj);
			obj.SetActive(true);
			return obj;
		}

		public static TMP_Text CloneLabel(TMP_Text donor, Transform parent, string name, string text)
		{
			if ((Object)(object)donor == (Object)null || (Object)(object)parent == (Object)null)
			{
				return null;
			}
			GameObject val = Object.Instantiate<GameObject>(((Component)donor).gameObject, parent, false);
			((Object)val).name = name;
			TMP_Text component = val.GetComponent<TMP_Text>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val);
				return null;
			}
			StripLocalizers(val);
			StaticText.Apply(component, text);
			val.SetActive(true);
			return component;
		}

		public static void ClearListeners<T>(UnityEvent<T> evt)
		{
			if (evt != null)
			{
				for (int num = ((UnityEventBase)evt).GetPersistentEventCount() - 1; num >= 0; num--)
				{
					((UnityEventBase)evt).SetPersistentListenerState(num, (UnityEventCallState)0);
				}
				((UnityEventBase)evt).RemoveAllListeners();
			}
		}

		public static void ClearListeners(UnityEvent evt)
		{
			if (evt != null)
			{
				for (int num = ((UnityEventBase)evt).GetPersistentEventCount() - 1; num >= 0; num--)
				{
					((UnityEventBase)evt).SetPersistentListenerState(num, (UnityEventCallState)0);
				}
				((UnityEventBase)evt).RemoveAllListeners();
			}
		}

		public static int StripScripts(GameObject root)
		{
			if ((Object)(object)root == (Object)null)
			{
				return 0;
			}
			Assembly assembly = typeof(SoundManager).Assembly;
			int num = 0;
			Component[] componentsInChildren = root.GetComponentsInChildren<Component>(true);
			foreach (Component val in componentsInChildren)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Type type = ((object)val).GetType();
					bool flag = type.Name.IndexOf("Localize", StringComparison.Ordinal) >= 0;
					bool flag2 = type.Assembly == assembly;
					if (flag || flag2)
					{
						Object.Destroy((Object)(object)val);
						num++;
					}
				}
			}
			return num;
		}

		public static int StripLocalizers(GameObject root)
		{
			return StripScripts(root);
		}

		public static void Place(RectTransform rt, Vector2 anchoredPos, Vector2 sizeDelta)
		{
			//IL_0031: 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_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)rt == (Object)null)
			{
				return;
			}
			LayoutGroup val = (((Object)(object)((Transform)rt).parent == (Object)null) ? null : ((Component)((Transform)rt).parent).GetComponent<LayoutGroup>());
			if ((Object)(object)val == (Object)null)
			{
				rt.anchoredPosition = anchoredPos;
				rt.sizeDelta = sizeDelta;
				return;
			}
			HorizontalOrVerticalLayoutGroup val2 = (HorizontalOrVerticalLayoutGroup)(object)((val is HorizontalOrVerticalLayoutGroup) ? val : null);
			if ((Object)(object)val2 != (Object)null && val2.childControlHeight)
			{
				LayoutElement val3 = ((Component)rt).GetComponent<LayoutElement>();
				if ((Object)(object)val3 == (Object)null)
				{
					val3 = ((Component)rt).gameObject.AddComponent<LayoutElement>();
				}
				val3.preferredHeight = sizeDelta.y;
			}
			else
			{
				rt.sizeDelta = new Vector2(rt.sizeDelta.x, sizeDelta.y);
			}
		}

		public static LayoutGroup DrivingLayout(Transform t)
		{
			if (!((Object)(object)t == (Object)null) && !((Object)(object)t.parent == (Object)null))
			{
				return ((Component)t.parent).GetComponent<LayoutGroup>();
			}
			return null;
		}

		public static void DropFocus()
		{
			if ((Object)(object)EventSystem.current != (Object)null)
			{
				EventSystem.current.SetSelectedGameObject((GameObject)null);
			}
		}

		public static void DumpHierarchy(Transform root, string title, int maxDepth = 6)
		{
			if ((Object)(object)root == (Object)null)
			{
				Plugin.Log.LogInfo((object)("[dump] " + title + ": <null>"));
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("[dump] ").Append(title).Append('\n');
			Describe(root, stringBuilder, 0, maxDepth);
			Plugin.Log.LogInfo((object)stringBuilder.ToString());
		}

		private static void Describe(Transform t, StringBuilder sb, int depth, int maxDepth)
		{
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_016d: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)t == (Object)null || depth > maxDepth)
			{
				return;
			}
			sb.Append(' ', depth * 2).Append(((Object)t).name);
			sb.Append("  [").Append(((Component)t).gameObject.activeSelf ? "on" : "off").Append(", sib ")
				.Append(t.GetSiblingIndex())
				.Append(']');
			Component[] components = ((Component)t).GetComponents<Component>();
			sb.Append("  {");
			for (int i = 0; i < components.Length; i++)
			{
				if (i > 0)
				{
					sb.Append(", ");
				}
				sb.Append(((Object)(object)components[i] == (Object)null) ? "<missing>" : ((object)components[i]).GetType().Name);
			}
			sb.Append('}');
			RectTransform val = (RectTransform)(object)((t is RectTransform) ? t : null);
			if (val != null)
			{
				sb.Append("  aMin").Append(V(val.anchorMin)).Append(" aMax")
					.Append(V(val.anchorMax))
					.Append(" piv")
					.Append(V(val.pivot))
					.Append(" pos")
					.Append(V(val.anchoredPosition))
					.Append(" size")
					.Append(V(val.sizeDelta))
					.Append(" scale")
					.Append(V(((Transform)val).localScale));
			}
			LayoutGroup val2 = (((Object)(object)t.parent == (Object)null) ? null : ((Component)t.parent).GetComponent<LayoutGroup>());
			if ((Object)(object)val2 != (Object)null)
			{
				sb.Append("  [LAYOUT-DRIVEN by ").Append(((object)val2).GetType().Name).Append(']');
			}
			LayoutGroup component = ((Component)t).GetComponent<LayoutGroup>();
			if ((Object)(object)component != (Object)null)
			{
				sb.Append("  ").Append(DescribeLayout(component));
			}
			Selectable component2 = ((Component)t).GetComponent<Selectable>();
			Slider val3 = (Slider)(object)((component2 is Slider) ? component2 : null);
			if (val3 != null)
			{
				sb.Append("  slider(persistent=").Append(((UnityEventBase)val3.onValueChanged).GetPersistentEventCount()).Append(')');
			}
			else
			{
				Button val4 = (Button)(object)((component2 is Button) ? component2 : null);
				if (val4 != null)
				{
					sb.Append("  button(persistent=").Append(((UnityEventBase)val4.onClick).GetPersistentEventCount()).Append(')');
				}
			}
			TMP_Text component3 = ((Component)t).GetComponent<TMP_Text>();
			if ((Object)(object)component3 != (Object)null)
			{
				string text = component3.text ?? "";
				if (text.Length > 40)
				{
					text = text.Substring(0, 40) + "...";
				}
				sb.Append("  text=\"").Append(text).Append('"');
			}
			sb.Append('\n');
			for (int j = 0; j < t.childCount; j++)
			{
				Describe(t.GetChild(j), sb, depth + 1, maxDepth);
			}
		}

		public static string DescribeLayout(LayoutGroup g)
		{
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)g == (Object)null)
			{
				return "";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(((object)g).GetType().Name).Append("(pad ").Append(g.padding.left)
				.Append(',')
				.Append(g.padding.right)
				.Append(',')
				.Append(g.padding.top)
				.Append(',')
				.Append(g.padding.bottom)
				.Append(" align=")
				.Append(g.childAlignment);
			HorizontalOrVerticalLayoutGroup val = (HorizontalOrVerticalLayoutGroup)(object)((g is HorizontalOrVerticalLayoutGroup) ? g : null);
			if (val != null)
			{
				stringBuilder.Append(" spacing=").Append(val.spacing).Append(" ctrlW=")
					.Append(val.childControlWidth)
					.Append(" ctrlH=")
					.Append(val.childControlHeight)
					.Append(" expW=")
					.Append(val.childForceExpandWidth)
					.Append(" expH=")
					.Append(val.childForceExpandHeight)
					.Append(" reverse=")
					.Append(val.reverseArrangement);
			}
			return stringBuilder.Append(')').ToString();
		}

		private static string V(Vector2 v)
		{
			return "(" + v.x.ToString("0.##") + "," + v.y.ToString("0.##") + ")";
		}

		private static string V(Vector3 v)
		{
			return "(" + v.x.ToString("0.##") + "," + v.y.ToString("0.##") + ")";
		}
	}
	public static class MiniPlayer
	{
		public static float TotalGrow => Mathf.Max(0f, ModConfig.PanelGrow.Value) + (ModConfig.ModPanelEnabled.Value ? Mathf.Max(0f, ModConfig.ModPanelHeight.Value) : 0f);

		public static Transform Resolve(UIManager ui)
		{
			if ((Object)(object)ui == (Object)null || Refl.MixTapeVolumeSlider == null)
			{
				return null;
			}
			Slider val = Refl.MixTapeVolumeSlider.Invoke(ui);
			if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).transform.parent == (Object)null)
			{
				return null;
			}
			return ((Component)val).transform.parent.parent;
		}

		public static void Grow(UIManager ui, Transform panel, float grow)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)panel == (Object)null || grow <= 0f)
			{
				return;
			}
			int num = 0;
			string[] array = new string[2] { "Image_Bg", "Image_Outline" };
			foreach (string text in array)
			{
				Transform obj = panel.Find(text);
				RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null);
				if (val != null)
				{
					val.sizeDelta = new Vector2(val.sizeDelta.x, val.sizeDelta.y + grow);
					val.anchoredPosition = new Vector2(val.anchoredPosition.x, val.anchoredPosition.y - grow * 0.5f);
					num++;
				}
			}
			if (num == 0)
			{
				Plugin.Log.LogWarning((object)"Could not find the mini player's background to enlarge; set UI/PanelGrow = 0 if the layout looks wrong.");
				return;
			}
			if (Refl.MusicCorner1 != null)
			{
				Transform obj2 = Refl.MusicCorner1.Invoke(ui);
				RectTransform val2 = (RectTransform)(object)((obj2 is RectTransform) ? obj2 : null);
				if (val2 != null)
				{
					val2.anchoredPosition = new Vector2(val2.anchoredPosition.x, val2.anchoredPosition.y - grow);
				}
				else
				{
					Plugin.Log.LogWarning((object)"The music hover-box corner is not a RectTransform; the panel may close when you reach for the added controls.");
				}
			}
			Plugin.Log.LogInfo((object)("Mini player grown by " + grow + " px (" + num + " layer(s))."));
		}

		public static void KeepOpen(UIManager ui)
		{
			if ((Object)(object)ui != (Object)null && Refl.MusicTimer != null)
			{
				Refl.MusicTimer.Invoke(ui) = 0f;
			}
		}
	}
	public sealed class ModPanel : MonoBehaviour
	{
		private const string PathName = "OTLP_FolderPath";

		private const string PickName = "OTLP_PickFolder";

		private const string ReloadName = "OTLP_Reload";

		private const string ShareName = "OTLP_Share";

		private const string ListenName = "OTLP_Listen";

		private static bool _failed;

		private UIManager _ui;

		private TMP_Text _pathLabel;

		private string _shown;

		private Button _shareButton;

		private Button _listenButton;

		private TMP_Text _shareCaption;

		private TMP_Text _listenCaption;

		private string _shownShareCaption;

		private string _shownListenCaption;

		public static void Build(UIManager ui, Transform panel)
		{
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			if (_failed || !ModConfig.ModPanelEnabled.Value)
			{
				return;
			}
			if (!Refl.ModPanelReady)
			{
				Plugin.Log.LogWarning((object)"Folder row: game handles missing, skipping. The folder is still settable in the config file.");
				_failed = true;
			}
			else
			{
				if ((Object)(object)panel == (Object)null || (Object)(object)GameUi.Existing(panel, "OTLP_FolderPath") != (Object)null)
				{
					return;
				}
				float num = ModConfig.SeekBarY.Value - ModConfig.ModPanelHeight.Value;
				TMP_Text val = Refl.ArtistNameLabel.Invoke(ui);
				TMP_Text val2 = GameUi.CloneLabel(val, panel, "OTLP_FolderPath", "");
				if ((Object)(object)val2 == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Folder row: could not clone a label.");
					return;
				}
				RectTransform val3 = (RectTransform)val2.transform;
				val3.sizeDelta = new Vector2(ModConfig.ModRowTextWidth.Value, 34f);
				val3.anchoredPosition = new Vector2(ModConfig.ModRowTextRight.Value, num);
				val2.alignment = (TextAlignmentOptions)513;
				val2.fontSize = Mathf.Max(10f, val.fontSize * ModConfig.SeekTimeFontScale.Value);
				((Graphic)val2).raycastTarget = false;
				ModPanel modPanel = ((Component)val2).gameObject.AddComponent<ModPanel>();
				modPanel._ui = ui;
				modPanel._pathLabel = val2;
				float value = ModConfig.ModRowButtonWidth.Value;
				float value2 = ModConfig.ModRowButtonGap.Value;
				float value3 = ModConfig.ModRowButtonRight.Value;
				Button val4 = MakeButton(ui, panel, "OTLP_Reload", ModConfig.ReloadLabel.Value, value3, num, OnReload);
				value3 -= value + value2;
				Button val5 = MakeButton(ui, panel, "OTLP_PickFolder", ModConfig.PickFolderLabel.Value, value3, num, OnPickFolder);
				value3 -= value + value2;
				if (ModConfig.SharingEnabled.Value)
				{
					modPanel._listenButton = MakeButton(ui, panel, "OTLP_Listen", "Listen", value3, num, ShareController.ToggleListen);
					value3 -= value + value2;
					modPanel._shareButton = MakeButton(ui, panel, "OTLP_Share", "Share", value3, num, ShareController.ToggleBroadcast);
					modPanel._listenCaption = CaptionOf(modPanel._listenButton);
					modPanel._shareCaption = CaptionOf(modPanel._shareButton);
				}
				Plugin.Log.LogInfo((object)("Mod row added at y " + num + " (" + (((Object)(object)val5 != (Object)null) ? "folder " : "") + (((Object)(object)val4 != (Object)null) ? "reload " : "") + (((Object)(object)modPanel._shareButton != (Object)null) ? "share listen" : "") + ")."));
			}
		}

		private static TMP_Text CaptionOf(Button b)
		{
			if (!((Object)(object)b == (Object)null))
			{
				return ((Component)b).GetComponentInChildren<TMP_Text>(true);
			}
			return null;
		}

		private static Button MakeButton(UIManager ui, Transform panel, string name, string caption, float right, float y, Action onClick)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: 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_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Expected O, but got Unknown
			Button val = FindButtonDonor(ui);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			GameObject val2 = GameUi.CloneObject(((Component)val).gameObject, panel, name);
			if ((Object)(object)val2 == (Object)null)
			{
				return null;
			}
			Button component = val2.GetComponent<Button>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				return null;
			}
			GameUi.ClearListeners((UnityEvent)(object)component.onClick);
			((UnityEvent)component.onClick).AddListener((UnityAction)delegate
			{
				try
				{
					onClick();
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)(name + " click failed: " + ex));
				}
				GameUi.DropFocus();
			});
			RectTransform val3 = (RectTransform)val2.transform;
			val3.anchorMin = new Vector2(0.5f, 0.5f);
			val3.anchorMax = new Vector2(0.5f, 0.5f);
			val3.pivot = new Vector2(1f, 0.5f);
			((Transform)val3).localScale = Vector3.one;
			val3.sizeDelta = new Vector2(ModConfig.ModRowButtonWidth.Value, ModConfig.ModRowHeight.Value);
			val3.anchoredPosition = new Vector2(right, y);
			NormaliseChildren(val3);
			TMP_Text componentInChildren = val2.GetComponentInChildren<TMP_Text>(true);
			if ((Object)(object)componentInChildren != (Object)null)
			{
				StaticText.Apply(componentInChildren, caption);
				componentInChildren.alignment = (TextAlignmentOptions)514;
				componentInChildren.textWrappingMode = (TextWrappingModes)0;
				componentInChildren.overflowMode = (TextOverflowModes)0;
				TMP_Text val4 = Refl.ArtistNameLabel.Invoke(ui);
				if ((Object)(object)val4 != (Object)null)
				{
					componentInChildren.fontSize = Mathf.Max(10f, val4.fontSize * ModConfig.ModRowFontScale.Value);
				}
			}
			return component;
		}

		private static Button FindButtonDonor(UIManager ui)
		{
			string[] array = new string[2] { "GameUI/Panel_Request/Button_Reject", "GameUI/Panel_Request/Button_Accept" };
			foreach (string text in array)
			{
				Transform val = ((Component)ui).transform.Find(text);
				Button val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<Button>() : null);
				if ((Object)(object)val2 != (Object)null)
				{
					return val2;
				}
			}
			Transform val3 = MiniPlayer.Resolve(ui);
			if ((Object)(object)val3 != (Object)null)
			{
				Transform val4 = val3.Find("ButtonMiniPlayerTabs/Button_Music");
				Button val5 = (((Object)(object)val4 != (Object)null) ? ((Component)val4).GetComponent<Button>() : null);
				if ((Object)(object)val5 != (Object)null)
				{
					Plugin.Log.LogInfo((object)"Folder row: using the mini player tab as the button donor.");
					return val5;
				}
			}
			Plugin.Log.LogWarning((object)"Folder row: falling back to the song-row prefab as a button donor; its decoration does not scale, so the buttons may look wrong.");
			if (Refl.SongButtonPrefab == null)
			{
				return null;
			}
			return Refl.SongButtonPrefab.Invoke(ui);
		}

		private static void NormaliseChildren(RectTransform root)
		{
			//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_0028: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < ((Transform)root).childCount; i++)
			{
				Transform child = ((Transform)root).GetChild(i);
				RectTransform val = (RectTransform)(object)((child is RectTransform) ? child : null);
				if (val != null && !(val.anchorMin != val.anchorMax))
				{
					val.anchorMin = Vector2.zero;
					val.anchorMax = Vector2.one;
					val.pivot = new Vector2(0.5f, 0.5f);
					val.offsetMin = Vector2.zero;
					val.offsetMax = Vector2.zero;
				}
			}
		}

		private static void OnPickFolder()
		{
			string start = (ModConfig.MusicFolder.Value ?? "").Trim().Trim('"');
			bool value = ModConfig.PickFolderByFile.Value;
			FolderPicker.Pick(value ? "Pick any track in your music folder" : "Open your music folder, then press Use this folder", start, value, delegate(string picked)
			{
				if (string.IsNullOrEmpty(picked))
				{
					Plugin.Log.LogInfo((object)"Folder picker: cancelled.");
				}
				else if (string.Equals(picked, start, StringComparison.OrdinalIgnoreCase))
				{
					Plugin.Log.LogInfo((object)"Folder picker: same folder, nothing to do.");
				}
				else
				{
					Plugin.Log.LogInfo((object)("Music folder set to " + picked));
					ModConfig.MusicFolder.Value = picked;
				}
			});
		}

		private static void OnReload()
		{
			ReloadCoordinator.Request(ReloadReason.Manual);
		}

		private void Update()
		{
			if (FolderPicker.IsOpen)
			{
				MiniPlayer.KeepOpen(_ui);
			}
			string text = (ReloadCoordinator.Busy ? "Scanning..." : Describe());
			string statusLine = ShareController.StatusLine;
			if (!string.IsNullOrEmpty(statusLine))
			{
				text = text + "   -   " + statusLine;
			}
			if (text != _shown)
			{
				_shown = text;
				StaticText.Apply(_pathLabel, text);
			}
			PumpShareButtons();
		}

		private void PumpShareButtons()
		{
			string shareButtonLabel = ShareController.ShareButtonLabel;
			if ((Object)(object)_shareCaption != (Object)null && shareButtonLabel != _shownShareCaption)
			{
				_shownShareCaption = shareButtonLabel;
				StaticText.Apply(_shareCaption, shareButtonLabel);
			}
			string listenButtonLabel = ShareController.ListenButtonLabel;
			if ((Object)(object)_listenCaption != (Object)null && listenButtonLabel != _shownListenCaption)
			{
				_shownListenCaption = listenButtonLabel;
				StaticText.Apply(_listenCaption, listenButtonLabel);
			}
			if ((Object)(object)_shareButton != (Object)null)
			{
				((Selectable)_shareButton).interactable = ShareController.CanBroadcast || ShareController.Mode == ShareMode.Broadcasting;
			}
			if ((Object)(object)_listenButton != (Object)null)
			{
				((Selectable)_listenButton).interactable = ShareController.AnyoneBroadcasting || ShareController.Mode == ShareMode.Listening;
			}
		}

		private static string Describe()
		{
			string text = (ModConfig.MusicFolder.Value ?? "").Trim().Trim('"');
			if (text.Length == 0)
			{
				return "No music folder chosen";
			}
			string text2;
			try
			{
				text2 = Path.GetFileName(text.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
			}
			catch (Exception)
			{
				text2 = text;
			}
			if (string.IsNullOrEmpty(text2))
			{
				text2 = text;
			}
			int count = PlaylistLibrary.Tracks.Count;
			string text3 = ((count == 1) ? "1 track" : (count + " tracks"));
			return text2 + "  -  " + text3;
		}

		public static void Reset()
		{
			_failed = false;
		}
	}
	public sealed class SeekBar : MonoBehaviour
	{
		private const string BarName = "OTLP_SeekBar";

		private const string LabelName = "OTLP_SeekTime";

		private static bool _failed;

		private Slider _slider;

		private TMP_Text _readout;

		private DragWatcher _watcher;

		private UIManager _ui;

		private int _shownSecond = -1;

		private int _shownTotal = -1;

		private bool _wasInteractable = true;

		private bool _probeLogged;

		private static float _lastSlowSeekLog = -999f;

		public static void Build(UIManager ui)
		{
			//IL_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_027a: Unknown result type (might be due to invalid IL or missing references)
			//IL_027f: Unknown result type (might be due to invalid IL or missing references)
			//IL_029a: Unknown result type (might be due to invalid IL or missing references)
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: 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)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
			if (_failed || !ModConfig.SeekBarEnabled.Value)
			{
				return;
			}
			if (!Refl.SeekUiReady)
			{
				Plugin.Log.LogWarning((object)"Seek bar: game handles missing, skipping.");
				_failed = true;
				return;
			}
			Slider val = Refl.MixTapeVolumeSlider.Invoke(ui);
			if ((Object)(object)val == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Seek bar: no mixtape volume slider on this UIManager.");
				return;
			}
			Transform val2 = (((Object)(object)((Component)val).transform.parent != (Object)null) ? ((Component)val).transform.parent.parent : null);
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)"Seek bar: the volume slider is not two levels inside the mini player.");
			}
			else
			{
				if ((Object)(object)GameUi.Existing(val2, "OTLP_SeekBar") != (Object)null)
				{
					return;
				}
				Slider val3 = GameUi.CloneSlider(val, val2, "OTLP_SeekBar");
				if ((Object)(object)val3 == (Object)null)
				{
					Plugin.Log.LogWarning((object)"Seek bar: cloning the volume slider failed.");
					return;
				}
				val3.minValue = 0f;
				val3.maxValue = 1f;
				val3.wholeNumbers = false;
				val3.SetValueWithoutNotify(0f);
				MakeTrackClickable(val3);
				RectTransform val4 = (RectTransform)((Component)val3).transform;
				val4.anchoredPosition = new Vector2(ModConfig.SeekBarX.Value, ModConfig.SeekBarY.Value);
				val4.sizeDelta = new Vector2(ModConfig.SeekBarWidth.Value, val4.sizeDelta.y);
				TMP_Text val5 = null;
				TMP_Text val6 = Refl.ArtistNameLabel.Invoke(ui);
				if ((Object)(object)val6 != (Object)null)
				{
					val5 = GameUi.CloneLabel(val6, val2, "OTLP_SeekTime", "--:-- / --:--");
					if ((Object)(object)val5 != (Object)null)
					{
						RectTransform val7 = (RectTransform)val5.transform;
						val7.anchoredPosition = new Vector2(ModConfig.SeekTimeX.Value, ModConfig.SeekBarY.Value);
						val7.sizeDelta = new Vector2(val7.sizeDelta.x, 34f);
						val5.alignment = (TextAlignmentOptions)516;
						val5.fontSize = Mathf.Max(10f, val6.fontSize * ModConfig.SeekTimeFontScale.Value);
						((Graphic)val5).raycastTarget = false;
					}
				}
				SeekBar seekBar = ((Component)val3).gameObject.AddComponent<SeekBar>();
				seekBar._slider = val3;
				seekBar._readout = val5;
				seekBar._ui = ui;
				seekBar._watcher = DragWatcher.Attach(((Component)val3).gameObject);
				seekBar._watcher.Up += seekBar.OnRelease;
				Plugin.Log.LogInfo((object)("Seek bar added to " + ((Object)val2).name + " at " + ((object)val4.anchoredPosition/*cast due to .constrained prefix*/).ToString() + " size " + ((object)val4.sizeDelta/*cast due to .constrained prefix*/).ToString()));
			}
		}

		private static void MakeTrackClickable(Slider bar)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			Image val = ((Component)bar).gameObject.GetComponent<Image>();
			if ((Object)(object)val == (Object)null)
			{
				val = ((Component)bar).gameObject.AddComponent<Image>();
			}
			((Graphic)val).color = new Color(0f, 0f, 0f, 0f);
			((Graphic)val).raycastTarget = true;
			if (ModConfig.VerboseLogging.Value)
			{
				Plugin.Log.LogInfo((object)"Seek bar: click-to-seek catcher attached.");
			}
		}

		private static void GrowPanel(UIManager ui, Transform panel)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			float value = ModConfig.PanelGrow.Value;
			if (value <= 0f)
			{
				return;
			}
			int num = 0;
			string[] array = new string[2] { "Image_Bg", "Image_Outline" };
			foreach (string text in array)
			{
				Transform obj = panel.Find(text);
				RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null);
				if (val != null)
				{
					val.sizeDelta = new Vector2(val.sizeDelta.x, val.sizeDelta.y + value);
					val.anchoredPosition = new Vector2(val.anchoredPosition.x, val.anchoredPosition.y - value * 0.5f);
					num++;
				}
			}
			if (num == 0)
			{
				Plugin.Log.LogWarning((object)"Seek bar: could not find the mini player's background to enlarge; set UI/PanelGrow = 0 if the layout looks wrong.");
				return;
			}
			if (Refl.MusicCorner1 != null)
			{
				Transform obj2 = Refl.MusicCorner1.Invoke(ui);
				RectTransform val2 = (RectTransform)(object)((obj2 is RectTransform) ? obj2 : null);
				if (val2 != null)
				{
					val2.anchoredPosition = new Vector2(val2.anchoredPosition.x, val2.anchoredPosition.y - value);
				}
				else
				{
					Plugin.Log.LogWarning((object)"Seek bar: the music hover-box corner is not a RectTransform; the panel may close when you reach for the scrub bar.");
				}
			}
			Plugin.Log.LogInfo((object)("Mini player grown by " + value + " px (" + num + " layer(s))."));
		}

		public static void SetInteractable(bool value)
		{
			SeekBar[] array = Resources.FindObjectsOfTypeAll<SeekBar>();
			foreach (SeekBar seekBar in array)
			{
				if (!((Object)(object)seekBar == (Object)null) && !((Object)(object)seekBar._slider == (Object)null))
				{
					seekBar._wasInteractable = value;
					((Selectable)seekBar._slider).interactable = value;
				}
			}
		}

		public static void Reset()
		{
			_failed = false;
		}

		private void LateUpdate()
		{
			try
			{
				Pump();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Seek bar disabled after an error: " + ex));
				((Behaviour)this).enabled = false;
			}
		}

		private void Pump()
		{
			SoundManager i = MonoSingleton<SoundManager>.I;
			AudioSource val = (((Object)(object)i != (Object)null) ? Refl.MixtapeMusic.Invoke(i) : null);
			AudioClip val2 = (((Object)(object)val != (Object)null) ? val.clip : null);
			if (!((Object)(object)val2 != (Object)null) || val2.samples <= 1 || MixtapeBridge.IsSilence(val2) || (!ModConfig.SeekBarOnVanillaMixtapes.Value && !MixtapeBridge.IsOurs(i)))
			{
				if (_wasInteractable)
				{
					((Selectable)_slider).interactable = false;
				}
				_slider.SetValueWithoutNotify(0f);
				SetReadout(-1, -1);
				return;
			}
			if (!((Selectable)_slider).interactable && _wasInteractable)
			{
				((Selectable)_slider).interactable = true;
			}
			if ((Object)(object)_watcher != (Object)null && _watcher.IsDragging)
			{
				if ((Object)(object)_ui != (Object)null && Refl.MusicTimer != null)
				{
					Refl.MusicTimer.Invoke(_ui) = 0f;
				}
				double num = TotalSeconds(i, val2);
				SetReadout((int)((double)_slider.value * num), (int)num);
			}
			else
			{
				int samples = val2.samples;
				float num2 = ((samples > 0) ? Mathf.Clamp01((float)val.timeSamples / (float)samples) : 0f);
				_slider.SetValueWithoutNotify(num2);
				double num3 = TotalSeconds(i, val2);
				SetReadout((int)((double)num2 * num3), (int)num3);
			}
		}

		private void OnRelease()
		{
			//IL_011e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			GameUi.DropFocus();
			try
			{
				SoundManager i = MonoSingleton<SoundManager>.I;
				AudioSource val = (((Object)(object)i != (Object)null) ? Refl.MixtapeMusic.Invoke(i) : null);
				AudioClip val2 = (((Object)(object)val != (Object)null) ? val.clip : null);
				if (!((Object)(object)val2 == (Object)null) && val2.samples > 1 && !MixtapeBridge.IsSilence(val2))
				{
					int num = Mathf.Max(1, val2.frequency / 4);
					int timeSamples = Mathf.Clamp(Mathf.RoundToInt(_slider.value * (float)val2.samples), 0, Mathf.Max(0, val2.samples - num));
					Stopwatch stopwatch = Stopwatch.StartNew();
					val.timeSamples = timeSamples;
					stopwatch.Stop();
					if (ModConfig.ProbeSeek.Value && !_probeLogged)
					{
						_probeLogged = true;
						Plugin.Log.LogInfo((object)("Seek probe: requested sample " + timeSamples + ", read back " + val.timeSamples + ", clip '" + ((Object)val2).name + "' loadType=" + ((object)val2.loadType/*cast due to .constrained prefix*/).ToString() + ", took " + stopwatch.Elapsed.TotalMilliseconds.ToString("0.0") + " ms."));
					}
					else if (stopwatch.Elapsed.TotalMilliseconds > 4.0 && Time.unscaledTime - _lastSlowSeekLog > 30f)
					{
						_lastSlowSeekLog = Time.unscaledTime;
						Plugin.Log.LogInfo((object)("Seeking takes " + stopwatch.Elapsed.TotalMilliseconds.ToString("0.0") + " ms on this machine."));
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Seek failed: " + ex));
			}
		}

		private static double TotalSeconds(SoundManager sm, AudioClip clip)
		{
			LocalTrack localTrack = MixtapeBridge.CurrentTrack(sm);
			if (localTrack != null && localTrack.Duration > 0.5)
			{
				return localTrack.Duration;
			}
			if (clip.frequency <= 0)
			{
				return 0.0;
			}
			return (double)clip.samples / (double)clip.frequency;
		}

		private void SetReadout(int seconds, int total)
		{
			if (!((Object)(object)_readout == (Object)null) && (seconds != _shownSecond || total != _shownTotal))
			{
				_shownSecond = seconds;
				_shownTotal = total;
				_readout.text = Clock(seconds) + " / " + Clock(total);
			}
		}

		private static string Clock(int seconds)
		{
			if (seconds < 0)
			{
				return "--:--";
			}
			int num = seconds / 60;
			int num2 = seconds % 60;
			return ((num < 10) ? "0" : "") + num + ":" + ((num2 < 10) ? "0" : "") + num2;
		}
	}
	public sealed class StaticText : MonoBehaviour
	{
		private TMP_Text _label;

		public string Text;

		public static StaticText Apply(TMP_Text label, string text)
		{
			if ((Object)(object)label == (Object)null)
			{
				return null;
			}
			StaticText staticText = ((Component)label).gameObject.GetComponent<StaticText>();
			if ((Object)(object)staticText == (Object)null)
			{
				staticText = ((Component)label).gameObject.AddComponent<StaticText>();
			}
			staticText._label = label;
			staticText.Text = text;
			label.text = text;
			return staticText;
		}

		private void OnEnable()
		{
			if ((Object)(object)_label != (Object)null && Text != null)
			{
				_label.text = Text;
			}
		}
	}
	public static class UiBuilder
	{
		private static UIManager _ui;

		public static void BuildMusicUi(UIManager ui)
		{
			if (!((Object)(object)ui == (Object)null))
			{
				_ui = ui;
				Transform val = MiniPlayer.Resolve(ui);
				if ((Object)(object)val != (Object)null && (Object)(object)GameUi.Existing(val, "OTLP_SeekBar") == (Object)null && (Object)(object)GameUi.Existing(val, "OTLP_FolderPath") == (Object)null)
				{
					MiniPlayer.Grow(ui, val, MiniPlayer.TotalGrow);
				}
				try
				{
					SeekBar.Build(ui);
				}
				catch (Exception ex)
				{
					Plugin.Log.LogError((object)("Seek bar build failed: " + ex));
				}
				try
				{
					ModPanel.Build(ui, val);
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogError((object)("Folder row build failed: " + ex2));
				}
				if (ModConfig.DumpUi.Value)
				{
					DumpMusic();
				}
			}
		}

		public static void DumpNow()
		{
			DumpMusic();
		}

		private static void DumpMusic()
		{
			try
			{
				if (!((Object)(object)_ui == (Object)null) && Refl.MixTapeVolumeSlider != null)
				{
					Slider val = Refl.MixTapeVolumeSlider.Invoke(_ui);
					GameUi.DumpHierarchy(((Object)(object)val != (Object)null && (Object)(object)((Component)val).transform.parent != (Object)null) ? ((Component)val).transform.parent.parent : null, "mini music player (Panel_MiniPlayer)");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("UI dump (music) failed: " + ex));
			}
		}
	}
}
namespace SleepeyDev.OnTogetherLocalPlaylist.Share
{
	public static class Broadcaster
	{
		private sealed class Subscriber
		{
			public CSteamID Id;

			public uint Cursor;

			public int Credit;

			public uint WantedTrack;

			public readonly HashSet<uint> Has = new HashSet<uint>();

			public float LastSeen;
		}

		private sealed class Sending
		{
			public uint TrackId;

			public string Path;

			public long Bytes;

			public uint Crc32;

			public bool CrcReady;

			public string Title;

			public string Artist;

			public uint DurationMs;

			public WireFormat Format;

			public float LastOfferAt;
		}

		private const float BeaconInterval = 0.25f;

		private const float OfferInterval = 2f;

		private const int DefaultCredit = 8;

		private const int QueuedBytesCeiling = 524288;

		private const int AggregateBytesPerSecond = 524288;

		private static readonly Dictionary<ulong, Subscriber> Subs = new Dictionary<ulong, Subscriber>();

		private static readonly Dictionary<uint, Sending> Catalogue = new Dictionary<uint, Sending>();

		private static readonly byte[] ChunkBuf = new byte[32768];

		private static uint _sessionId;

		private static uint _seq;

		private static float _nextBeacon;

		private static float _budgetWindowStart;

		private static int _budgetSpent;

		private static uint _slot0;

		private static uint _slot1;

		public static bool Active { get; private set; }

		public static int ListenerCount => Subs.Count;

		public static void Start()
		{
			if (!Active)
			{
				Active = true;
				_sessionId = FreshSessionId();
				_seq = 0u;
				_slot0 = (_slot1 = 0u);
				Subs.Clear();
				Plugin.Log.LogInfo((object)("Broadcasting, session " + _sessionId + "."));
			}
		}

		public static void Stop()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			if (!Active)
			{
				return;
			}
			Active = false;
			foreach (Subscriber value in Subs.Values)
			{
				P2PTransport.SendControl(value.Id, (byte[] b) => Messages.Bye(b, 0));
				P2PTransport.CloseChannels(value.Id);
			}
			Subs.Clear();
			Catalogue.Clear();
			Plugin.Log.LogInfo((object)"Stopped broadcasting.");
		}

		private static uint FreshSessionId()
		{
			return (uint)((int)(Environment.TickCount * 2654435761u) ^ -1640531527);
		}

		public static void OnMessage(CSteamID from, MsgType type, byte[] buf, int off, int len)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: 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_001e: Unknown result type (might be due to invalid IL or missing references)
			switch (type)
			{
			case MsgType.Sub:
				OnSub(from, new Wire.Reader(buf, off, len));
				break;
			case MsgType.Want:
				OnWant(from, new Wire.Reader(buf, off, len));
				break;
			case MsgType.Have:
				OnHave(from, new Wire.Reader(buf, off, len));
				break;
			case MsgType.Ping:
				OnPing(from, new Wire.Reader(buf, off, len));
				break;
			case MsgType.Bye:
				Drop(from, "said goodbye");
				break;
			}
		}

		private static void OnSub(CSteamID from, Wire.Reader r)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: 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)
			bool flag = r.U8() != 0;
			if (r.Failed)
			{
				return;
			}
			if (!flag)
			{
				Drop(from, "unsubscribed");
				return;
			}
			if (!Active)
			{
				P2PTransport.SendControl(from, (byte[] b) => Messages.SubAck(b, accepted: false, SubReason.NotBroadcasting, 0u));
				return;
			}
			if (!Subs.ContainsKey(from.m_SteamID) && Subs.Count >= Mathf.Max(1, ModConfig.MaxListeners.Value))
			{
				P2PTransport.SendControl(from, (byte[] b) => Messages.SubAck(b, accepted: false, SubReason.Full, 0u));
				return;
			}
			if (!Subs.TryGetValue(from.m_SteamID, out var value))
			{
				value = new Subscriber
				{
					Id = from
				};
				Subs[from.m_SteamID] = value;
				Plugin.Log.LogInfo((object)(NameOf(from) + " is now listening (" + Subs.Count + " total)."));
			}
			value.LastSeen = Time.unscaledTime;
			uint session = _sessionId;
			P2PTransport.SendControl(from, (byte[] b) => Messages.SubAck(b, accepted: true, SubReason.Ok, session));
		}

		private static void OnWant(CSteamID from, Wire.Reader r)
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			if (Messages.DecodeWant(r, out var v) && v.SessionId == _sessionId && Subs.TryGetValue(from.m_SteamID, out var value))
			{
				value.LastSeen = Time.unscaledTime;
				value.WantedTrack = v.TrackId;
				value.Cursor = v.FromChunk;
				value.Credit = Mathf.Clamp((v.Window == 0) ? 8 : v.Window, 1, 64);
			}
		}

		private static void OnHave(CSteamID from, Wire.Reader r)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			uint num = r.U32();
			uint num2 = r.U32();
			if (!r.Failed && num == _sessionId && Subs.TryGetValue(from.m_SteamID, out var value))
			{
				value.LastSeen = Time.unscaledTime;
				value.Has.Add(num2);
				if (value.WantedTrack == num2)
				{
					value.Credit = 0;
				}
				if (ModConfig.VerboseLogging.Value)
				{
					Plugin.Log.LogInfo((object)(NameOf(from) + " has track " + num2 + "."));
				}
			}
		}

		private static void OnPing(CSteamID from, Wire.Reader r)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			long t0 = r.I64();
			if (!r.Failed)
			{
				long t1 = Clock.NowUs();
				P2PTransport.SendControl(from, (byte[] b) => Messages.Pong(b, t0, t1));
			}
		}

		private static void Drop(CSteamID from, string why)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			if (Subs.Remove(from.m_SteamID))
			{
				Plugin.Log.LogInfo((object)(NameOf(from) + " " + why + " (" + Subs.Count + " left)."));
				P2PTransport.CloseChannels(from);
			}
		}

		public static void OnPeerUnreachable(CSteamID peer)
		{
			//IL_0005: 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)
			if (Subs.ContainsKey(peer.m_SteamID) && ModConfig.VerboseLogging.Value)
			{
				Plugin.Log.LogInfo((object)(NameOf(peer) + " is briefly unreachable; keeping the subscription."));
			}
		}

		public static void Tick(SoundManager sm)
		{
			if (Active && !((Object)(object)sm == (Object)null))
			{
				ResetBudgetWindow();
				UpdateSlots(sm);
				if (Time.unscaledTime >= _nextBeacon)
				{
					_nextBeacon = Time.unscaledTime + 0.25f;
					SendBeacons(sm);
				}
				PumpTransfers();
			}
		}

		private static void ResetBudgetWindow()
		{
			if (!(Time.unscaledTime - _budgetWindowStart < 1f))
			{
				_budgetWindowStart = Time.unscaledTime;
				_budgetSpent = 0;
			}
		}

		private static void UpdateSlots(SoundManager sm)
		{
			if (MixtapeBridge.Which(sm) != MixtapeBridge.Tape.Local)
			{
				return;
			}
			int num = Refl.SongIndex.Invoke(sm);
			LocalTrack localTrack = PlaylistLibrary.TrackAt(num);
			if (localTrack == null)
			{
				return;
			}
			uint num2 = Ids.For(localTrack);
			if (num2 != _slot0)
			{
				_slot0 = num2;
				Register(localTrack, num2);
				foreach (Subscriber value in Subs.Values)
				{
					value.Credit = 0;
				}
			}
			int count = PlaylistLibrary.Tracks.Count;
			if (count <= 1)
			{
				return;
			}
			LocalTrack localTrack2 = PlaylistLibrary.TrackAt((num + 1) % count);
			if (localTrack2 != null)
			{
				uint num3 = Ids.For(localTrack2);
				if (num3 != _slot1)
				{
					_slot1 = num3;
					Register(localTrack2, num3);
				}
			}
		}

		private static void Register(LocalTrack track, uint id)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			if (!Catalogue.ContainsKey(id))
			{
				long length;
				try
				{
					length = new FileInfo(track.Path).Length;
				}
				catch (Exception ex)
				{
					Plugin.Log.LogWarning((object)("Cannot offer " + track.Path + ": " + ex.Message));
					return;
				}
				Sending s = new Sending
				{
					TrackId = id,
					Path = track.Path,
					Bytes = length,
					Title = track.Song.Title,
					Artist = track.Song.Artist,
					DurationMs = (uint)Math.Max(0.0, track.Duration * 1000.0),
					Format = FormatOf(track.AudioType),
					LastOfferAt = -999f
				};
				Catalogue[id] = s;
				ThreadPool.QueueUserWorkItem(delegate
				{
					ComputeCrc(s);
				});
			}
		}

		private static void ComputeCrc(Sending s)
		{
			uint crc = 0u;
			try
			{
				byte[] array = new byte[65536];
				using FileStream fileStream = new FileStream(s.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, array.Length);
				int count;
				while ((count = fileStream.Read(array, 0, array.Length)) > 0)
				{
					crc = Crc32.Update(crc, array, 0, count);
				}
			}
			catch (Exception ex)
			{
				Exception ex2 = ex;
				Exception e = ex2;
				MainThread.Post(delegate
				{
					Plugin.Log.LogWarning((object)("Checksumming " + s.Path + " failed: " + e.Message));
				});
				return;
			}
			MainThread.Post(delegate
			{
				s.Crc32 = crc;
				s.CrcReady = true;
			});
		}

		private static WireFormat FormatOf(AudioType t)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Invalid comparison between Unknown and I4
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Invalid comparison between Unknown and I4
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Invalid comparison between Unknown and I4
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Invalid comparison between Unknown and I4
			if ((int)t <= 13)
			{
				if ((int)t == 2)
				{
					return WireFormat.Aiff;
				}
				if ((int)t == 13)
				{
					return WireFormat.Mp3;
				}
			}
			else
			{
				if ((int)t == 14)
				{
					return WireFormat.Ogg;
				}
				if ((int)t == 20)
				{
					return WireFormat.Wav;
				}
			}
			return WireFormat.Unknown;
		}

		private static void SendBeacons(SoundManager sm)
		{
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			bool num = MixtapeBridge.Which(sm) == MixtapeBridge.Tape.Local;
			AudioSource val = Refl.MixtapeMusic.Invoke(sm);
			uint posMs = 0u;
			long sentUs = Clock.NowUs();
			if (num && (Object)(object)val != (Object)null && (Object)(object)val.clip != (Object)null && !MixtapeBridge.IsSilence(val.clip) && val.clip.frequency > 0)
			{
				posMs = (uint)((long)val.timeSamples * 1000L / val.clip.frequency);
			}
			BeaconFlags flags = ((!num) ? BeaconFlags.Paused : ((!Refl.IsPlayingFlag.Invoke(sm)) ? BeaconFlags.Paused : ((!((Object)(object)val != (Object)null) || !((Object)(object)val.clip != (Object)null) || !MixtapeBridge.IsSilence(val.clip)) ? BeaconFlags.Playing : BeaconFlags.Buffering)));
			Beacon beacon = new Beacon
			{
				SessionId = _sessionId,
				TrackId = _slot0,
				Seq = _seq++,
				SentUs = sentUs,
				PosMs = posMs,
				Flags = flags,
				NextTrackId = _slot1
			};
			foreach (Subscriber value in Subs.Values)
			{
				P2PTransport.SendBeacon(value.Id, (byte[] b) => Messages.EncodeBeacon(b, in beacon));
			}
			SendOffers();
		}

		private static void SendOffers()
		{
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			uint[] array = new uint[2] { _slot0, _slot1 };
			foreach (uint num in array)
			{
				if (num == 0 || !Catalogue.TryGetValue(num, out var value) || !value.CrcReady || Time.unscaledTime - value.LastOfferAt < 2f)
				{
					continue;
				}
				value.LastOfferAt = Time.unscaledTime;
				Offer offer = new Offer
				{
					SessionId = _sessionId,
					Slot = ((num != _slot0) ? ((byte)1) : ((byte)0)),
					TrackId = value.TrackId,
					Bytes = (uint)Math.Min(value.Bytes, 4294967295L),
					DurationMs = value.DurationMs,
					Format = value.Format,
					Crc32 = value.Crc32,
					Title = value.Title,
					Artist = value.Artist
				};
				foreach (Subscriber value2 in Subs.Values)
				{
					if (!value2.Has.Contains(num))
					{
						P2PTransport.SendControl(value2.Id, (byte[] b) => Messages.EncodeOffer(b, in offer));
					}
				}
			}
		}

		private static void PumpTransfers()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			foreach (Subscriber value2 in Subs.Values)
			{
				if (value2.Credit <= 0 || value2.WantedTrack == 0 || !Catalogue.TryGetValue(value2.WantedTrack, out var value))
				{
					continue;
				}
				if (_budgetSpent >= 524288)
				{
					break;
				}
				if (P2PTransport.QueuedBytes(value2.Id) > 524288)
				{
					continue;
				}
				bool pastEnd;
				int num = ReadChunk(value, value2.Cursor, out pastEnd);
				if (pastEnd || num <= 0)
				{
					value2.Credit = 0;
					continue;
				}
				uint session = _sessionId;
				uint track = value.TrackId;
				uint idx = value2.Cursor;
				int count = num;
				if (P2PTransport.SendBulk(value2.Id, (byte[] b) => Messages.EncodeData(b, session, track, idx, ChunkBuf, 0, count)))
				{
					value2.Cursor++;
					value2.Credit--;
					_budgetSpent += count;
				}
				else
				{
					value2.Credit = 0;
				}
			}
		}

		private static int ReadChunk(Sending s, uint index, out bool pastEnd)
		{
			pastEnd = false;
			long num = (long)index * 32768L;
			if (num >= s.Bytes)
			{
				pastEnd = true;
				return 0;
			}
			int num2 = (int)Math.Min(32768L, s.Bytes - num);
			try
			{
				using FileStream fileStream = new FileStream(s.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096);
				fileStream.Seek(num, SeekOrigin.Begin);
				int i;
				int num3;
				for (i = 0; i < num2; i += num3)
				{
					num3 = fileStream.Read(ChunkBuf, i, num2 - i);
					if (num3 <= 0)
					{
						break;
					}
				}
				return i;
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Reading a chunk of " + s.Path + " failed: " + ex.Message));
				return 0;
			}
		}

		private static string NameOf(CSteamID id)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!Discovery.TryGetPeer(id, out var peer))
			{
				return id.m_SteamID.ToString();
			}
			return peer.Name;
		}
	}
	public static class Clock
	{
		private static readonly Stopwatch Watch = Stopwatch.StartNew();

		public static long NowUs()
		{
			return Watch.ElapsedTicks / (Stopwatch.Frequency / 1000000);
		}
	}
	public static class Ids
	{
		public static uint For(LocalTrack track)
		{
			if (track == null)
			{
				return 0u;
			}
			long num = 0L;
			long num2 = 0L;
			try
			{
				FileInfo fileInfo = new FileInfo(track.Path);
				num = fileInfo.Length;
				num2 = fileInfo.LastWriteTimeUtc.Ticks;
			}
			catch (Exception)
			{
			}
			uint num3 = 2166136261u;
			string text = track.Path ?? "";
			for (int i = 0; i < text.Length; i++)
			{
				num3 = (num3 ^ text[i]) * 16777619;
			}
			num3 = (num3 ^ (uint)(int)num) * 16777619;
			num3 = (num3 ^ (uint)(int)num2) * 16777619;
			if (num3 != 0)
			{
				return num3;
			}
			return 1u;
		}
	}
	public static class Listener
	{
		private sealed class Transfer
		{
			public Offer Offer;

			public Spool Spool;

			public string PartPath;

			public string FinalPath;

			public float LastWantAt;

			public bool Loading;

			public bool Failed;
		}

		private const float WantInterval = 1f;

		private const float PingInterval = 2f;

		private const float BroadcasterTimeout = 3f;

		private const ushort WantWindow = 8;

		private static readonly Dictionary<uint, Transfer> Transfers = new Dictionary<uint, Transfer>();

		private static readonly byte[] ChunkBuf = new byte[32768];

		private static readonly SyncMath.OffsetEstimator Offsets = new SyncMath.OffsetEstimator();

		private static readonly SyncMath.Smoother ErrorSmoother = new SyncMath.Smoother();

		private static CSteamID _host;

		private static uint _sessionId;

		private static float _lastBeaconAt;

		private static float _lastPingAt;

		private static Beacon _beacon;

		private static bool _haveBeacon;

		private static uint _playingTrack;

		private const long MaxTrackBytes = 67108864L;

		private const long MaxCacheBytes = 536870912L;

		private const int MaxConcurrentTransfers = 4;

		private static string _shownOnTape;

		public static bool Active { get; private set; }

		public static CSteamID Host => _host;

		public static string Status { get; private set; } = "";

		private static string CacheDir => Path.Combine(Application.temporaryCachePath, "OTLP-shared");

		public static void Join(CSteamID host)
		{
			//IL_001a: 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_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			if (!Active || !(host == _host))
			{
				Leave();
				_host = host;
				Active = true;
				_haveBeacon = false;
				_lastBeaconAt = Time.unscaledTime;
				Offsets.Reset();
				ErrorSmoother.Reset();
				Status = "Connecting...";
				LiveTape.ShowStatus("Connecting...", NameOf(host));
				P2PTransport.SendControl(host, (byte[] b) => Messages.Sub(b, subscribe: true));
				Plugin.Log.LogInfo((object)("Asked " + NameOf(host) + " to share."));
			}
		}

		public static void Leave()
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			if (!Active)
			{
				return;
			}
			P2PTransport.SendControl(_host, (byte[] b) => Messages.Sub(b, subscribe: false));
			P2PTransport.CloseChannels(_host);
			Active = false;
			_haveBeacon = false;
			_playingTrack = 0u;
			Status = "";
			foreach (Transfer value in Transfers.Values)
			{
				value.Spool?.Dispose();
			}
			Transfers.Clear();
			LiveTape.Clear();
			Plugin.Log.LogInfo((object)"Stopped listening.");
		}

		public static void OnMessage(CSteamID from, MsgType type, byte[] buf, int off, int len)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			if (Active && !(from != _host))
			{
				switch (type)
				{
				case MsgType.SubAck:
					OnSubAck(new Wire.Reader(buf, off, len));
					break;
				case MsgType.Offer:
					OnOffer(new Wire.Reader(buf, off, len));
					break;
				case MsgType.Data:
					OnData(new Wire.Reader(buf, off, len));
					break;
				case MsgType.Beacon:
					OnBeacon(new Wire.Reader(buf, off, len));
					break;
				case MsgType.Pong:
					OnPong(new Wire.Reader(buf, off, len));
					break;
				case MsgType.Bye:
					OnBye();
					break;
				}
			}
		}

		private static void OnSubAck(Wire.Reader r)
		{
			//IL_0085: 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)
			bool flag = r.U8() != 0;
			SubReason r2 = (SubReason)r.U8();
			uint sessionId = r.U32();
			if (!r.Failed)
			{
				if (!flag)
				{
					Status = Describe(r2);
					LiveTape.ShowStatus(Status, NameOf(_host));
					Plugin.Log.LogInfo((object)("Refused: " + r2.ToString() + "."));
					Active = false;
				}
				else
				{
					_sessionId = sessionId;
					Status = "Waiting for audio";
					LiveTape.ShowStatus(Status, NameOf(_host));
				}
			}
		}

		private static string Describe(SubReason r)
		{
			return r switch
			{
				SubReason.Full => "That session is full", 
				SubReason.NotBroadcasting => "They stopped sharing", 
				SubReason.VersionMismatch => "Different mod version", 
				_ => "Refused", 
			};
		}

		private static void OnOffer(Wire.Reader r)
		{
			if (!Messages.DecodeOffer(r, out var o) || o.Bytes == 0 || o.ChunkCount <= 0 || (long)o.Bytes > 67108864L || o.Format == WireFormat.Unknown)
			{
				return;
			}
			if (Transfers.TryGetValue(o.TrackId, out var value))
			{
				value.Offer = o;
				if (value.Spool != null)
				{
					value.Spool.SessionId = o.SessionId;
				}
				return;
			}
			if (OpenTransfers() >= 4)
			{
				if (ModConfig.VerboseLogging.Value)
				{
					Plugin.Log.LogInfo((object)("Ignoring an offer: already receiving " + 4 + "."));
				}
				return;
			}
			if (CacheBytes() + o.Bytes > 536870912)
			{
				Plugin.Log.LogWarning((object)("The shared-music cache has reached " + 512L + " MB; refusing more for now."));
				return;
			}
			string text = CachePath(o.TrackId, Extension(o.Format));
			if (File.Exists(text))
			{
				Transfer value2 = new Transfer
				{
					Offer = o,
					FinalPath = text
				};
				Transfers[o.TrackId] = value2;
				Send((byte[] b) => Messages.Have(b, _sessionId, o.TrackId));
				MaybePlay(o.TrackId);
				return;
			}
			string text2 = text + ".part";
			Spool spool;
			try
			{
				Directory.CreateDirectory(CacheDir);
				FileStream fs = new FileStream(text2, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);
				spool = new Spool(o.TrackId, o.SessionId, o.Bytes, o.Crc32, new FileChunkStore(fs));
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Cannot open a cache file for the shared track: " + ex.Message));
				return;
			}
			Transfers[o.TrackId] = new Transfer
			{
				Offer = o,
				Spool = spool,
				PartPath = text2,
				FinalPath = text,
				LastWantAt = -999f
			};
			if (ModConfig.VerboseLogging.Value)
			{
				Plugin.Log.LogInfo((object)("Offered '" + o.Title + "' (" + o.Bytes / 1024 + " KiB, " + o.ChunkCount + " chunks)."));
			}
		}

		private static int OpenTransfers()
		{
			int num = 0;
			foreach (Transfer value in Transfers.Values)
			{
				if (value.Spool != null)
				{
					num++;
				}
			}
			return num;
		}

		private static long CacheBytes()
		{
			try
			{
				if (!Directory.Exists(CacheDir))
				{
					return 0L;
				}
				long num = 0L;
				string[] files = Directory.GetFiles(CacheDir);
				foreach (string fileName in files)
				{
					num += new FileInfo(fileName).Length;
				}
				return num;
			}
			catch (Exception)
			{
				return 0L;
			}
		}

		private static bool LooksLikeAudio(string path, WireFormat format)
		{
			try
			{
				byte[] array = new byte[12];
				using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
				{
					if (fileStream.Read(array, 0, array.Length) < 4)
					{
						return false;
					}
				}
				return format switch
				{
					WireFormat.Mp3 => (array[0] == 73 && array[1] == 68 && array[2] == 51) || (array[0] == byte.MaxValue && (array[1] & 0xE0) == 224), 
					WireFormat.Ogg => array[0] == 79 && array[1] == 103 && array[2] == 103 && array[3] == 83, 
					WireFormat.Wav => array[0] == 82 && array[1] == 73 && array[2] == 70 && array[3] == 70 && array[8] == 87 && array[9] == 65 && array[10] == 86 && array[11] == 69, 
					WireFormat.Aiff => array[0] == 70 && array[1] == 79 && array[2] == 82 && array[3] == 77, 
					_ => false, 
				};
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static void OnData(Wire.Reader r)
		{
			if (!Messages.DecodeData(r, ChunkBuf, out var sessionId, out var trackId, out var chunkIdx, out var count) || sessionId != _sessionId || !Transfers.TryGetValue(trackId, out var value) || value.Spool == null || !value.Spool.Accept(chunkIdx, ChunkBuf, count) || !value.Spool.Complete)
			{
				return;
			}
			value.Spool.Flush();
			value.Spool.Dispose();
			value.Spool = null;
			if (!Verify(value))
			{
				value.Failed = true;
				Plugin.Log.LogWarning((object)"The shared track arrived corrupt; discarding it.");
				SafeDelete(value.PartPath);
				Transfers.Remove(trackId);
				return;
			}
			if (!LooksLikeAudio(value.PartPath, value.Offer.Format))
			{
				value.Failed = true;
				Plugin.Log.LogWarning((object)("A peer sent something that is not " + value.Offer.Format.ToString() + " audio; discarding it without decoding."));
				SafeDelete(value.PartPath);
				Transfers.Remove(trackId);
				return;
			}
			try
			{
				if (File.Exists(value.FinalPath))
				{
					File.Delete(value.FinalPath);
				}
				File.Move(value.PartPath, value.FinalPath);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not finish the shared track: " + ex.Message));
				return;
			}
			Plugin.Log.LogInfo((object)("Received '" + value.Offer.Title + "'."));
			Send((byte[] b) => Messages.Have(b, _sessionId, trackId));
			MaybePlay(trackId);
		}

		private static bool Verify(Transfer t)
		{
			try
			{
				uint num = 0u;
				byte[] array = new byte[65536];
				using (FileStream fileStream = new FileStream(t.PartPath, FileMode.Open, FileAccess.Read))
				{
					int count;
					while ((count = fileStream.Read(array, 0, array.Length)) > 0)
					{
						num = Crc32.Update(num, array, 0, count);
					}
				}
				return num == t.Offer.Crc32;
			}
			catch (Exception)
			{
				return false;
			}
		}

		private static void OnBeacon(Wire.Reader r)
		{
			if (Messages.DecodeBeacon(r, out var b) && b.SessionId == _sessionId)
			{
				_beacon = b;
				_haveBeacon = true;
				_lastBeaconAt = Time.unscaledTime;
				MaybePlay(b.TrackId);
			}
		}

		private static void OnPong(Wire.Reader r)
		{
			long t0Us = r.I64();
			long t1Us = r.I64();
			if (!r.Failed)
			{
				long t3Us = Clock.NowUs();
				Offsets.Push(SyncMath.Offset(t0Us, t1Us, t3Us), SyncMath.RoundTrip(t0Us, t3Us));
			}
		}

		private static void OnBye()
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			Status = "They stopped sharing";
			LiveTape.ShowStatus(Status, NameOf(_host));
			Leave();
		}

		public static void Tick(SoundManager sm)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if (!Active)
			{
				return;
			}
			if (Time.unscaledTime - _lastBeaconAt > 3f && _haveBeacon)
			{
				Status = "Lost the broadcaster";
				LiveTape.ShowStatus(Status, NameOf(_host));
				_haveBeacon = false;
			}
			if (Time.unscaledTime - _lastPingAt >= 2f)
			{
				_lastPingAt = Time.unscaledTime;
				long t0 = Clock.NowUs();
				Send((byte[] b) => Messages.Ping(b, t0));
			}
			RequestChunks();
			UpdateStatus();
			Follow(sm);
		}

		private static void RequestChunks()
		{
			foreach (Transfer value in Transfers.Values)
			{
				if (value.Spool != null && !value.Failed && !(Time.unscaledTime - value.LastWantAt < 1f))
				{
					value.LastWantAt = Time.unscaledTime;
					Want want = new Want
					{
						SessionId = _sessionId,
						TrackId = value.Offer.TrackId,
						FromChunk = (uint)value.Spool.FirstMissing,
						Window = 8
					};
					Send((byte[] b) => Messages.EncodeWant(b, in want));
				}
			}
		}

		private static void UpdateStatus()
		{
			if (_haveBeacon)
			{
				if (Transfers.TryGetValue(_beacon.TrackId, out var value) && value.Spool != null)
				{
					Status = "Buffering " + Mathf.RoundToInt(value.Spool.Progress * 100f) + "%";
					PushToTape(Status, value.Offer.Title);
				}
				else if (_playingTrack == _beacon.TrackId)
				{
					Status = "Listening";
				}
			}
		}

		private static void PushToTape(string title, string artist)
		{
			LiveTape.ShowStatus(title, artist);
			if (title == _shownOnTape)
			{
				return;
			}
			_shownOnTape = title;
			SoundManager i = MonoSingleton<SoundManager>.I;
			if ((Object)(object)i == (Object)null || MixtapeBridge.Which(i) != MixtapeBridge.Tape.Live)
			{
				return;
			}
			UIManager i2 = MonoSingleton<UIManager>.I;
			if ((Object)(object)i2 == (Object)null)
			{
				return;
			}
			try
			{
				i2.UpdateSongInfo(i.GetSong());
			}
			catch (Exception)
			{
			}
		}

		private static void MaybePlay(uint trackId)
		{
			if (_haveBeacon && _beacon.TrackId == trackId && _playingTrack != trackId && Transfers.TryGetValue(trackId, out var value) && value.Spool == null && !value.Failed && !value.Loading && !string.IsNullOrEmpty(value.FinalPath) && File.Exists(value.FinalPath))
			{
				value.Loading = true;
				ModRoot.Run(LoadAndHandOver(value));
			}
		}

		private static IEnumerator LoadAndHandOver(Transfer t)
		{
			AudioType val = AudioTypeOf(t.Offer.Format);
			UnityWebRequest uwr = null;
			try
			{
				uwr = UnityWebRequestMultimedia.GetAudioClip(FileUrl.ForRequest(t.FinalPath), val);
				DownloadHandler downloadHandler = uwr.downloadHandler;
				DownloadHandlerAudioClip val2 = (DownloadHandlerAudioClip)(object)((downloadHandler is DownloadHandlerAudioClip) ? downloadHandler : null);
				if (val2 != null)
				{
					val2.streamAudio = true;
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not open the shared track: " + ex.Message));
				UnityWebRequest obj = uwr;
				if (obj != null)
				{
					obj.Dispose();
				}
				t.Loading = false;
				yield break;
			}
			yield return uwr.SendWebRequest();
			if ((int)uwr.result != 1)
			{
				Plugin.Log.LogWarning((object)("Decoding the shared track failed: " + uwr.error));
				uwr.Dispose();
				t.Loading = false;
				yield break;
			}
			AudioClip val3 = null;
			try
			{
				val3 = DownloadHandlerAudioClip.GetContent(uwr);
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogWarning((object)("Decoding the shared track threw: " + ex2.Message));
			}
			if ((Object)(object)val3 == (Object)null || val3.samples <= 0)
			{
				uwr.Dispose();
				t.Loading = false;
				yield break;
			}
			((Object)val3).name = t.Offer.Title;
			LiveTape.SetTrack(t.Offer.TrackId, t.Offer.Title, t.Offer.Artist, val3);
			_playingTrack = t.Offer.TrackId;
			t.Loading = false;
			Status = "Listening";
			SoundManager i = MonoSingleton<SoundManager>.I;
			if ((Object)(object)i != (Object)null)
			{
				LiveTape.SetName(i, ModConfig.LiveTapeName.Value + ": " + NameOf(_host));
			}
		}

		private static void Follow(SoundManager sm)
		{
			if ((Object)(object)sm == (Object)null || !_haveBeacon || MixtapeBridge.Which(sm) != MixtapeBridge.Tape.Live || _playingTrack == 0 || _playingTrack != _beacon.TrackId)
			{
				return;
			}
			AudioSource val = Refl.MixtapeMusic.Invoke(sm);
			AudioClip val2 = (((Object)(object)val != (Object)null) ? val.clip : null);
			if ((Object)(object)val2 == (Object)null || MixtapeBridge.IsSilence(val2) || val2.frequency <= 0)
			{
				return;
			}
			if ((_beacon.Flags & BeaconFlags.Playing) == 0)
			{
				if (!Mathf.Approximately(val.pitch, 0f))
				{
					val.pitch = 0f;
				}
				return;
			}
			double num = SyncMath.TargetSeconds(_beacon.PosMs, _beacon.SentUs, Clock.NowUs(), Offsets.Best(), (double)Mathf.Max(0, ModConfig.ListenLatencyMs.Value) / 1000.0);
			double num2 = (double)val.timeSamples / (double)val2.frequency;
			double num3 = ((SyncMath.Decide(num - num2) == SyncAction.Seek) ? (num - num2) : ErrorSmoother.Push(num - num2));
			switch (SyncMath.Decide(num3))
			{
			case SyncAction.Hold:
				if (!Mathf.Approximately(val.pitch, 1f))
				{
					val.pitch = 1f;
				}
				break;
			case SyncAction.Nudge:
				val.pitch = SyncMath.PitchFor(num3);
				break;
			case SyncAction.Seek:
			{
				int num4 = Mathf.Max(1, val2.frequency / 4);
				int timeSamples = Mathf.Clamp((int)(num * (double)val2.frequency), 0, Mathf.Max(0, val2.samples - num4));
				val.timeSamples = timeSamples;
				val.pitch = 1f;
				ErrorSmoother.Reset();
				if (ModConfig.VerboseLogging.Value)
				{
					Plugin.Log.LogInfo((object)("Re-synced by " + num3.ToString("0.00") + " s."));
				}
				break;
			}
			}
		}

		private static void Send(Func<byte[], int> encode)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			P2PTransport.SendControl(_host, encode);
		}

		private static string CachePath(uint trackId, string ext)
		{
			return Path.Combine(CacheDir, trackId.ToString("x8") + ext);
		}

		private static string Extension(WireFormat f)
		{
			return f switch
			{
				WireFormat.Mp3 => ".mp3", 
				WireFormat.Ogg => ".ogg", 
				WireFormat.Wav => ".wav", 
				WireFormat.Aiff => ".aiff", 
				_ => ".bin", 
			};
		}

		private static AudioType AudioTypeOf(WireFormat f)
		{
			return (AudioType)(f switch
			{
				WireFormat.Mp3 => 13, 
				WireFormat.Ogg => 14, 
				WireFormat.Wav => 20, 
				WireFormat.Aiff => 2, 
				_ => 0, 
			});
		}

		private static void SafeDelete(string path)
		{
			try
			{
				if (!string.IsNullOrEmpty(path) && File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception)
			{
			}
		}

		private static string NameOf(CSteamID id)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			if (!Discovery.TryGetPeer(id, out var peer))
			{
				return id.m_SteamID.ToString();
			}
			return peer.Name;
		}

		public static void ClearCache()
		{
			try
			{
				if (Directory.Exists(CacheDir))
				{
					Directory.Delete(CacheDir, recursive: true);
				}
			}
			catch (Exception)
			{
			}
		}
	}
	public sealed class FileChunkStore : IChunkStore, IDisposable
	{
		private readonly FileStream _fs;

		public FileChunkStore(FileStream fs)
		{
			_fs = fs;
		}

		public void Write(long offset, byte[] data, int dataOffset, int count)
		{
			_fs.Seek(offset, SeekOrigin.Begin);
			_fs.Write(data, dataOffset, count);
		}

		public void Flush()
		{
			_fs.Flush(flushToDisk: true);
		}

		public void Dispose()
		{
			_fs.Dispose();
		}
	}
	public static class LiveTape
	{
		public static readonly List<Song> Songs = new List<Song>
		{
			new Song
			{
				Title = "Nobody is sharing",
				Artist = "",
				Music = null,
				Volume = 1f
			}
		};

		private static Song Slot => Songs[0];

		public static bool Injected { get; private set; }

		public static int MixtapeIndex { get; private set; } = -1;

		public static AudioClip Clip { get; private set; }

		public static uint TrackId { get; private set; }

		public static bool HasClip => (Object)(object)Clip != (Object)null;

		public static void EnsureInjected(SoundManager sm)
		{
			//IL_0042: 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_00bc: Unknown result type (might be due to invalid IL or missing references)
			if (Injected || (Object)(object)sm == (Object)null || !ModConfig.SharingEnabled.Value)
			{
				return;
			}
			MusicMixtapes mixtapes = sm.Mixtapes;
			if ((Object)(object)mixtapes == (Object)null || mixtapes.MixTapes == null)
			{
				return;
			}
			for (int i = 0; i < mixtapes.MixTapes.Count; i++)
			{
				if (mixtapes.MixTapes[i].Songs == Songs)
				{
					MixtapeIndex = i;
					Injected = true;
					return;
				}
			}
			Sprite val = BorrowCover(mixtapes);
			mixtapes.MixTapes.Add(new Mixtape
			{
				MixtapeName = ModConfig.LiveTapeName.Value,
				Songs = Songs,
				IsSummerDLC = false,
				MixtapeCover = val,
				MixtapeStreamerCover = val
			});
			MixtapeIndex = mixtapes.MixTapes.Count - 1;
			Injected = true;
			Plugin.Log.LogInfo((object)("Injected the shared-listening tape at index " + MixtapeIndex + "."));
		}

		private static Sprite BorrowCover(MusicMixtapes asset)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < asset.MixTapes.Count; i++)
			{
				Sprite mixtapeCover = asset.MixTapes[i].MixtapeCover;
				if ((Object)(object)mixtapeCover != (Object)null)
				{
					return mixtapeCover;
				}
			}
			return null;
		}

		public static void SetName(SoundManager sm, string name)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (!Injected || (Object)(object)sm == (Object)null)
			{
				return;
			}
			MusicMixtapes mixtapes = sm.Mixtapes;
			if (!((Object)(object)mixtapes == (Object)null) && MixtapeIndex >= 0 && MixtapeIndex < mixtapes.