Decompiled source of ClipLinkMedia v5.11.0
Mods\ClipLinkMedia.dll
Decompiled 2 days ago
The result has been truncated due to the large size, download it to view full contents!
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,