Decompiled source of ClipLinkMedia v5.11.0

Mods\ClipLinkMedia.dll

Decompiled 2 days ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BoneLib;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using ClipLinkMedia;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow.Data;
using LabFusion.Entities;
using LabFusion.Marrow.Pool;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using LabFusion.Player;
using LabFusion.RPC;
using LabFusion.SDK.Modules;
using LabFusion.UI;
using LabFusion.Utilities;
using MelonLoader;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "ClipLink Media", "5.11.0", "seeleyllp-crypto", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: AssemblyFileVersion("5.11.0.0")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyVersion("5.11.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ClipLinkMedia
{
	public sealed class Core : MelonMod
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Action<NetworkPlayer> <0>__TryAttachOwnerTag;

			public static ServerEvent <1>__OnFusionJoinedServer;

			public static ServerEvent <2>__OnFusionDisconnected;

			public static PlayerUpdate <3>__OnFusionPlayerJoined;

			public static PlayerUpdate <4>__OnFusionPlayerLeft;

			public static Action <5>__OpenRealYouTubeBrowser;

			public static Action <6>__CopyLastRealYouTubeSelection;

			public static Action <7>__SpawnMediaPlayerWithRealYouTubeSelection;

			public static Action <8>__CheckRealYouTubeBrowser;

			public static Action <9>__OpenRealYouTubeBrowserDownloadPage;

			public static Action <10>__OpenRealYouTubeBrowserFolder;

			public static Action <11>__OpenYouTube;

			public static Action <12>__SearchYouTube;

			public static Action <13>__AddCopiedFavorite;

			public static Action <14>__PreviewCopiedVideo;

			public static Action <15>__CopyLastPreview;

			public static Action <16>__StartClipboardJob;

			public static Action <17>__StartLocalDownloadJob;

			public static Action <18>__RetryLastFailedUpload;

			public static Action <19>__CancelCurrentJob;

			public static Action <20>__SpawnMediaPlayerWithLastUrl;

			public static Action <21>__CheckMediaPlayerSetup;

			public static Action <22>__StartLastPublicJob;

			public static Action <23>__StartLastLocalJob;

			public static Action <24>__CopyLastSourceUrl;

			public static Action <25>__OpenLastSourceUrl;

			public static Action <26>__ShowCurrentQuality;

			public static Action <27>__ShowCurrentRetention;

			public static Action <28>__CopyLastPublicUrl;

			public static Action <29>__OpenLastPublicUrl;

			public static Action <30>__CheckSetup;

			public static Action <31>__OpenYtDlpDownload;

			public static Action <32>__OpenYtDlpFolder;

			public static Action <33>__OpenDownloadsFolder;

			public static Action <34>__CopyDownloadsPath;

			public static Action <35>__InspectCopiedUrl;

			public static Action <36>__OpenCopiedYouTubeUrl;

			public static Action <37>__ShowStatistics;

			public static Action <38>__ShowJobStatus;

			public static Action <39>__CopySetupReport;

			public static Action <40>__ShowClock;

			public static Action <41>__ShowSceneAndUptime;

			public static Action <42>__ShowMeasuredFps;

			public static Action <43>__CopyHeadsetPosition;

			public static Action <44>__CopySessionReport;

			public static Action <45>__CopyDeviceReport;

			public static Action <46>__StartUtilityStopwatch;

			public static Action <47>__PauseUtilityStopwatch;

			public static Action <48>__ShowUtilityStopwatch;

			public static Action <49>__ResetUtilityStopwatch;

			public static Action <50>__ShowCountdown;

			public static Action <51>__CancelCountdown;

			public static Action <52>__SavePersonalNote;

			public static Action <53>__CopyPersonalNote;

			public static Action <54>__ClearPersonalNote;

			public static Action <55>__ShowLocalSettings;

			public static Action<bool> <56>__SetLowFpsAlerts;

			public static Action <57>__CopyLocalTimestamp;

			public static Action <58>__CopyUtcTimestamp;

			public static Action <59>__CopyCurrentScene;

			public static Action <60>__RunModHealthCheck;

			public static Action <61>__CopyInstalledModList;

			public static Action <62>__CopyRecentLogErrors;

			public static Action <63>__CopyCompleteSupportReport;

			public static Action <64>__OpenModsFolder;

			public static Action <65>__OpenMelonLoaderFolder;

			public static Action <66>__OpenUserDataFolder;

			public static Action <67>__OpenGameFolder;

			public static Action <68>__StartConnectivityTest;

			public static Action <69>__CheckForClipLinkUpdate;

			public static Action <70>__OpenLatestRelease;

			public static Action <71>__ShowFusionPlayerCount;

			public static Action <72>__ShowClipLinkUsers;

			public static Action <73>__CopyClipLinkUserList;

			public static Action <74>__RefreshClipLinkDetection;

			public static Action <75>__CopyFusionPlayerList;

			public static Action <76>__CopyFusionSessionReport;

			public static Action <77>__ShowDiskSpace;

			public static Action <78>__TakeScreenshot;

			public static Action <79>__OpenScreenshotsFolder;

			public static Action <80>__BackupClipLinkData;

			public static Action <81>__OpenBackupsFolder;

			public static Action <82>__CleanOldClipLinkTemps;

			public static Action <83>__RunConnectivityTest;

			public static Action <84>__LoadLatestRelease;

			public static Action <85>__CleanOldClipLinkTempsWorker;

			public static Action <86>__ClearSearchHistory;

			public static Action <87>__ClearRecentVideos;

			public static Action <88>__RemoveCopiedFavorite;

			public static Action <89>__ClearFavorites;

			public static Action <90>__AddCopiedToQueue;

			public static Action <91>__RemoveNextQueuedVideo;

			public static Action <92>__ClearQueue;

			public static Action <93>__RemoveExpiredHistory;

			public static Action <94>__ClearHistory;

			public static Func<string, DateTime> <95>__GetLastWriteTimeUtc;

			public static Action <96>__RefreshAndOpenDownloadsPage;

			public static Action <97>__ContinueQueue;
		}

		private const string ModVersion = "5.11.0";

		private const ulong OwnerPlatformId = 76561199548494681uL;

		private const string YouTubeHome = "https://www.youtube.com/";

		private const string RealYouTubeBrowserFileName = "ClipLinkYouTubeBrowser.exe";

		private const string YtDlpReleasesPageUrl = "https://github.com/yt-dlp/yt-dlp/releases/latest";

		private const string LitterboxUploadUrl = "https://litterbox.catbox.moe/resources/internals/api.php";

		private const string LatestReleaseApiUrl = "https://api.github.com/repos/seeleyllp-crypto/cliplink/releases/latest";

		private const string ReleasesPageUrl = "https://github.com/seeleyllp-crypto/cliplink/releases";

		private const string MediaPlayerPalletFolder = "Elijoe.MediaPlayer";

		private const string MediaPlayerBarcode = "Elijoe.MediaPlayer.Spawnable.MediaPlayer";

		private const string FlatScreenMediaPlayerBarcode = "Elijoe.MediaPlayer.Spawnable.FlatscreenMediaPlayer";

		private const string CrtMediaPlayerBarcode = "Elijoe.MediaPlayer.Spawnable.CRTTV";

		private const string PhoneMediaPlayerBarcode = "Elijoe.MediaPlayer.Spawnable.PhoneMediaPlayer";

		private const string ComputerMediaPlayerBarcode = "Elijoe.MediaPlayer.Spawnable.ComputerMonitor";

		private const string BoomBoxMediaPlayerBarcode = "Elijoe.MediaPlayer.Spawnable.BoomBox";

		private const string DefaultLitterboxRetention = "72h";

		private const int MaximumHistoryEntries = 8;

		private const int MaximumRecentVideos = 12;

		private const int MaximumRecentSearches = 8;

		private const int MaximumFavorites = 20;

		private const int MaximumQueuedVideos = 10;

		private const int MaximumDownloadLibraryEntries = 20;

		private const int MaximumThumbnailCacheEntries = 48;

		private const long MaxUploadBytes = 1000000000L;

		private const long MinimumTemporarySpace = 1258291200L;

		private static readonly ConcurrentQueue<Action> MainThreadActions = new ConcurrentQueue<Action>();

		private static readonly HttpClient YouTubeHttpClient = CreateYouTubeHttpClient();

		private static readonly HttpClient DiagnosticsHttpClient = CreateDiagnosticsHttpClient();

		private static readonly object JobGate = new object();

		private static readonly Stopwatch UtilityStopwatch = new Stopwatch();

		private static readonly Dictionary<NetworkPlayer, OwnerTagElement> OwnerTags = new Dictionary<NetworkPlayer, OwnerTagElement>();

		private static readonly object PresenceGate = new object();

		private static readonly Dictionary<byte, ClipLinkPresenceInfo> ClipLinkUsers = new Dictionary<byte, ClipLinkPresenceInfo>();

		private static readonly object ThumbnailGate = new object();

		private static readonly Dictionary<string, Texture2D> ThumbnailCache = new Dictionary<string, Texture2D>(StringComparer.Ordinal);

		private static readonly Dictionary<string, List<FunctionElement>> PendingThumbnailButtons = new Dictionary<string, List<FunctionElement>>(StringComparer.Ordinal);

		private static readonly Queue<string> ThumbnailCacheOrder = new Queue<string>();

		private static readonly SemaphoreSlim ThumbnailDownloadSlots = new SemaphoreSlim(3, 3);

		private static readonly List<LinkHistoryEntry> LinkHistory = new List<LinkHistoryEntry>();

		private static string _dataDirectory = string.Empty;

		private static string _downloadsDirectory = string.Empty;

		private static string _settingsPath = string.Empty;

		private static string _historyPath = string.Empty;

		private static string _backupsDirectory = string.Empty;

		private static string _screenshotsDirectory = string.Empty;

		private static string _ytDlpPath = string.Empty;

		private static string _realYouTubeBrowserPath = string.Empty;

		private static string _realYouTubeSelectionPath = string.Empty;

		private static string _lastPublicUrl = string.Empty;

		private static string _searchQuery = "bonelab";

		private static Page? _searchResultsPage;

		private static Page? _historyPage;

		private static Page? _recentVideosPage;

		private static Page? _searchHistoryPage;

		private static Page? _favoritesPage;

		private static Page? _queuePage;

		private static Page? _downloadsPage;

		private static Texture2D? _youtubeBackground;

		private static Texture2D? _youtubeLogo;

		private static ClipLinkSettings _settings = new ClipLinkSettings();

		private static CancellationTokenSource? _jobCancellation;

		private static string _jobStatus = "Idle";

		private static string _lastPreviewSummary = string.Empty;

		private static string _draftNote = string.Empty;

		private static string _latestReleaseUrl = "https://github.com/seeleyllp-crypto/cliplink/releases";

		private static DateTimeOffset? _jobStartedUtc;

		private static DateTimeOffset? _countdownEndsUtc;

		private static string _countdownLabel = string.Empty;

		private static float _fpsWindowSeconds;

		private static float _measuredFps;

		private static int _fpsWindowFrames;

		private static bool _queueModeActive;

		private static bool _queueUploadPublicly;

		private static bool _cleanupConfirmed;

		private static bool _rightsConfirmed;

		private static float _lowFpsSeconds;

		private static bool _lowFpsAlertShown;

		private static int _jobRunning;

		private static int _searchRunning;

		private static int _previewRunning;

		private static int _diagnosticRunning;

		private static float _realBrowserPollSeconds;

		private static DateTime _lastRealBrowserSelectionWriteUtc;

		private static bool _fusionPresenceInitialized;

		private static bool _ownerTagInitialized;

		public override void OnInitializeMelon()
		{
			_dataDirectory = Path.Combine(MelonEnvironment.UserDataDirectory, "ClipLinkMedia");
			_downloadsDirectory = Path.Combine(_dataDirectory, "Downloads");
			_settingsPath = Path.Combine(_dataDirectory, "settings.json");
			_historyPath = Path.Combine(_dataDirectory, "history.json");
			_backupsDirectory = Path.Combine(_dataDirectory, "Backups");
			_screenshotsDirectory = Path.Combine(_dataDirectory, "Screenshots");
			_realYouTubeSelectionPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ClipLinkMedia", "selected-youtube-url.txt");
			Directory.CreateDirectory(_dataDirectory);
			Directory.CreateDirectory(_downloadsDirectory);
			Directory.CreateDirectory(_backupsDirectory);
			Directory.CreateDirectory(_screenshotsDirectory);
			_ytDlpPath = ResolveYtDlpPath();
			_realYouTubeBrowserPath = ResolveRealYouTubeBrowserPath();
			_realYouTubeSelectionPath = ResolveRealYouTubeSelectionPath();
			if (File.Exists(_realYouTubeSelectionPath))
			{
				_lastRealBrowserSelectionWriteUtc = File.GetLastWriteTimeUtc(_realYouTubeSelectionPath);
			}
			LoadPersistentState();
			_draftNote = _settings.PersonalNote;
			BuildBoneMenu();
			InitializeFusionOwnerTag();
			InitializeFusionPresence();
			MelonLogger.Msg($"All-in-one v{"5.11.0"} ready with the real signed-out YouTube GUI, Fusion mod-presence detection, synced media-player spawning, and resilient video uploads. Expiry: {_settings.LitterboxRetention}; quality: {_settings.VideoQuality}; {LinkHistory.Count} saved link(s); {_settings.Favorites.Count} favorite(s); {_settings.JobQueue.Count} queued.");
			MelonLogger.Msg("Fusion OWNER tag enabled. Players with ClipLink Media installed will see OWNER above the creator's head.");
			MelonLogger.Warning("Litterbox uploads are public and temporary. Upload only videos you own or have permission to share.");
			if (!File.Exists(_ytDlpPath))
			{
				MelonLogger.Error("yt-dlp.exe is not installed. Use the BoneMenu GitHub download and folder buttons.");
			}
			if (!File.Exists(_realYouTubeBrowserPath))
			{
				MelonLogger.Warning("The real YouTube browser companion is not installed. ClipLink will not download executables; use the manual GitHub page in BoneMenu.");
			}
		}

		public override void OnUpdate()
		{
			DrainMainThreadActions();
			_fpsWindowFrames++;
			_fpsWindowSeconds += Time.unscaledDeltaTime;
			if (_fpsWindowSeconds >= 0.75f)
			{
				_measuredFps = (float)_fpsWindowFrames / _fpsWindowSeconds;
				_fpsWindowFrames = 0;
				_fpsWindowSeconds = 0f;
			}
			if (_settings.LowFpsAlertsEnabled && _measuredFps > 0f && _measuredFps < (float)_settings.LowFpsThreshold)
			{
				_lowFpsSeconds += Time.unscaledDeltaTime;
				if (_lowFpsSeconds >= 10f && !_lowFpsAlertShown)
				{
					_lowFpsAlertShown = true;
					Notify("Low FPS warning", $"FPS stayed below {_settings.LowFpsThreshold} for 10 seconds (now {_measuredFps:0.0}).", (NotificationType)1, 7f);
				}
			}
			else
			{
				_lowFpsSeconds = 0f;
				if (_measuredFps >= (float)_settings.LowFpsThreshold + 5f)
				{
					_lowFpsAlertShown = false;
				}
			}
			if (_countdownEndsUtc.HasValue && DateTimeOffset.UtcNow >= _countdownEndsUtc.Value)
			{
				string countdownLabel = _countdownLabel;
				_countdownEndsUtc = null;
				_countdownLabel = string.Empty;
				Notify("Countdown finished", countdownLabel + " timer is complete.", (NotificationType)3, 8f);
				MelonLogger.Msg("Countdown finished: " + countdownLabel);
			}
			_realBrowserPollSeconds += Time.unscaledDeltaTime;
			if (_realBrowserPollSeconds >= 0.75f)
			{
				_realBrowserPollSeconds = 0f;
				PollRealYouTubeSelection();
			}
		}

		private static void DrainMainThreadActions()
		{
			Action result;
			while (MainThreadActions.TryDequeue(out result))
			{
				try
				{
					result();
				}
				catch (Exception value)
				{
					MelonLogger.Error($"Main-thread action failed: {value}");
				}
			}
		}

		private static void InitializeFusionOwnerTag()
		{
			if (!_ownerTagInitialized)
			{
				_ownerTagInitialized = true;
				NetworkPlayer.OnNetworkPlayerRegistered += TryAttachOwnerTag;
				NetworkPlayer[] array = NetworkPlayer.Players.ToArray();
				for (int i = 0; i < array.Length; i++)
				{
					TryAttachOwnerTag(array[i]);
				}
			}
		}

		private static void TryAttachOwnerTag(NetworkPlayer player)
		{
			try
			{
				NetworkPlayer obj = player;
				if (((obj != null) ? obj.PlayerID : null) != null && player.PlayerID.IsValid && player.PlayerID.PlatformID == 76561199548494681L && !player.PlayerID.IsMe && !OwnerTags.ContainsKey(player))
				{
					OwnerTagElement ownerTagElement = new OwnerTagElement();
					OwnerTags.Add(player, ownerTagElement);
					player.HeadUI.RegisterElement((IPopupLayoutElement)(object)ownerTagElement);
					player.PlayerID.OnDestroyedEvent += delegate
					{
						RemoveOwnerTag(player);
					};
					MelonLogger.Msg($"Attached OWNER tag to Fusion player {player.Username} ({player.PlayerID.PlatformID}).");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not attach Fusion OWNER tag: " + ex.Message);
			}
		}

		private static void RemoveOwnerTag(NetworkPlayer player)
		{
			if (!OwnerTags.Remove(player, out OwnerTagElement value))
			{
				return;
			}
			try
			{
				player.HeadUI.UnregisterElement((IPopupLayoutElement)(object)value);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not remove Fusion OWNER tag: " + ex.Message);
			}
		}

		private static void InitializeFusionPresence()
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_0054: 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_005f: Expected O, but got Unknown
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			if (_fusionPresenceInitialized)
			{
				return;
			}
			_fusionPresenceInitialized = true;
			try
			{
				if (ModuleMessageManager.GetHandlerByType(typeof(ClipLinkPresenceMessage)) == null)
				{
					ModuleMessageManager.RegisterHandler<ClipLinkPresenceMessage>();
				}
				object obj = <>O.<1>__OnFusionJoinedServer;
				if (obj == null)
				{
					ServerEvent val = OnFusionJoinedServer;
					<>O.<1>__OnFusionJoinedServer = val;
					obj = (object)val;
				}
				MultiplayerHooking.OnJoinedServer += (ServerEvent)obj;
				object obj2 = <>O.<2>__OnFusionDisconnected;
				if (obj2 == null)
				{
					ServerEvent val2 = OnFusionDisconnected;
					<>O.<2>__OnFusionDisconnected = val2;
					obj2 = (object)val2;
				}
				MultiplayerHooking.OnDisconnected += (ServerEvent)obj2;
				object obj3 = <>O.<3>__OnFusionPlayerJoined;
				if (obj3 == null)
				{
					PlayerUpdate val3 = OnFusionPlayerJoined;
					<>O.<3>__OnFusionPlayerJoined = val3;
					obj3 = (object)val3;
				}
				MultiplayerHooking.OnPlayerJoined += (PlayerUpdate)obj3;
				object obj4 = <>O.<4>__OnFusionPlayerLeft;
				if (obj4 == null)
				{
					PlayerUpdate val4 = OnFusionPlayerLeft;
					<>O.<4>__OnFusionPlayerLeft = val4;
					obj4 = (object)val4;
				}
				MultiplayerHooking.OnPlayerLeft += (PlayerUpdate)obj4;
				if (NetworkInfo.HasServer)
				{
					OnFusionJoinedServer();
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not initialize Fusion ClipLink presence: " + ex.Message);
			}
		}

		private static void OnFusionJoinedServer()
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			lock (PresenceGate)
			{
				ClipLinkUsers.Clear();
				if (PlayerIDManager.LocalID != null)
				{
					ClipLinkUsers[PlayerIDManager.LocalSmallID] = new ClipLinkPresenceInfo("5.11.0", DateTimeOffset.UtcNow);
				}
			}
			SendClipLinkPresence(CommonMessageRoutes.ReliableToOtherClients, requestReply: true);
			MelonLogger.Msg("Announced ClipLink presence to the current Fusion lobby.");
		}

		private static void OnFusionDisconnected()
		{
			lock (PresenceGate)
			{
				ClipLinkUsers.Clear();
			}
		}

		private static void OnFusionPlayerJoined(PlayerID playerId)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			if (playerId != null && !playerId.IsMe)
			{
				SendClipLinkPresence(new MessageRoute(playerId.SmallID, (NetworkChannel)0), requestReply: true);
			}
		}

		private static void OnFusionPlayerLeft(PlayerID playerId)
		{
			if (playerId == null)
			{
				return;
			}
			lock (PresenceGate)
			{
				ClipLinkUsers.Remove(playerId.SmallID);
			}
		}

		private static void SendClipLinkPresence(MessageRoute route, bool requestReply)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkInfo.HasServer)
			{
				return;
			}
			try
			{
				MessageRelay.RelayModule<ClipLinkPresenceMessage, ClipLinkPresenceData>(new ClipLinkPresenceData("5.11.0", requestReply), route);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not send ClipLink presence: " + ex.Message);
			}
		}

		internal static void ReceiveClipLinkPresence(ReceivedMessage received)
		{
			//IL_013f: Unknown result type (might be due to invalid IL or missing references)
			if (!((ReceivedMessage)(ref received)).Sender.HasValue)
			{
				return;
			}
			ClipLinkPresenceData data = ((ReceivedMessage)(ref received)).ReadData<ClipLinkPresenceData>();
			byte value = ((ReceivedMessage)(ref received)).Sender.Value;
			bool flag;
			lock (PresenceGate)
			{
				flag = !ClipLinkUsers.ContainsKey(value);
				ClipLinkUsers[value] = new ClipLinkPresenceInfo(string.IsNullOrWhiteSpace(data.Version) ? "unknown" : data.Version, DateTimeOffset.UtcNow);
			}
			string playerName = GetFusionPlayerName(value);
			MelonLogger.Msg($"ClipLink presence received from {playerName} (Fusion ID {value}, v{data.Version}).");
			if (flag)
			{
				MainThreadActions.Enqueue(delegate
				{
					Notify("ClipLink user detected", playerName + " is using ClipLink Media v" + data.Version + ".", (NotificationType)3, 5f);
				});
			}
			if (data.RequestReply)
			{
				SendClipLinkPresence(new MessageRoute(value, (NetworkChannel)0), requestReply: false);
			}
		}

		private static string GetFusionPlayerName(byte smallId)
		{
			NetworkPlayer val = default(NetworkPlayer);
			if (!NetworkPlayerManager.TryGetPlayer(smallId, ref val) || string.IsNullOrWhiteSpace(val.Username))
			{
				return $"Fusion player {smallId}";
			}
			return val.Username;
		}

		private static void ApplyYouTubeTheme(Page page, bool videoGrid = false)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			EnsureYouTubeTextures();
			page.Color = new Color(1f, 0f, 0f);
			page.Background = _youtubeBackground;
			page.BackgroundOpacity = 0.96f;
			page.Logo = _youtubeLogo;
			page.ElementSpacing = (videoGrid ? 108f : 62f);
		}

		private static void EnsureYouTubeTextures()
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00af: 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_008f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_01af: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: 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)
			if ((Object)(object)_youtubeBackground == (Object)null)
			{
				_youtubeBackground = new Texture2D(64, 64, (TextureFormat)4, false)
				{
					name = "ClipLink YouTube Background",
					filterMode = (FilterMode)1,
					wrapMode = (TextureWrapMode)1
				};
				for (int i = 0; i < 64; i++)
				{
					for (int j = 0; j < 64; j++)
					{
						bool num = i >= 56;
						bool flag = !num && i / 8 % 2 == 0;
						Color val = (num ? new Color(0.85f, 0.02f, 0.02f, 1f) : (flag ? new Color(0.055f, 0.055f, 0.06f, 1f) : new Color(0.035f, 0.035f, 0.04f, 1f)));
						_youtubeBackground.SetPixel(j, i, val);
					}
				}
				_youtubeBackground.Apply(false, false);
			}
			if (!((Object)(object)_youtubeLogo == (Object)null))
			{
				return;
			}
			_youtubeLogo = new Texture2D(96, 64, (TextureFormat)4, false)
			{
				name = "ClipLink YouTube Logo",
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1
			};
			for (int k = 0; k < 64; k++)
			{
				for (int l = 0; l < 96; l++)
				{
					bool flag2 = l >= 4 && l <= 91 && k >= 7 && k <= 56;
					Color val2 = (Color)((l >= 38 && l <= 70 && (float)Math.Abs(k - 32) <= (float)(l - 38) * 0.72f) ? Color.white : (flag2 ? new Color(1f, 0f, 0f, 1f) : new Color(0f, 0f, 0f, 0f)));
					_youtubeLogo.SetPixel(l, k, val2);
				}
			}
			_youtubeLogo.Apply(false, false);
		}

		private static void SetYouTubeSearchAndRun(string query)
		{
			_searchQuery = query;
			SearchYouTube();
		}

		private static void BuildBoneMenu()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			PopulateBoneMenu(Page.Root.CreatePage("ClipLink Media", Color.cyan, 0, true));
		}

		private static void PopulateBoneMenu(Page page)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0173: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0221: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_026a: 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_02ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0329: Unknown result type (might be due to invalid IL or missing references)
			//IL_0359: Unknown result type (might be due to invalid IL or missing references)
			//IL_037f: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_03f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0420: Unknown result type (might be due to invalid IL or missing references)
			//IL_044c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0478: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0500: Unknown result type (might be due to invalid IL or missing references)
			//IL_052c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0558: Unknown result type (might be due to invalid IL or missing references)
			//IL_0584: Unknown result type (might be due to invalid IL or missing references)
			//IL_05b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0618: Unknown result type (might be due to invalid IL or missing references)
			//IL_062a: Unknown result type (might be due to invalid IL or missing references)
			//IL_065a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0686: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0716: Unknown result type (might be due to invalid IL or missing references)
			//IL_0746: Unknown result type (might be due to invalid IL or missing references)
			//IL_0775: Unknown result type (might be due to invalid IL or missing references)
			//IL_07a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_07e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0811: Unknown result type (might be due to invalid IL or missing references)
			//IL_083d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0868: Unknown result type (might be due to invalid IL or missing references)
			//IL_0894: Unknown result type (might be due to invalid IL or missing references)
			//IL_08a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_08d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0902: Unknown result type (might be due to invalid IL or missing references)
			//IL_0932: Unknown result type (might be due to invalid IL or missing references)
			//IL_0961: Unknown result type (might be due to invalid IL or missing references)
			//IL_0991: Unknown result type (might be due to invalid IL or missing references)
			//IL_09a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_09cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_09ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a2f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a5e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a8e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aaa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ad6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b02: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b14: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b40: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b6c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b98: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bc4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bf0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c1c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c48: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c74: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ca0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ccc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cf7: Unknown result type (might be due to invalid IL or missing references)
			Page obj = page.CreatePage("REAL YouTube website", new Color(1f, 0f, 0f), 0, true);
			obj.CreateFunction("Open REAL YouTube GUI", Color.red, (Action)OpenRealYouTubeBrowser);
			obj.CreateFunction("Copy last clicked video", Color.white, (Action)CopyLastRealYouTubeSelection);
			obj.CreateFunction("Use clicked video + spawn player", Color.cyan, (Action)SpawnMediaPlayerWithRealYouTubeSelection);
			obj.CreateFunction("Check browser install", Color.green, (Action)CheckRealYouTubeBrowser);
			obj.CreateFunction("Open manual GitHub download", Color.yellow, (Action)OpenRealYouTubeBrowserDownloadPage);
			obj.CreateFunction("Open browser install folder", Color.yellow, (Action)OpenRealYouTubeBrowserFolder);
			obj.CreateFunction("Open regular browser fallback", Color.gray, (Action)OpenYouTube);
			Page obj2 = page.CreatePage("Thumbnail browser fallback", Color.gray, 0, true);
			ApplyYouTubeTheme(obj2);
			FunctionElement obj3 = obj2.CreateFunction("Native VR fallback", Color.white, (Action)delegate
			{
				Notify("YouTube", "This is the native VR fallback. Use REAL YouTube website for YouTube's actual GUI.", (NotificationType)0, 7f);
			});
			obj3.Logo = _youtubeLogo;
			((Element)obj3).SetTooltip("Fallback thumbnail search. The REAL YouTube website page launches YouTube's actual GUI.");
			((Element)obj2.CreateString("Search", Color.white, _searchQuery, (Action<string>)delegate(string value)
			{
				_searchQuery = value.Trim();
			})).SetTooltip("Select the keyboard button, type a search, and press Enter.");
			obj2.CreateFunction("Search YouTube", Color.red, (Action)SearchYouTube);
			Page obj4 = obj2.CreatePage("Explore", Color.red, 0, true);
			ApplyYouTubeTheme(obj4);
			obj4.CreateFunction("BONELAB", Color.white, (Action)delegate
			{
				SetYouTubeSearchAndRun("BONELAB VR gameplay");
			});
			obj4.CreateFunction("VR gaming", Color.white, (Action)delegate
			{
				SetYouTubeSearchAndRun("VR gaming");
			});
			obj4.CreateFunction("Gaming", Color.white, (Action)delegate
			{
				SetYouTubeSearchAndRun("gaming");
			});
			obj4.CreateFunction("Music", Color.white, (Action)delegate
			{
				SetYouTubeSearchAndRun("music");
			});
			obj4.CreateFunction("Trending videos", Color.white, (Action)delegate
			{
				SetYouTubeSearchAndRun("trending videos");
			});
			obj4.CreateFunction("Media Player tutorials", Color.white, (Action)delegate
			{
				SetYouTubeSearchAndRun("BONELAB Media Player mod tutorial");
			});
			_searchResultsPage = obj2.CreatePage("Videos", Color.red, 0, true);
			ApplyYouTubeTheme(_searchResultsPage, videoGrid: true);
			_searchResultsPage.CreateFunction("Search first", Color.gray, (Action)SearchYouTube);
			_recentVideosPage = obj2.CreatePage("Recently selected videos", Color.cyan, 0, true);
			ApplyYouTubeTheme(_recentVideosPage, videoGrid: true);
			RefreshRecentVideosPage();
			_searchHistoryPage = obj2.CreatePage("Recent searches", Color.yellow, 0, true);
			ApplyYouTubeTheme(_searchHistoryPage);
			RefreshSearchHistoryPage();
			_favoritesPage = obj2.CreatePage("Favorite videos", Color.magenta, 0, true);
			ApplyYouTubeTheme(_favoritesPage, videoGrid: true);
			RefreshFavoritesPage();
			obj2.CreateFunction("Add copied video to favorites", Color.magenta, (Action)AddCopiedFavorite);
			obj2.CreateFunction("Preview copied video", Color.green, (Action)PreviewCopiedVideo);
			obj2.CreateFunction("Copy last preview info", Color.white, (Action)CopyLastPreview);
			obj2.CreateFunction("Open REAL YouTube GUI", Color.red, (Action)OpenRealYouTubeBrowser);
			page.CreateBool("I own / have permission", Color.yellow, false, (Action<bool>)delegate(bool value)
			{
				_rightsConfirmed = value;
			});
			page.CreateFunction("Make public MP4 URL", Color.green, (Action)StartClipboardJob);
			page.CreateFunction("Download MP4 only", Color.green, (Action)StartLocalDownloadJob);
			page.CreateFunction("Retry saved failed upload", Color.yellow, (Action)RetryLastFailedUpload);
			page.CreateFunction("Cancel current job", Color.red, (Action)CancelCurrentJob);
			_queuePage = page.CreatePage("Batch video queue", Color.yellow, 0, true);
			RefreshQueuePage();
			_downloadsPage = page.CreatePage("Downloaded MP4 library", Color.cyan, 0, true);
			RefreshDownloadsPage();
			page.CreateFunction("Spawn media player", Color.blue, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.MediaPlayer", "Media Player");
			});
			Page obj5 = page.CreatePage("Media player spawner", Color.blue, 0, true);
			obj5.CreateFunction("Spawn media player", Color.green, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.MediaPlayer", "Media Player");
			});
			obj5.CreateFunction("Use last MP4 URL + spawn", Color.cyan, (Action)SpawnMediaPlayerWithLastUrl);
			obj5.CreateFunction("Spawn flatscreen TV", Color.cyan, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.FlatscreenMediaPlayer", "Flatscreen Media Player");
			});
			obj5.CreateFunction("Spawn CRT TV", Color.yellow, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.CRTTV", "CRT TV");
			});
			obj5.CreateFunction("Spawn phone player", Color.magenta, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.PhoneMediaPlayer", "Phone Media Player");
			});
			obj5.CreateFunction("Spawn computer monitor", Color.white, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.ComputerMonitor", "Computer Monitor");
			});
			obj5.CreateFunction("Spawn boom box", Color.green, (Action)delegate
			{
				SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.BoomBox", "Boom Box");
			});
			obj5.CreateFunction("Check media-player setup", Color.white, (Action)CheckMediaPlayerSetup);
			BuildUtilityToolbox(page);
			Page obj6 = page.CreatePage("Repeat last video", Color.green, 0, true);
			obj6.CreateFunction("Make public URL again", Color.green, (Action)StartLastPublicJob);
			obj6.CreateFunction("Download MP4 again", Color.cyan, (Action)StartLastLocalJob);
			obj6.CreateFunction("Copy last YouTube link", Color.white, (Action)CopyLastSourceUrl);
			obj6.CreateFunction("Open last YouTube video", Color.red, (Action)OpenLastSourceUrl);
			obj6.CreateFunction("Copy MP4 URL + spawn player", Color.blue, (Action)SpawnMediaPlayerWithLastUrl);
			Page obj7 = page.CreatePage("MP4 quality", Color.magenta, 0, true);
			obj7.CreateFunction("Show current quality", Color.white, (Action)ShowCurrentQuality);
			obj7.CreateFunction("Use best MP4", Color.cyan, (Action)delegate
			{
				SetVideoQuality("Best");
			});
			obj7.CreateFunction("Use 720p", Color.cyan, (Action)delegate
			{
				SetVideoQuality("720p");
			});
			obj7.CreateFunction("Use 480p", Color.cyan, (Action)delegate
			{
				SetVideoQuality("480p");
			});
			obj7.CreateFunction("Use 360p", Color.cyan, (Action)delegate
			{
				SetVideoQuality("360p");
			});
			Page obj8 = page.CreatePage("Public-link expiry", Color.yellow, 0, true);
			obj8.CreateFunction("Show current expiry", Color.white, (Action)ShowCurrentRetention);
			obj8.CreateFunction("Use 1 hour", Color.cyan, (Action)delegate
			{
				SetRetention("1h");
			});
			obj8.CreateFunction("Use 12 hours", Color.cyan, (Action)delegate
			{
				SetRetention("12h");
			});
			obj8.CreateFunction("Use 24 hours", Color.cyan, (Action)delegate
			{
				SetRetention("24h");
			});
			obj8.CreateFunction("Use 72 hours", Color.cyan, (Action)delegate
			{
				SetRetention("72h");
			});
			_historyPage = page.CreatePage("Recent public URLs", Color.cyan, 0, true);
			RefreshHistoryPage();
			page.CreateFunction("Copy last public URL", Color.cyan, (Action)CopyLastPublicUrl);
			page.CreateFunction("Open last public URL", Color.cyan, (Action)OpenLastPublicUrl);
			Page obj9 = page.CreatePage("Setup and folders", Color.gray, 0, true);
			obj9.CreateFunction("Check setup", Color.green, (Action)CheckSetup);
			obj9.CreateFunction("Open REAL YouTube GUI", Color.red, (Action)OpenRealYouTubeBrowser);
			obj9.CreateFunction("Browser download page", Color.yellow, (Action)OpenRealYouTubeBrowserDownloadPage);
			obj9.CreateFunction("Get yt-dlp from GitHub", Color.yellow, (Action)OpenYtDlpDownload);
			obj9.CreateFunction("Open ClipLink folder", Color.yellow, (Action)OpenYtDlpFolder);
			obj9.CreateFunction("Open downloads folder", Color.cyan, (Action)OpenDownloadsFolder);
			obj9.CreateFunction("Copy downloads path", Color.white, (Action)CopyDownloadsPath);
			obj9.CreateFunction("Inspect copied URL", Color.white, (Action)InspectCopiedUrl);
			obj9.CreateFunction("Open copied YouTube URL", Color.red, (Action)OpenCopiedYouTubeUrl);
			obj9.CreateFunction("Show usage statistics", Color.magenta, (Action)ShowStatistics);
			obj9.CreateFunction("Show job and queue status", Color.yellow, (Action)ShowJobStatus);
			obj9.CreateFunction("Copy setup report", Color.green, (Action)CopySetupReport);
		}

		private static void SpawnMediaPlayerWithLastUrl()
		{
			if (string.IsNullOrWhiteSpace(_lastPublicUrl))
			{
				Warn("Create a public MP4 URL first, then use this button.");
				return;
			}
			GUIUtility.systemCopyBuffer = _lastPublicUrl;
			SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.MediaPlayer", "Media Player");
		}

		private static void SpawnMediaPlayer(string barcode, string displayName)
		{
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: 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_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: 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)
			string mediaPlayerPalletPath = GetMediaPlayerPalletPath();
			if (!File.Exists(mediaPlayerPalletPath))
			{
				Warn("The Elijoe Media Player content mod is not installed or enabled.");
				MelonLogger.Warning("Media Player pallet was not found at " + mediaPlayerPalletPath);
				return;
			}
			Transform head = Player.Head;
			if ((Object)(object)head == (Object)null)
			{
				Warn("The player rig is not ready. Enter a level and try again.");
				return;
			}
			Vector3 val = Vector3.ProjectOnPlane(head.forward, Vector3.up);
			if (((Vector3)(ref val)).sqrMagnitude < 0.001f)
			{
				val = head.forward;
			}
			((Vector3)(ref val)).Normalize();
			Vector3 position = head.position + val * 1.75f - Vector3.up * 1.15f;
			Quaternion rotation = Quaternion.LookRotation(-val, Vector3.up);
			try
			{
				if (NetworkInfo.HasServer)
				{
					SpawnFusionMediaPlayer(barcode, displayName, position, rotation);
				}
				else
				{
					SpawnLocalMediaPlayer(barcode, displayName, position, rotation);
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Error($"Could not spawn {displayName}: {ex}");
				Warn("Could not spawn " + displayName + ": " + LastPart(ex.Message, 100));
			}
		}

		private static void SpawnFusionMediaPlayer(string barcode, string displayName, Vector3 position, Quaternion rotation)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: 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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			Spawnable spawnable = LocalAssetSpawner.CreateSpawnable(barcode);
			NetworkAssetSpawner.Spawn(new SpawnRequestInfo
			{
				Spawnable = spawnable,
				Position = position,
				Rotation = rotation,
				SpawnEffect = true,
				SpawnSource = (EntitySource)2,
				SpawnCallback = delegate(SpawnCallbackInfo info)
				{
					//IL_0000: Unknown result type (might be due to invalid IL or missing references)
					//IL_0029: Unknown result type (might be due to invalid IL or missing references)
					//IL_0031: Unknown result type (might be due to invalid IL or missing references)
					if ((Object)(object)info.Spawned == (Object)null)
					{
						Warn("Fusion did not return a spawned " + displayName + ".");
					}
					else
					{
						string value = ((info.Entity == null) ? "unknown" : info.Entity.ID.ToString());
						MelonLogger.Msg($"Fusion network spawn completed for {displayName}; entity {value}.");
						Notify("Fusion media player synced", displayName + " spawned for the lobby. Grab it and press B to use the copied direct MP4 URL.", (NotificationType)3, 8f);
					}
				}
			});
			MelonLogger.Msg($"Requested Fusion network spawn for {displayName} with barcode {barcode} at {position}.");
			Notify("Fusion spawn requested", "Syncing " + displayName + " to the lobby...", (NotificationType)0, 4f);
		}

		private static void SpawnLocalMediaPlayer(string barcode, string displayName, Vector3 position, Quaternion rotation)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: 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)
			HelperMethods.SpawnCrate(barcode, position, rotation, Vector3.one, false, (Action<GameObject>)delegate(GameObject spawned)
			{
				if (!((Object)(object)spawned == (Object)null))
				{
					Notify("Media player spawned", displayName + " is in front of you. Grab it and press B to use the copied direct MP4 URL.", (NotificationType)3, 7f);
				}
			}, (Action<GameObject>)null);
			MelonLogger.Msg($"Requested local {displayName} spawn with barcode {barcode} at {position}.");
		}

		private static void CheckMediaPlayerSetup()
		{
			if (!File.Exists(GetMediaPlayerPalletPath()))
			{
				Warn("Elijoe Media Player is missing or disabled in BONELAB's content mods.");
				return;
			}
			string text = (NetworkInfo.HasServer ? "Fusion network spawning is active. Lobby players need the Media Player content pack." : "Single-player local spawning is active.");
			Notify("Media Player ready", "The Elijoe Media Player pallet is installed. " + text, (NotificationType)3, 8f);
		}

		private static string GetMediaPlayerPalletPath()
		{
			return Path.Combine(Application.persistentDataPath, "Mods", "Elijoe.MediaPlayer", "Elijoe.MediaPlayer.pallet.json");
		}

		private static void BuildUtilityToolbox(Page rootPage)
		{
			//IL_0006: 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_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: 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_0105: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0223: Unknown result type (might be due to invalid IL or missing references)
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Unknown result type (might be due to invalid IL or missing references)
			//IL_030a: Unknown result type (might be due to invalid IL or missing references)
			//IL_031c: Unknown result type (might be due to invalid IL or missing references)
			//IL_035a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0386: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_03dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_041b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0451: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0511: Unknown result type (might be due to invalid IL or missing references)
			//IL_0541: Unknown result type (might be due to invalid IL or missing references)
			//IL_0571: Unknown result type (might be due to invalid IL or missing references)
			//IL_05a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0601: Unknown result type (might be due to invalid IL or missing references)
			//IL_0631: Unknown result type (might be due to invalid IL or missing references)
			//IL_0661: Unknown result type (might be due to invalid IL or missing references)
			//IL_0691: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0702: Unknown result type (might be due to invalid IL or missing references)
			//IL_072e: Unknown result type (might be due to invalid IL or missing references)
			//IL_075a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0786: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_07dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_07ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_081b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0847: Unknown result type (might be due to invalid IL or missing references)
			//IL_0873: Unknown result type (might be due to invalid IL or missing references)
			//IL_089f: Unknown result type (might be due to invalid IL or missing references)
			//IL_08cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_08f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0922: Unknown result type (might be due to invalid IL or missing references)
			//IL_094e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0960: Unknown result type (might be due to invalid IL or missing references)
			//IL_098c: Unknown result type (might be due to invalid IL or missing references)
			//IL_09b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_09e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_09f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a21: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a4d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0a79: Unknown result type (might be due to invalid IL or missing references)
			//IL_0aa5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0ad0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0afb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b0d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b39: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b65: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b91: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bbd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0be9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c19: Unknown result type (might be due to invalid IL or missing references)
			Page obj = rootPage.CreatePage("BONELAB utility toolbox", Color.magenta, 0, true);
			Page obj2 = obj.CreatePage("Session dashboard", Color.cyan, 0, true);
			obj2.CreateFunction("Show clock", Color.white, (Action)ShowClock);
			obj2.CreateFunction("Show scene and uptime", Color.green, (Action)ShowSceneAndUptime);
			obj2.CreateFunction("Show measured FPS", Color.yellow, (Action)ShowMeasuredFps);
			obj2.CreateFunction("Copy headset position", Color.cyan, (Action)CopyHeadsetPosition);
			obj2.CreateFunction("Copy session report", Color.green, (Action)CopySessionReport);
			obj2.CreateFunction("Copy device report", Color.magenta, (Action)CopyDeviceReport);
			Page obj3 = obj.CreatePage("Stopwatch and timers", Color.yellow, 0, true);
			obj3.CreateFunction("Start / resume stopwatch", Color.green, (Action)StartUtilityStopwatch);
			obj3.CreateFunction("Pause stopwatch", Color.yellow, (Action)PauseUtilityStopwatch);
			obj3.CreateFunction("Show stopwatch", Color.white, (Action)ShowUtilityStopwatch);
			obj3.CreateFunction("Reset stopwatch", Color.red, (Action)ResetUtilityStopwatch);
			obj3.CreateFunction("Start 1-minute timer", Color.cyan, (Action)delegate
			{
				StartCountdown(1);
			});
			obj3.CreateFunction("Start 5-minute timer", Color.cyan, (Action)delegate
			{
				StartCountdown(5);
			});
			obj3.CreateFunction("Start 10-minute timer", Color.cyan, (Action)delegate
			{
				StartCountdown(10);
			});
			obj3.CreateFunction("Start 15-minute timer", Color.cyan, (Action)delegate
			{
				StartCountdown(15);
			});
			obj3.CreateFunction("Show countdown", Color.white, (Action)ShowCountdown);
			obj3.CreateFunction("Cancel countdown", Color.red, (Action)CancelCountdown);
			Page obj4 = obj.CreatePage("Saved support notes", Color.green, 0, true);
			((Element)obj4.CreateString("Personal note", Color.white, _draftNote, (Action<string>)delegate(string value)
			{
				_draftNote = value;
			})).SetTooltip("Save reproduction steps or troubleshooting notes across launches.");
			obj4.CreateFunction("Save note", Color.green, (Action)SavePersonalNote);
			obj4.CreateFunction("Copy saved note", Color.cyan, (Action)CopyPersonalNote);
			obj4.CreateFunction("Clear saved note", Color.red, (Action)ClearPersonalNote);
			Page obj5 = obj.CreatePage("Local audio and FPS", Color.blue, 0, true);
			obj5.CreateFunction("Show audio and FPS", Color.white, (Action)ShowLocalSettings);
			obj5.CreateBool("Low-FPS warnings", Color.yellow, _settings.LowFpsAlertsEnabled, (Action<bool>)SetLowFpsAlerts);
			obj5.CreateFunction("Warn below 45 FPS", Color.yellow, (Action)delegate
			{
				SetLowFpsThreshold(45);
			});
			obj5.CreateFunction("Warn below 60 FPS", Color.yellow, (Action)delegate
			{
				SetLowFpsThreshold(60);
			});
			obj5.CreateFunction("Warn below 72 FPS", Color.yellow, (Action)delegate
			{
				SetLowFpsThreshold(72);
			});
			obj5.CreateFunction("Mute local audio", Color.red, (Action)delegate
			{
				SetAudioVolume(0f);
			});
			obj5.CreateFunction("Audio 25%", Color.yellow, (Action)delegate
			{
				SetAudioVolume(0.25f);
			});
			obj5.CreateFunction("Audio 50%", Color.yellow, (Action)delegate
			{
				SetAudioVolume(0.5f);
			});
			obj5.CreateFunction("Audio 75%", Color.green, (Action)delegate
			{
				SetAudioVolume(0.75f);
			});
			obj5.CreateFunction("Audio 100%", Color.green, (Action)delegate
			{
				SetAudioVolume(1f);
			});
			obj5.CreateFunction("Target 72 FPS", Color.cyan, (Action)delegate
			{
				SetTargetFrameRate(72);
			});
			obj5.CreateFunction("Target 90 FPS", Color.cyan, (Action)delegate
			{
				SetTargetFrameRate(90);
			});
			obj5.CreateFunction("Target 120 FPS", Color.cyan, (Action)delegate
			{
				SetTargetFrameRate(120);
			});
			obj5.CreateFunction("Unlimited target FPS", Color.magenta, (Action)delegate
			{
				SetTargetFrameRate(-1);
			});
			obj5.CreateFunction("VSync off", Color.yellow, (Action)delegate
			{
				SetVSync(enabled: false);
			});
			obj5.CreateFunction("VSync on", Color.green, (Action)delegate
			{
				SetVSync(enabled: true);
			});
			Page obj6 = obj.CreatePage("Clipboard helpers", Color.gray, 0, true);
			obj6.CreateFunction("Copy local timestamp", Color.white, (Action)CopyLocalTimestamp);
			obj6.CreateFunction("Copy UTC timestamp", Color.white, (Action)CopyUtcTimestamp);
			obj6.CreateFunction("Copy current scene", Color.cyan, (Action)CopyCurrentScene);
			obj6.CreateFunction("Copy headset position", Color.cyan, (Action)CopyHeadsetPosition);
			obj6.CreateFunction("Copy session report", Color.green, (Action)CopySessionReport);
			Page obj7 = obj.CreatePage("Mod health and support", Color.green, 0, true);
			obj7.CreateFunction("Run mod health check", Color.green, (Action)RunModHealthCheck);
			obj7.CreateFunction("Copy installed mod list", Color.cyan, (Action)CopyInstalledModList);
			obj7.CreateFunction("Copy recent log errors", Color.yellow, (Action)CopyRecentLogErrors);
			obj7.CreateFunction("Copy complete support report", Color.magenta, (Action)CopyCompleteSupportReport);
			obj7.CreateFunction("Open Mods folder", Color.white, (Action)OpenModsFolder);
			obj7.CreateFunction("Open MelonLoader folder", Color.white, (Action)OpenMelonLoaderFolder);
			obj7.CreateFunction("Open UserData folder", Color.white, (Action)OpenUserDataFolder);
			obj7.CreateFunction("Open BONELAB folder", Color.white, (Action)OpenGameFolder);
			Page obj8 = obj.CreatePage("Connectivity and updates", Color.cyan, 0, true);
			obj8.CreateFunction("Test YouTube/Litterbox/GitHub", Color.green, (Action)StartConnectivityTest);
			obj8.CreateFunction("Check ClipLink update", Color.cyan, (Action)CheckForClipLinkUpdate);
			obj8.CreateFunction("Open latest release", Color.white, (Action)OpenLatestRelease);
			Page obj9 = obj.CreatePage("Fusion session diagnostics", Color.magenta, 0, true);
			obj9.CreateFunction("Show Fusion player count", Color.white, (Action)ShowFusionPlayerCount);
			obj9.CreateFunction("Show ClipLink users", Color.green, (Action)ShowClipLinkUsers);
			obj9.CreateFunction("Copy ClipLink user list", Color.cyan, (Action)CopyClipLinkUserList);
			obj9.CreateFunction("Refresh ClipLink detection", Color.yellow, (Action)RefreshClipLinkDetection);
			obj9.CreateFunction("Copy Fusion player list", Color.cyan, (Action)CopyFusionPlayerList);
			obj9.CreateFunction("Copy Fusion session report", Color.green, (Action)CopyFusionSessionReport);
			Page obj10 = obj.CreatePage("Maintenance and backups", Color.yellow, 0, true);
			obj10.CreateFunction("Show disk space", Color.white, (Action)ShowDiskSpace);
			obj10.CreateFunction("Take BONELAB screenshot", Color.cyan, (Action)TakeScreenshot);
			obj10.CreateFunction("Open screenshots folder", Color.white, (Action)OpenScreenshotsFolder);
			obj10.CreateFunction("Backup ClipLink data", Color.green, (Action)BackupClipLinkData);
			obj10.CreateFunction("Open backups folder", Color.white, (Action)OpenBackupsFolder);
			obj10.CreateBool("Confirm old-temp cleanup", Color.red, false, (Action<bool>)delegate(bool value)
			{
				_cleanupConfirmed = value;
			});
			obj10.CreateFunction("Clean old ClipLink temp jobs", Color.red, (Action)CleanOldClipLinkTemps);
		}

		private static void ShowClock()
		{
			Notify("Current time", $"Local: {DateTime.Now:F} | UTC: {DateTime.UtcNow:u}", (NotificationType)0, 7f);
		}

		private static void ShowSceneAndUptime()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			string value = FormatDuration(TimeSpan.FromSeconds(Math.Max(0f, Time.realtimeSinceStartup)));
			Notify("BONELAB session", $"Scene: {name}; game uptime: {value}.", (NotificationType)0, 6f);
		}

		private static void ShowMeasuredFps()
		{
			string value = ((Application.targetFrameRate < 0) ? "unlimited" : Application.targetFrameRate.ToString());
			Notify("Local FPS", $"Measured: {_measuredFps:0.0} FPS; target: {value}; VSync: {((QualitySettings.vSyncCount > 0) ? "on" : "off")}.", (NotificationType)0, 6f);
		}

		private static bool TryGetHeadsetPosition(out Vector3 position)
		{
			//IL_0023: 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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			Camera main = Camera.main;
			if ((Object)(object)main == (Object)null)
			{
				position = Vector3.zero;
				return false;
			}
			position = ((Component)main).transform.position;
			return true;
		}

		private static string PositionText(Vector3 position)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			return $"X {position.x:0.000}, Y {position.y:0.000}, Z {position.z:0.000}";
		}

		private static void CopyHeadsetPosition()
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			if (!TryGetHeadsetPosition(out var position))
			{
				Warn("The active headset camera is not ready yet.");
				return;
			}
			string message = (GUIUtility.systemCopyBuffer = PositionText(position));
			Notify("Headset position copied", message, (NotificationType)3, 5f);
		}

		private static void CopySessionReport()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: 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)
			Vector3 position;
			string text = (TryGetHeadsetPosition(out position) ? PositionText(position) : "Unavailable");
			string text2 = ((Application.targetFrameRate < 0) ? "Unlimited" : Application.targetFrameRate.ToString());
			string newLine = Environment.NewLine;
			string[] obj = new string[11]
			{
				"BONELAB local session report",
				$"Local time: {DateTime.Now:F}",
				null,
				null,
				null,
				null,
				null,
				null,
				null,
				null,
				null
			};
			Scene activeScene = SceneManager.GetActiveScene();
			obj[2] = "Scene: " + ((Scene)(ref activeScene)).name;
			obj[3] = "Game uptime: " + FormatDuration(TimeSpan.FromSeconds(Math.Max(0f, Time.realtimeSinceStartup)));
			obj[4] = "Headset position: " + text;
			obj[5] = $"Measured FPS: {_measuredFps:0.0}";
			obj[6] = "Target FPS: " + text2;
			obj[7] = "VSync: " + ((QualitySettings.vSyncCount > 0) ? "On" : "Off");
			obj[8] = $"Local audio: {AudioListener.volume * 100f:0}%";
			obj[9] = "Unity: " + Application.unityVersion;
			obj[10] = "ClipLink Media: 5.11.0";
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = string.Join(newLine, obj));
			Notify("Session report", "Local BONELAB session report copied.", (NotificationType)3, 5f);
		}

		private static void CopyDeviceReport()
		{
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = string.Join(Environment.NewLine, "BONELAB device report", "Device: " + SystemInfo.deviceModel, "Operating system: " + SystemInfo.operatingSystem, $"Processor: {SystemInfo.processorType} ({SystemInfo.processorCount} logical cores)", $"System memory: {SystemInfo.systemMemorySize} MB", "Graphics: " + SystemInfo.graphicsDeviceName, $"Graphics memory: {SystemInfo.graphicsMemorySize} MB", $"Graphics API: {SystemInfo.graphicsDeviceType}", "Unity: " + Application.unityVersion));
			Notify("Device report", "Local device and graphics report copied.", (NotificationType)3, 5f);
		}

		private static void StartUtilityStopwatch()
		{
			UtilityStopwatch.Start();
			Notify("Stopwatch", "Running from " + FormatDuration(UtilityStopwatch.Elapsed) + ".", (NotificationType)3, 3f);
		}

		private static void PauseUtilityStopwatch()
		{
			UtilityStopwatch.Stop();
			Notify("Stopwatch paused", FormatDuration(UtilityStopwatch.Elapsed), (NotificationType)0, 4f);
		}

		private static void ShowUtilityStopwatch()
		{
			Notify("Stopwatch", FormatDuration(UtilityStopwatch.Elapsed) + " (" + (UtilityStopwatch.IsRunning ? "running" : "paused") + ").", (NotificationType)0, 4f);
		}

		private static void ResetUtilityStopwatch()
		{
			UtilityStopwatch.Reset();
			Notify("Stopwatch", "Reset to zero.", (NotificationType)3, 3f);
		}

		private static void StartCountdown(int minutes)
		{
			_countdownEndsUtc = DateTimeOffset.UtcNow.AddMinutes(minutes);
			_countdownLabel = $"{minutes}-minute";
			Notify("Countdown started", $"{minutes} minute(s); ends at {_countdownEndsUtc.Value.LocalDateTime:T}.", (NotificationType)3, 5f);
		}

		private static void ShowCountdown()
		{
			if (!_countdownEndsUtc.HasValue)
			{
				Warn("No countdown is running.");
				return;
			}
			TimeSpan duration = _countdownEndsUtc.Value - DateTimeOffset.UtcNow;
			Notify("Countdown", $"{FormatDuration(duration)} remaining; ends at {_countdownEndsUtc.Value.LocalDateTime:T}.", (NotificationType)0, 5f);
		}

		private static void CancelCountdown()
		{
			_countdownEndsUtc = null;
			_countdownLabel = string.Empty;
			Notify("Countdown", "Countdown cancelled.", (NotificationType)3, 3f);
		}

		private static void SavePersonalNote()
		{
			_settings.PersonalNote = _draftNote.Trim();
			SaveSettings();
			Notify("Personal note", "Note saved for future game launches.", (NotificationType)3, 4f);
		}

		private static void CopyPersonalNote()
		{
			if (string.IsNullOrWhiteSpace(_settings.PersonalNote))
			{
				Warn("No personal note is saved.");
				return;
			}
			GUIUtility.systemCopyBuffer = _settings.PersonalNote;
			Notify("Personal note", "Saved note copied.", (NotificationType)3, 3f);
		}

		private static void ClearPersonalNote()
		{
			_draftNote = string.Empty;
			_settings.PersonalNote = string.Empty;
			SaveSettings();
			Notify("Personal note", "Saved note cleared.", (NotificationType)3, 3f);
		}

		private static void ShowLocalSettings()
		{
			string value = ((Application.targetFrameRate < 0) ? "unlimited" : Application.targetFrameRate.ToString());
			Notify("Local audio and FPS", $"Audio: {AudioListener.volume * 100f:0}%; measured: {_measuredFps:0.0} FPS; target: {value}; VSync: {((QualitySettings.vSyncCount > 0) ? "on" : "off")}.", (NotificationType)0, 7f);
		}

		private static void SetAudioVolume(float volume)
		{
			AudioListener.volume = Mathf.Clamp01(volume);
			Notify("Local audio", $"Volume set to {AudioListener.volume * 100f:0}%.", (NotificationType)3, 3f);
		}

		private static void SetTargetFrameRate(int target)
		{
			Application.targetFrameRate = target;
			Notify("Target FPS", (target < 0) ? "Set to unlimited." : $"Set to {target} FPS.", (NotificationType)3, 3f);
		}

		private static void SetVSync(bool enabled)
		{
			QualitySettings.vSyncCount = (enabled ? 1 : 0);
			Notify("Local VSync", enabled ? "VSync enabled." : "VSync disabled.", (NotificationType)3, 3f);
		}

		private static void SetLowFpsAlerts(bool enabled)
		{
			_settings.LowFpsAlertsEnabled = enabled;
			_lowFpsSeconds = 0f;
			_lowFpsAlertShown = false;
			SaveSettings();
			Notify("Low-FPS warnings", enabled ? $"Enabled below {_settings.LowFpsThreshold} FPS." : "Disabled.", (NotificationType)3, 4f);
		}

		private static void SetLowFpsThreshold(int threshold)
		{
			_settings.LowFpsThreshold = threshold;
			_lowFpsSeconds = 0f;
			_lowFpsAlertShown = false;
			SaveSettings();
			Notify("Low-FPS threshold", $"Warnings will trigger below {threshold} FPS for 10 seconds.", (NotificationType)3, 4f);
		}

		private static void CopyLocalTimestamp()
		{
			GUIUtility.systemCopyBuffer = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss zzz");
			Notify("Clipboard helper", "Local timestamp copied.", (NotificationType)3, 3f);
		}

		private static void CopyUtcTimestamp()
		{
			GUIUtility.systemCopyBuffer = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
			Notify("Clipboard helper", "UTC timestamp copied.", (NotificationType)3, 3f);
		}

		private static void CopyCurrentScene()
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			GUIUtility.systemCopyBuffer = ((Scene)(ref activeScene)).name;
			Notify("Clipboard helper", "Current scene name copied.", (NotificationType)3, 3f);
		}

		private static string GetMelonBaseDirectory()
		{
			return Directory.GetParent(MelonEnvironment.UserDataDirectory)?.FullName ?? MelonEnvironment.UserDataDirectory;
		}

		private static string GetModsDirectory()
		{
			return Path.Combine(GetMelonBaseDirectory(), "Mods");
		}

		private static string GetMelonLoaderDirectory()
		{
			return Path.Combine(GetMelonBaseDirectory(), "MelonLoader");
		}

		private static string GetLatestLogPath()
		{
			string text = Path.Combine(GetMelonLoaderDirectory(), "Latest.log");
			if (File.Exists(text))
			{
				return text;
			}
			string text2 = Path.Combine(GetGameDirectory(), "MelonLoader", "Latest.log");
			if (!File.Exists(text2))
			{
				return text;
			}
			return text2;
		}

		private static string GetGameDirectory()
		{
			string currentDirectory = Environment.CurrentDirectory;
			if (File.Exists(Path.Combine(currentDirectory, "BONELAB_Steam_Windows64.exe")))
			{
				return currentDirectory;
			}
			string text = AppContext.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			if (!File.Exists(Path.Combine(text, "BONELAB_Steam_Windows64.exe")))
			{
				return currentDirectory;
			}
			return text;
		}

		private static void OpenModsFolder()
		{
			OpenFolder(GetModsDirectory(), "Mods folder");
		}

		private static void OpenMelonLoaderFolder()
		{
			OpenFolder(GetMelonLoaderDirectory(), "MelonLoader folder");
		}

		private static void OpenUserDataFolder()
		{
			OpenFolder(MelonEnvironment.UserDataDirectory, "UserData folder");
		}

		private static void OpenGameFolder()
		{
			OpenFolder(GetGameDirectory(), "BONELAB folder");
		}

		private static void OpenScreenshotsFolder()
		{
			OpenFolder(_screenshotsDirectory, "screenshots folder");
		}

		private static void OpenBackupsFolder()
		{
			OpenFolder(_backupsDirectory, "backups folder");
		}

		private static string DllVersion(string path)
		{
			if (!File.Exists(path))
			{
				return "missing";
			}
			string fileVersion = FileVersionInfo.GetVersionInfo(path).FileVersion;
			if (!string.IsNullOrWhiteSpace(fileVersion))
			{
				return fileVersion;
			}
			return "unknown";
		}

		private static string BuildModHealthReport(out int issueCount)
		{
			List<string> list = new List<string>();
			List<string> list2 = new List<string>();
			string modsDirectory = GetModsDirectory();
			string melonLoaderDirectory = GetMelonLoaderDirectory();
			string latestLogPath = GetLatestLogPath();
			_ytDlpPath = ResolveYtDlpPath();
			if (!Directory.Exists(modsDirectory))
			{
				list.Add("Mods folder is missing");
			}
			if (!Directory.Exists(melonLoaderDirectory))
			{
				list.Add("MelonLoader folder is missing");
			}
			if (!File.Exists(latestLogPath))
			{
				list.Add("Latest.log is missing");
			}
			if (!File.Exists(_ytDlpPath))
			{
				list.Add("yt-dlp.exe is missing");
			}
			string path = Path.Combine(modsDirectory, "BoneLib.dll");
			string path2 = Path.Combine(modsDirectory, "LabFusion.dll");
			string path3 = Path.Combine(modsDirectory, "ClipLinkMedia.dll");
			if (!File.Exists(path))
			{
				list.Add("BoneLib.dll is missing");
			}
			if (!File.Exists(path2))
			{
				list.Add("LabFusion.dll is missing");
			}
			if (!File.Exists(path3))
			{
				list.Add("ClipLinkMedia.dll is missing from the active Mods folder");
			}
			int value = 0;
			try
			{
				FileInfo[] files = new DirectoryInfo(modsDirectory).GetFiles("*.dll", SearchOption.AllDirectories);
				value = files.Length;
				foreach (FileInfo item in files.Where((FileInfo file) => file.Length == 0))
				{
					list.Add("Zero-byte mod DLL: " + item.Name);
				}
				foreach (IGrouping<string, FileInfo> item2 in from @group in files.GroupBy<FileInfo, string>((FileInfo file) => file.Name, StringComparer.OrdinalIgnoreCase)
					where @group.Count() > 1
					select @group)
				{
					list.Add($"Duplicate DLL name: {item2.Key} ({item2.Count()} copies)");
				}
			}
			catch (Exception ex)
			{
				list.Add("Could not scan mod DLLs: " + ex.Message);
			}
			try
			{
				string text = Path.GetPathRoot(GetMelonBaseDirectory()) ?? string.Empty;
				if (!string.IsNullOrEmpty(text))
				{
					long availableFreeSpace = new DriveInfo(text).AvailableFreeSpace;
					list2.Add("Free disk space: " + FormatBytes(availableFreeSpace));
					if (availableFreeSpace < 2147483648u)
					{
						list.Add("Less than 2 GB free disk space");
					}
				}
			}
			catch (Exception ex2)
			{
				list2.Add("Disk check failed: " + ex2.Message);
			}
			list2.Add("Active Mods folder: " + modsDirectory);
			list2.Add($"Installed DLL count: {value}");
			list2.Add("ClipLink Media: " + DllVersion(path3));
			list2.Add("BoneLib: " + DllVersion(path));
			list2.Add("Fusion: " + DllVersion(path2));
			list2.Add("yt-dlp: " + GetYtDlpVersion());
			list2.Add("Latest log: " + latestLogPath);
			issueCount = list.Count;
			List<string> list3 = new List<string>
			{
				"ClipLink Media mod health report",
				"Status: " + ((issueCount == 0) ? "READY" : $"{issueCount} ISSUE(S)"),
				$"Checked: {DateTime.Now:F}"
			};
			list3.AddRange(list2);
			if (list.Count > 0)
			{
				list3.Add("Issues:");
				list3.AddRange(list.Select((string issue) => "- " + issue));
			}
			return string.Join(Environment.NewLine, list3);
		}

		private static void RunModHealthCheck()
		{
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = BuildModHealthReport(out var issueCount));
			Notify("Mod health check", (issueCount == 0) ? "Ready. Full report copied." : $"Found {issueCount} issue(s). Full report copied.", (NotificationType)((issueCount != 0) ? 1 : 3), 7f);
		}

		private static string BuildInstalledModList(int maximumEntries = 200)
		{
			string modsDirectory = GetModsDirectory();
			if (!Directory.Exists(modsDirectory))
			{
				return "Mods folder missing: " + modsDirectory;
			}
			try
			{
				FileInfo[] array = new DirectoryInfo(modsDirectory).GetFiles("*.dll", SearchOption.AllDirectories).OrderBy<FileInfo, string>((FileInfo file) => file.Name, StringComparer.OrdinalIgnoreCase).Take(maximumEntries)
					.ToArray();
				List<string> list = new List<string>
				{
					$"Installed BONELAB mod DLLs ({array.Length} shown)",
					"Folder: " + modsDirectory
				};
				FileInfo[] array2 = array;
				foreach (FileInfo fileInfo in array2)
				{
					list.Add($"{fileInfo.Name} | v{DllVersion(fileInfo.FullName)} | {FormatBytes(fileInfo.Length)}");
				}
				return string.Join(Environment.NewLine, list);
			}
			catch (Exception ex)
			{
				return "Could not list installed mods: " + ex.Message;
			}
		}

		private static void CopyInstalledModList()
		{
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = BuildInstalledModList());
			Notify("Installed mod list", "Installed DLL names, versions, and sizes copied.", (NotificationType)3, 5f);
		}

		private static string GetRecentLogErrors(int maximumEntries = 30)
		{
			string latestLogPath = GetLatestLogPath();
			if (!File.Exists(latestLogPath))
			{
				return "Latest log not found: " + latestLogPath;
			}
			try
			{
				string[] array = (from line in File.ReadLines(latestLogPath).TakeLast(2500)
					where line.Contains("[ERROR]", StringComparison.OrdinalIgnoreCase) || line.Contains("Exception", StringComparison.OrdinalIgnoreCase) || line.Contains("failed", StringComparison.OrdinalIgnoreCase) || line.Contains("missing dependency", StringComparison.OrdinalIgnoreCase)
					select line).TakeLast(maximumEntries).ToArray();
				if (array.Length == 0)
				{
					return "No recent error-like lines found in " + latestLogPath;
				}
				return $"Recent BONELAB error-like log lines ({array.Length}){Environment.NewLine}Log: {latestLogPath}{Environment.NewLine}{string.Join(Environment.NewLine, array)}";
			}
			catch (Exception ex)
			{
				return "Could not read recent log errors: " + ex.Message;
			}
		}

		private static void CopyRecentLogErrors()
		{
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = GetRecentLogErrors());
			Notify("Recent log errors", "Recent error-like log lines copied for troubleshooting.", (NotificationType)3, 5f);
		}

		private static string BuildFusionPlayerList()
		{
			NetworkPlayer[] array = NetworkPlayer.Players.ToArray();
			if (array.Length == 0)
			{
				return "No Fusion players are currently registered.";
			}
			List<string> list = new List<string> { $"Fusion players ({array.Length})" };
			NetworkPlayer[] array2 = array;
			foreach (NetworkPlayer val in array2)
			{
				string text = ((val.PlayerID != null && val.PlayerID.IsMe) ? " [LOCAL]" : string.Empty);
				string text2 = ((val.PlayerID != null && val.PlayerID.PlatformID == 76561199548494681L) ? " [OWNER]" : string.Empty);
				list.Add("- " + val.Username + text + text2);
			}
			return string.Join(Environment.NewLine, list);
		}

		private static void ShowFusionPlayerCount()
		{
			int count = NetworkPlayer.Players.Count;
			int count2;
			lock (PresenceGate)
			{
				count2 = ClipLinkUsers.Count;
			}
			Notify("Fusion session", $"{count} registered player(s); {count2} confirmed ClipLink user(s); {OwnerTags.Count} remote OWNER tag(s).", (NotificationType)0, 6f);
		}

		private static string BuildClipLinkUserList()
		{
			List<KeyValuePair<byte, ClipLinkPresenceInfo>> list;
			lock (PresenceGate)
			{
				list = ClipLinkUsers.OrderBy<KeyValuePair<byte, ClipLinkPresenceInfo>, byte>((KeyValuePair<byte, ClipLinkPresenceInfo> entry) => entry.Key).ToList();
			}
			if (!NetworkInfo.HasServer)
			{
				return "Not connected to a Fusion lobby.";
			}
			if (list.Count == 0)
			{
				return "No confirmed ClipLink users have answered yet.";
			}
			List<string> list2 = new List<string> { $"Confirmed ClipLink Media users ({list.Count})" };
			foreach (KeyValuePair<byte, ClipLinkPresenceInfo> item in list)
			{
				item.Deconstruct(out var key, out var value);
				byte b = key;
				ClipLinkPresenceInfo clipLinkPresenceInfo = value;
				string value2 = ((b == PlayerIDManager.LocalSmallID) ? " [YOU]" : string.Empty);
				list2.Add($"- {GetFusionPlayerName(b)}{value2} | v{clipLinkPresenceInfo.Version}");
			}
			list2.Add("Only players with ClipLink Media v5.11.0 or newer can answer this check.");
			return string.Join(Environment.NewLine, list2);
		}

		private static void ShowClipLinkUsers()
		{
			if (!NetworkInfo.HasServer)
			{
				Warn("Join a Fusion lobby before checking ClipLink users.");
				return;
			}
			int count;
			lock (PresenceGate)
			{
				count = ClipLinkUsers.Count;
			}
			int value = Math.Max(PlayerIDManager.PlayerCount, NetworkPlayer.Players.Count);
			Notify("ClipLink users", $"{count} of {value} Fusion player(s) confirmed. Use 'Copy ClipLink user list' for names and versions.", (NotificationType)0, 7f);
		}

		private static void CopyClipLinkUserList()
		{
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = BuildClipLinkUserList());
			Notify("ClipLink user list", "Confirmed ClipLink player names and versions copied.", (NotificationType)3, 5f);
		}

		private static void RefreshClipLinkDetection()
		{
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkInfo.HasServer)
			{
				Warn("Join a Fusion lobby before refreshing ClipLink detection.");
				return;
			}
			lock (PresenceGate)
			{
				ClipLinkUsers.Clear();
				if (PlayerIDManager.LocalID != null)
				{
					ClipLinkUsers[PlayerIDManager.LocalSmallID] = new ClipLinkPresenceInfo("5.11.0", DateTimeOffset.UtcNow);
				}
			}
			SendClipLinkPresence(CommonMessageRoutes.ReliableToOtherClients, requestReply: true);
			Notify("ClipLink detection", "Presence request sent to the Fusion lobby.", (NotificationType)0, 5f);
		}

		private static void CopyFusionPlayerList()
		{
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = BuildFusionPlayerList());
			Notify("Fusion player list", "Current Fusion player names copied.", (NotificationType)3, 4f);
		}

		private static void CopyFusionSessionReport()
		{
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = string.Join(Environment.NewLine, "Fusion session diagnostics", $"Fusion assembly: {typeof(NetworkPlayer).Assembly.GetName().Version}", $"Local OWNER tags attached: {OwnerTags.Count}", BuildClipLinkUserList(), BuildFusionPlayerList()));
			Notify("Fusion diagnostics", "Fusion session report copied.", (NotificationType)3, 4f);
		}

		private static void CopyCompleteSupportReport()
		{
			//IL_001b: 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)
			int issueCount;
			string text = BuildModHealthReport(out issueCount);
			string newLine = Environment.NewLine;
			string[] array = new string[9];
			Scene activeScene = SceneManager.GetActiveScene();
			array[0] = "Scene: " + ((Scene)(ref activeScene)).name;
			array[1] = $"Measured FPS: {_measuredFps:0.0}";
			array[2] = $"Target FPS: {Application.targetFrameRate}";
			array[3] = $"VSync: {QualitySettings.vSyncCount}";
			array[4] = "Unity: " + Application.unityVersion;
			array[5] = "OS: " + SystemInfo.operatingSystem;
			array[6] = "CPU: " + SystemInfo.processorType;
			array[7] = "GPU: " + SystemInfo.graphicsDeviceName;
			array[8] = $"RAM: {SystemInfo.systemMemorySize} MB";
			string text2 = string.Join(newLine, array);
			MelonLogger.Msg(GUIUtility.systemCopyBuffer = string.Join(Environment.NewLine + Environment.NewLine, "CLIPLINK MEDIA COMPLETE SUPPORT REPORT v5.11.0", text, text2, BuildFusionPlayerList(), BuildInstalledModList(100), GetRecentLogErrors(40)));
			Notify("Support report", "Health, session, Fusion, mod-list, and log report copied.", (NotificationType)3, 6f);
		}

		private static void StartConnectivityTest()
		{
			if (Interlocked.CompareExchange(ref _diagnosticRunning, 1, 0) != 0)
			{
				Warn("A connectivity or update check is already running.");
				return;
			}
			Notify("Connectivity test", "Checking YouTube, Litterbox, and GitHub...", (NotificationType)0, 4f);
			Task.Run((Action)RunConnectivityTest);
		}

		private static void RunConnectivityTest()
		{
			try
			{
				string[] value = new string[3]
				{
					TestEndpoint("YouTube", "https://www.youtube.com/generate_204"),
					TestEndpoint("Litterbox", "https://litterbox.catbox.moe/"),
					TestEndpoint("GitHub", "https://api.github.com/")
				};
				string report = $"ClipLink connectivity test - {DateTime.Now:F}{Environment.NewLine}{string.Join(Environment.NewLine, value)}";
				MainThreadActions.Enqueue(delegate
				{
					GUIUtility.systemCopyBuffer = report;
					MelonLogger.Msg(report);
					Notify("Connectivity test finished", "YouTube/Litterbox/GitHub results copied.", (NotificationType)3, 6f);
				});
			}
			catch (Exception ex)
			{
				FailOnMainThread("Connectivity test failed: " + ex.Message);
			}
			finally
			{
				Interlocked.Exchange(ref _diagnosticRunning, 0);
			}
		}

		private static string TestEndpoint(string name, string url)
		{
			Stopwatch stopwatch = Stopwatch.StartNew();
			try
			{
				using HttpResponseMessage httpResponseMessage = DiagnosticsHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult();
				stopwatch.Stop();
				return $"{name}: HTTP {httpResponseMessage.StatusCode} in {stopwatch.ElapsedMilliseconds} ms";
			}
			catch (Exception ex)
			{
				stopwatch.Stop();
				return $"{name}: FAILED after {stopwatch.ElapsedMilliseconds} ms ({ex.Message})";
			}
		}

		private static void CheckForClipLinkUpdate()
		{
			if (Interlocked.CompareExchange(ref _diagnosticRunning, 1, 0) != 0)
			{
				Warn("A connectivity or update check is already running.");
				return;
			}
			Notify("ClipLink update check", "Checking the latest GitHub release...", (NotificationType)0, 4f);
			Task.Run((Action)LoadLatestRelease);
		}

		private static void LoadLatestRelease()
		{
			try
			{
				using JsonDocument jsonDocument = JsonDocument.Parse(DiagnosticsHttpClient.GetStringAsync("https://api.github.com/repos/seeleyllp-crypto/cliplink/releases/latest").GetAwaiter().GetResult());
				string tag = JsonText(jsonDocument.RootElement, "tag_name", "unknown");
				string url = JsonText(jsonDocument.RootElement, "html_url", "https://github.com/seeleyllp-crypto/cliplink/releases");
				MainThreadActions.Enqueue(delegate
				{
					_latestReleaseUrl = url;
					Version result;
					Version result2;
					bool flag = Version.TryParse(tag.TrimStart('v'), out result) && Version.TryParse("5.11.0", out result2) && result2.CompareTo(result) >= 0;
					Notify("ClipLink update check", flag ? ("You are current (" + tag + ").") : ("Latest release: " + tag + ". Open latest release to update."), (NotificationType)((!flag) ? 1 : 3), 7f);
				});
			}
			catch (Exception ex)
			{
				FailOnMainThread("Update check failed: " + ex.Message);
			}
			finally
			{
				Interlocked.Exchange(ref _diagnosticRunning, 0);
			}
		}

		private static void OpenLatestRelease()
		{
			Application.OpenURL(_latestReleaseUrl);
			Notify("ClipLink Media", "Opened the latest GitHub release page.", (NotificationType)0, 3f);
		}

		private static void ShowDiskSpace()
		{
			try
			{
				string text = Path.GetPathRoot(_dataDirectory) ?? string.Empty;
				string text2 = Path.GetPathRoot(ChooseJobsRoot()) ?? string.Empty;
				string value = (string.IsNullOrEmpty(text) ? "unknown" : FormatBytes(new DriveInfo(text).AvailableFreeSpace));
				string value2 = (string.IsNullOrEmpty(text2) ? "unknown" : FormatBytes(new DriveInfo(text2).AvailableFreeSpace));
				Notify("Disk space", $"ClipLink data drive: {value} free; temporary-job drive: {value2} free.", (NotificationType)0, 6f);
			}
			catch (Exception ex)
			{
				Warn("Could not read disk space: " + ex.Message);
			}
		}

		private static void TakeScreenshot()
		{
			try
			{
				Directory.CreateDirectory(_screenshotsDirectory);
				string text = Path.Combine(_screenshotsDirectory, $"BONELAB-{DateTime.Now:yyyyMMdd-HHmmss}.png");
				ScreenCapture.CaptureScreenshot(text, 1);
				MelonLogger.Msg("Screenshot requested: " + text);
				Notify("BONELAB screenshot", "Saving " + Path.GetFileName(text) + " in ClipLink Screenshots.", (NotificationType)3, 5f);
			}
			catch (Exception ex)
			{
				Warn("Could not take screenshot: " + ex.Message);
			}
		}

		private static void BackupClipLinkData()
		{
			try
			{
				string text = Path.Combine(_backupsDirectory, DateTime.Now.ToString("yyyyMMdd-HHmmss"));
				Directory.CreateDirectory(text);
				int num = 0;
				string[] array = new string[2] { _settingsPath, _historyPath };
				foreach (string text2 in array)
				{
					if (File.Exists(text2))
					{
						File.Copy(text2, Path.Combine(text, Path.GetFileName(text2)), overwrite: false);
						num++;
					}
				}
				MelonLogger.Msg($"Backed up {num} ClipLink data file(s) to {text}");
				Notify("ClipLink backup", $"Backed up {num} data file(s).", (NotificationType)3, 5f);
			}
			catch (Exception ex)
			{
				Warn("Backup failed: " + ex.Message);
			}
		}

		private static void CleanOldClipLinkTemps()
		{
			if (!_cleanupConfirmed)
			{
				Warn("Turn on 'Confirm old-temp cleanup' before deleting old ClipLink job folders.");
				return;
			}
			_cleanupConfirmed = false;
			Notify("ClipLink maintenance", "Scanning only ClipLinkMediaJobs folders older than 24 hours...", (NotificationType)0, 4f);
			Task.Run((Action)CleanOldClipLinkTempsWorker);
		}

		private static void CleanOldClipLinkTempsWorker()
		{
			int deletedDirectories = 0;
			long deletedBytes = 0L;
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Path.Combine(Path.GetTempPath(), "ClipLinkMediaJobs") };
			foreach (DriveInfo item in from drive in DriveInfo.GetDrives()
				where drive.IsReady && drive.DriveType == DriveType.Fixed
				select drive)
			{
				hashSet.Add(Path.Combine(item.RootDirectory.FullName, "ClipLinkMediaJobs"));
			}
			DateTime dateTime = DateTime.UtcNow.AddHours(-24.0);
			foreach (string item2 in hashSet)
			{
				try
				{
					string path = Path.GetFullPath(item2).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
					if (!string.Equals(Path.GetFileName(path), "ClipLinkMediaJobs", StringComparison.OrdinalIgnoreCase) || !Directory.Exists(path))
					{
						continue;
					}
					DirectoryInfo directoryInfo = new DirectoryInfo(path);
					if ((directoryInfo.Attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
					{
						continue;
					}
					DirectoryInfo[] directories = directoryInfo.GetDirectories();
					foreach (DirectoryInfo directoryInfo2 in directories)
					{
						if (directoryInfo2.LastWriteTimeUtc >= dateTime || (directoryInfo2.Attributes & FileAttributes.ReparsePoint) != FileAttributes.None)
						{
							continue;
						}
						if (directoryInfo2.GetDirectories().Length != 0)
						{
							MelonLogger.Warning("Skipped unexpected nested ClipLink temp folder: " + directoryInfo2.FullName);
							continue;
						}
						try
						{
							deletedBytes += directoryInfo2.GetFiles().Sum((FileInfo file) => file.Length);
						}
						catch
						{
						}
						directoryInfo2.Delete(recursive: true);
						deletedDirectories++;
					}
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Could not clean a ClipLink temp root: " + ex.Message);
				}
			}
			MainThreadActions.Enqueue(delegate
			{
				Notify("ClipLink cleanup finished", $"Deleted {deletedDirectories} old job folder(s), freeing {FormatBytes(deletedBytes)}.", (NotificationType)3, 6f);
			});
		}

		private static void LoadPersistentState()
		{
			try
			{
				if (File.Exists(_settingsPath))
				{
					_settings = JsonSerializer.Deserialize<ClipLinkSettings>(File.ReadAllText(_settingsPath)) ?? new ClipLinkSettings();
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not load settings: " + ex.Message);
				_settings = new ClipLinkSettings();
			}
			if (!IsSupportedRetention(_settings.LitterboxRetention))
			{
				_settings.LitterboxRetention = "72h";
			}
			if (!IsSupportedVideoQuality(_settings.VideoQuality))
			{
				_settings.VideoQuality = "Best";
			}
			ClipLinkSettings settings = _settings;
			if (settings.LastSourceUrl == null)
			{
				string text = (settings.LastSourceUrl = string.Empty);
			}
			settings = _settings;
			if (settings.LastFailedUploadPath == null)
			{
				string text = (settings.LastFailedUploadPath = string.Empty);
			}
			settings = _settings;
			if (settings.LastFailedUploadSourceUrl == null)
			{
				string text = (settings.LastFailedUploadSourceUrl = string.Empty);
			}
			settings = _settings;
			if (settings.LastFailedUploadRetention == null)
			{
				string text = (settings.LastFailedUploadRetention = string.Empty);
			}
			settings = _settings;
			if (settings.PersonalNote == null)
			{
				string text = (settings.PersonalNote = string.Empty);
			}
			int lowFpsThreshold = _settings.LowFpsThreshold;
			if ((lowFpsThreshold != 45 && lowFpsThreshold != 60 && lowFpsThreshold != 72) || 1 == 0)
			{
				_settings.LowFpsThreshold = 45;
			}
			settings = _settings;
			if (settings.RecentSearches == null)
			{
				List<string> list = (settings.RecentSearches = new List<string>());
			}
			settings = _settings;
			if (settings.RecentVideos == null)
			{
				List<RecentVideoEntry> list3 = (settings.RecentVideos = new List<RecentVideoEntry>());
			}
			settings = _settings;
			if (settings.Favorites == null)
			{
				List<RecentVideoEntry> list3 = (settings.Favorites = new List<RecentVideoEntry>());
			}
			settings = _settings;
			if (settings.JobQueue == null)
			{
				List<RecentVideoEntry> list3 = (settings.JobQueue = new List<RecentVideoEntry>());
			}
			_settings.RecentSearches = _settings.RecentSearches.Where((string query) => !string.IsNullOrWhiteSpace(query)).Distinct<string>(StringComparer.OrdinalIgnoreCase).Take(8)
				.ToList();
			_settings.RecentVideos = (from entry in _settings.RecentVideos
				where IsYouTubeUrl(entry.Url)
				orderby entry.SelectedUtc descending
				select entry).Take(12).ToList();
			_settings.Favorites = (from @group in _settings.Favorites.Where((RecentVideoEntry entry) => IsYouTubeUrl(entry.Url)).GroupBy<RecentVideoEntry, string>((RecentVideoEntry entry) => entry.Url, StringComparer.OrdinalIgnoreCase)
				select @group.First() into entry
				orderby entry.SelectedUtc descending
				select entry).Take(20).ToList();
			_settings.JobQueue = _settings.JobQueue.Where((RecentVideoEntry entry) => IsYouTubeUrl(entry.Url)).Take(10).ToList();
			try
			{
				if (File.Exists(_historyPath))
				{
					List<LinkHistoryEntry> list7 = JsonSerializer.Deserialize<List<LinkHistoryEntry>>(File.ReadAllText(_historyPath));
					if (list7 != null)
					{
						LinkHistory.AddRange((from entry in list7
							where entry.ExpiresUtc > DateTimeOffset.UtcNow && IsHttpsUrl(entry.DirectUrl)
							orderby entry.CreatedUtc descending
							select entry).Take(8));
					}
				}
			}
			catch (Exception ex2)
			{
				MelonLogger.Warning("Could not load recent links: " + ex2.Message);
			}
			_lastPublicUrl = LinkHistory.FirstOrDefault()?.DirectUrl ?? string.Empty;
			SaveSettings();
			SaveHistory();
		}

		private static void SaveSettings()
		{
			try
			{
				Directory.CreateDirectory(_dataDirectory);
				File.WriteAllText(_settingsPath, JsonSerializer.Serialize(_settings, new JsonSerializerOptions
				{
					WriteIndented = true
				}));
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not save settings: " + ex.Message);
			}
		}

		private static void SaveHistory()
		{
			try
			{
				Directory.CreateDirectory(_dataDirectory);
				File.WriteAllText(_historyPath, JsonSerializer.Serialize(LinkHistory, new JsonSerializerOptions
				{
					WriteIndented = true
				}));
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not save recent links: " + ex.Message);
			}
		}

		private static void SetRetention(string retention)
		{
			if (IsSupportedRetention(retention))
			{
				_settings.LitterboxRetention = retention;
				SaveSettings();
				string text = RetentionDisplay(retention);
				MelonLogger.Msg("Public-link expiry set to " + text + ".");
				Notify("ClipLink Media", "New public links will last " + text + ".", (NotificationType)3, 4f);
			}
		}

		private static void ShowCurrentRetention()
		{
			Notify("Public-link expiry", "Current setting: " + RetentionDisplay(_settings.LitterboxRetention) + ".", (NotificationType)0, 4f);
		}

		private static void SetVideoQuality(string quality)
		{
			if (IsSupportedVideoQuality(quality))
			{
				_settings.VideoQuality = quality;
				SaveSettings();
				MelonLogger.Msg("MP4 quality set to " + quality + ".");
				Notify("MP4 quality", "New jobs will use " + quality + " quality.", (NotificationType)3, 4f);
			}
		}

		private static void ShowCurrentQuality()
		{
			Notify("MP4 quality", "Current setting: " + _settings.VideoQuality + ".", (NotificationType)0, 4f);
		}

		private static void AddRecentSearch(string query)
		{
			_settings.RecentSearches.RemoveAll((string saved) => string.Equals(saved, query, StringComparison.OrdinalIgnoreCase));
			_settings.RecentSearches.Insert(0, query);
			while (_settings.RecentSearches.Count > 8)
			{
				_settings.RecentSearches.RemoveAt(_settings.RecentSearches.Count - 1);
			}
			SaveSettings();
			RefreshSearchHistoryPage();
		}

		private static void RefreshSearchHistoryPage()
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			if (_searchHistoryPage == null)
			{
				return;
			}
			_searchHistoryPage.RemoveAll();
			if (_settings.RecentSearches.Count == 0)
			{
				_searchHistoryPage.CreateFunction("No recent searches", Color.gray, (Action)delegate
				{
					Warn("Run a YouTube search first.");
				});
			}
			else
			{
				string[] array = _settings.RecentSearches.ToArray();
				foreach (string text in array)
				{
					string savedQuery = text;
					_searchHistoryPage.CreateFunction(ShortMenuText(text, 64), Color.white,