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 <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 <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 <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 MainThreadActions = new ConcurrentQueue(); 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 OwnerTags = new Dictionary(); private static readonly object PresenceGate = new object(); private static readonly Dictionary ClipLinkUsers = new Dictionary(); private static readonly object ThumbnailGate = new object(); private static readonly Dictionary ThumbnailCache = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary> PendingThumbnailButtons = new Dictionary>(StringComparer.Ordinal); private static readonly Queue ThumbnailCacheOrder = new Queue(); private static readonly SemaphoreSlim ThumbnailDownloadSlots = new SemaphoreSlim(3, 3); private static readonly List LinkHistory = new List(); 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(); } 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(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(); 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)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)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)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)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)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)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)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 list = new List(); List list2 = new List(); 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 item2 in from @group in files.GroupBy((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 list3 = new List { "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 file) => file.Name, StringComparer.OrdinalIgnoreCase).Take(maximumEntries) .ToArray(); List list = new List { $"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 list = new List { $"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> list; lock (PresenceGate) { list = ClipLinkUsers.OrderBy, byte>((KeyValuePair 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 list2 = new List { $"Confirmed ClipLink Media users ({list.Count})" }; foreach (KeyValuePair 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 hashSet = new HashSet(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(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 list = (settings.RecentSearches = new List()); } settings = _settings; if (settings.RecentVideos == null) { List list3 = (settings.RecentVideos = new List()); } settings = _settings; if (settings.Favorites == null) { List list3 = (settings.Favorites = new List()); } settings = _settings; if (settings.JobQueue == null) { List list3 = (settings.JobQueue = new List()); } _settings.RecentSearches = _settings.RecentSearches.Where((string query) => !string.IsNullOrWhiteSpace(query)).Distinct(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 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 list7 = JsonSerializer.Deserialize>(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, (Action)delegate { RunSavedSearch(savedQuery); }); } } _searchHistoryPage.CreateFunction("Clear search history", Color.red, (Action)ClearSearchHistory); } private static void RunSavedSearch(string query) { _searchQuery = query; SearchYouTube(); } private static void ClearSearchHistory() { _settings.RecentSearches.Clear(); SaveSettings(); RefreshSearchHistoryPage(); Notify("YouTube browser", "Search history cleared.", (NotificationType)3, 3f); } private static void AddRecentVideo(string title, string url) { _settings.RecentVideos.RemoveAll((RecentVideoEntry entry) => string.Equals(entry.Url, url, StringComparison.OrdinalIgnoreCase)); _settings.RecentVideos.Insert(0, new RecentVideoEntry { Title = title, Url = url, SelectedUtc = DateTimeOffset.UtcNow }); while (_settings.RecentVideos.Count > 12) { _settings.RecentVideos.RemoveAt(_settings.RecentVideos.Count - 1); } _settings.LastSourceUrl = url; SaveSettings(); RefreshRecentVideosPage(); } private static void RefreshRecentVideosPage() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) if (_recentVideosPage == null) { return; } _recentVideosPage.RemoveAll(); if (_settings.RecentVideos.Count == 0) { _recentVideosPage.CreateFunction("No selected videos", Color.gray, (Action)delegate { Warn("Select a YouTube search result first."); }); } else { RecentVideoEntry[] array = _settings.RecentVideos.ToArray(); foreach (RecentVideoEntry recentVideoEntry in array) { RecentVideoEntry selectedEntry = recentVideoEntry; FunctionElement obj = _recentVideosPage.CreateFunction(ShortMenuText(recentVideoEntry.Title, 64), Color.white, (Action)delegate { CopyRecentVideo(selectedEntry); }); ((Element)obj).SetTooltip($"{recentVideoEntry.Title}\nSelected: {recentVideoEntry.SelectedUtc.LocalDateTime:g}\nSelect the thumbnail to copy:\n{recentVideoEntry.Url}"); AttachYouTubeThumbnail(obj, GetYouTubeVideoId(recentVideoEntry.Url)); } } _recentVideosPage.CreateFunction("Clear selected videos", Color.red, (Action)ClearRecentVideos); } private static void CopyRecentVideo(RecentVideoEntry entry) { GUIUtility.systemCopyBuffer = entry.Url; _settings.LastSourceUrl = entry.Url; SaveSettings(); Notify("YouTube link copied", ShortMenuText(entry.Title, 100), (NotificationType)3, 4f); } private static void ClearRecentVideos() { _settings.RecentVideos.Clear(); _settings.LastSourceUrl = string.Empty; SaveSettings(); RefreshRecentVideosPage(); Notify("YouTube browser", "Selected-video history cleared.", (NotificationType)3, 3f); } private static void AddCopiedFavorite() { string url = GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty; if (!IsYouTubeUrl(url)) { Warn("Copy or select a YouTube video before adding a favorite."); return; } string text = _settings.RecentVideos.FirstOrDefault((RecentVideoEntry entry) => string.Equals(entry.Url, url, StringComparison.OrdinalIgnoreCase))?.Title ?? ("YouTube " + GetYouTubeVideoId(url)); _settings.Favorites.RemoveAll((RecentVideoEntry entry) => string.Equals(entry.Url, url, StringComparison.OrdinalIgnoreCase)); _settings.Favorites.Insert(0, new RecentVideoEntry { Title = text, Url = url, SelectedUtc = DateTimeOffset.UtcNow }); while (_settings.Favorites.Count > 20) { _settings.Favorites.RemoveAt(_settings.Favorites.Count - 1); } _settings.LastSourceUrl = url; SaveSettings(); RefreshFavoritesPage(); Notify("Favorite saved", ShortMenuText(text, 100), (NotificationType)3, 4f); } private static void RefreshFavoritesPage() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) if (_favoritesPage == null) { return; } _favoritesPage.RemoveAll(); if (_settings.Favorites.Count == 0) { _favoritesPage.CreateFunction("No favorite videos", Color.gray, (Action)delegate { Warn("Copy a video, then choose Add copied video to favorites."); }); } else { RecentVideoEntry[] array = _settings.Favorites.ToArray(); foreach (RecentVideoEntry recentVideoEntry in array) { RecentVideoEntry selectedEntry = recentVideoEntry; FunctionElement obj = _favoritesPage.CreateFunction(ShortMenuText(recentVideoEntry.Title, 64), Color.white, (Action)delegate { CopyFavorite(selectedEntry); }); ((Element)obj).SetTooltip(recentVideoEntry.Title + "\nSelect the thumbnail to copy:\n" + recentVideoEntry.Url); AttachYouTubeThumbnail(obj, GetYouTubeVideoId(recentVideoEntry.Url)); } } _favoritesPage.CreateFunction("Remove copied favorite", Color.yellow, (Action)RemoveCopiedFavorite); _favoritesPage.CreateFunction("Clear all favorites", Color.red, (Action)ClearFavorites); } private static void CopyFavorite(RecentVideoEntry entry) { GUIUtility.systemCopyBuffer = entry.Url; _settings.LastSourceUrl = entry.Url; SaveSettings(); Notify("Favorite link copied", ShortMenuText(entry.Title, 100), (NotificationType)3, 4f); } private static void RemoveCopiedFavorite() { string url = GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty; if (_settings.Favorites.RemoveAll((RecentVideoEntry entry) => string.Equals(entry.Url, url, StringComparison.OrdinalIgnoreCase)) == 0) { Warn("The copied URL is not in favorites."); return; } SaveSettings(); RefreshFavoritesPage(); Notify("Favorite removed", "Removed the copied video from favorites.", (NotificationType)3, 3f); } private static void ClearFavorites() { _settings.Favorites.Clear(); SaveSettings(); RefreshFavoritesPage(); Notify("Favorites", "All favorite videos cleared.", (NotificationType)3, 3f); } private static void RefreshQueuePage() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) if (_queuePage == null) { return; } _queuePage.RemoveAll(); _queuePage.CreateFunction("Add copied video", Color.green, (Action)AddCopiedToQueue); _queuePage.CreateFunction("Show queue status", Color.white, (Action)ShowJobStatus); _queuePage.CreateFunction("Start public-link queue", Color.green, (Action)delegate { StartQueue(uploadPublicly: true); }); _queuePage.CreateFunction("Start local-download queue", Color.cyan, (Action)delegate { StartQueue(uploadPublicly: false); }); _queuePage.CreateFunction("Remove next queued video", Color.yellow, (Action)RemoveNextQueuedVideo); _queuePage.CreateFunction("Clear pending queue", Color.red, (Action)ClearQueue); RecentVideoEntry[] array = _settings.JobQueue.ToArray(); foreach (RecentVideoEntry recentVideoEntry in array) { RecentVideoEntry selectedEntry = recentVideoEntry; ((Element)_queuePage.CreateFunction("Queued: " + ShortMenuText(recentVideoEntry.Title, 54), Color.white, (Action)delegate { CopyQueuedVideo(selectedEntry); })).SetTooltip(recentVideoEntry.Url); } } private static void AddCopiedToQueue() { string url = GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty; if (!IsYouTubeUrl(url)) { Warn("Copy or select a YouTube video before adding it to the queue."); return; } if (_settings.JobQueue.Count >= 10) { Warn($"The batch queue is full ({10} videos)."); return; } if (_settings.JobQueue.Any((RecentVideoEntry entry) => string.Equals(entry.Url, url, StringComparison.OrdinalIgnoreCase))) { Warn("That video is already in the queue."); return; } string text = _settings.RecentVideos.FirstOrDefault((RecentVideoEntry entry) => string.Equals(entry.Url, url, StringComparison.OrdinalIgnoreCase))?.Title ?? ("YouTube " + GetYouTubeVideoId(url)); _settings.JobQueue.Add(new RecentVideoEntry { Title = text, Url = url, SelectedUtc = DateTimeOffset.UtcNow }); SaveSettings(); RefreshQueuePage(); Notify("Batch queue", $"Added {ShortMenuText(text, 90)} ({_settings.JobQueue.Count}/{10}).", (NotificationType)3, 4f); } private static void CopyQueuedVideo(RecentVideoEntry entry) { GUIUtility.systemCopyBuffer = entry.Url; _settings.LastSourceUrl = entry.Url; SaveSettings(); Notify("Queued link copied", ShortMenuText(entry.Title, 100), (NotificationType)3, 3f); } private static void RemoveNextQueuedVideo() { if (_settings.JobQueue.Count == 0) { Warn("The batch queue is empty."); return; } string title = _settings.JobQueue[0].Title; _settings.JobQueue.RemoveAt(0); SaveSettings(); RefreshQueuePage(); Notify("Batch queue", "Removed " + ShortMenuText(title, 90) + ".", (NotificationType)3, 3f); } private static void ClearQueue() { _queueModeActive = false; _settings.JobQueue.Clear(); SaveSettings(); RefreshQueuePage(); Notify("Batch queue", "Pending videos cleared. The current job was not cancelled.", (NotificationType)3, 4f); } private static void StartQueue(bool uploadPublicly) { if (!_rightsConfirmed) { Warn("Turn on 'I own / have permission' before starting the batch queue."); return; } if (Volatile.Read(in _jobRunning) != 0) { Warn("Wait for the current video job to finish before starting the queue."); return; } if (_settings.JobQueue.Count == 0) { Warn("Add at least one copied YouTube video to the queue first."); return; } _queueUploadPublicly = uploadPublicly; _queueModeActive = true; Notify("Batch queue started", $"Processing {_settings.JobQueue.Count} video(s) as {(uploadPublicly ? "public links" : "local downloads")}.", (NotificationType)0, 5f); ContinueQueue(); } private static void ContinueQueue() { if (_queueModeActive && Volatile.Read(in _jobRunning) == 0) { if (_settings.JobQueue.Count == 0) { _queueModeActive = false; _jobStatus = "Batch queue completed"; RefreshQueuePage(); Notify("Batch queue finished", "All queued videos have been processed.", (NotificationType)3, 6f); } else { RecentVideoEntry recentVideoEntry = _settings.JobQueue[0]; _settings.JobQueue.RemoveAt(0); SaveSettings(); RefreshQueuePage(); TryStartJob(recentVideoEntry.Url, _queueUploadPublicly, fromQueue: true); } } } private static void RefreshHistoryPage() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) if (_historyPage == null) { return; } _historyPage.RemoveAll(); LinkHistory.RemoveAll((LinkHistoryEntry entry) => entry.ExpiresUtc <= DateTimeOffset.UtcNow); if (LinkHistory.Count == 0) { _historyPage.CreateFunction("No saved public URLs", Color.gray, (Action)delegate { Warn("Create a public MP4 URL first."); }); } else { LinkHistoryEntry[] array = LinkHistory.ToArray(); foreach (LinkHistoryEntry linkHistoryEntry in array) { LinkHistoryEntry selectedEntry = linkHistoryEntry; string youTubeVideoId = GetYouTubeVideoId(linkHistoryEntry.SourceUrl); string value = $"{linkHistoryEntry.ExpiresUtc.LocalDateTime:g} | {youTubeVideoId}"; ((Element)_historyPage.CreateFunction(ShortMenuText(value, 64), Color.white, (Action)delegate { CopyHistoryEntry(selectedEntry); })).SetTooltip($"Expires: {linkHistoryEntry.ExpiresUtc.LocalDateTime:F}\nSource: {linkHistoryEntry.SourceUrl}\nDirect URL: {linkHistoryEntry.DirectUrl}"); } } _historyPage.CreateFunction("Remove expired URLs", Color.yellow, (Action)RemoveExpiredHistory); _historyPage.CreateFunction("Clear all saved URLs", Color.red, (Action)ClearHistory); SaveHistory(); } private static void CopyHistoryEntry(LinkHistoryEntry entry) { GUIUtility.systemCopyBuffer = entry.DirectUrl; _lastPublicUrl = entry.DirectUrl; Notify("Recent URL copied", $"Expires {entry.ExpiresUtc.LocalDateTime:g}.", (NotificationType)3, 4f); } private static void RemoveExpiredHistory() { int num = LinkHistory.RemoveAll((LinkHistoryEntry entry) => entry.ExpiresUtc <= DateTimeOffset.UtcNow); RefreshHistoryPage(); Notify("Recent URLs", (num == 1) ? "Removed 1 expired URL." : $"Removed {num} expired URLs.", (NotificationType)3, 3f); } private static void ClearHistory() { LinkHistory.Clear(); _lastPublicUrl = string.Empty; RefreshHistoryPage(); Notify("Recent URLs", "Saved public URLs cleared.", (NotificationType)3, 3f); } private static void SearchYouTube() { string query = _searchQuery.Trim(); if (string.IsNullOrWhiteSpace(query)) { Warn("Type something in the YouTube search box first."); return; } if (Interlocked.CompareExchange(ref _searchRunning, 1, 0) != 0) { Warn("A YouTube search is already running."); return; } AddRecentSearch(query); MelonLogger.Msg("Searching signed-out YouTube results for: " + query); Notify("YouTube browser", "Searching for " + ShortMenuText(query, 60) + "...", (NotificationType)0, 3f); Task.Run(delegate { LoadYouTubeResults(query); }); } private static void LoadYouTubeResults(string query) { try { string requestUri = "https://www.youtube.com/results?hl=en&sp=EgIQAQ%3D%3D&search_query=" + Uri.EscapeDataString(query); string result = YouTubeHttpClient.GetStringAsync(requestUri).GetAwaiter().GetResult(); List results = YouTubeSearchParser.Parse(result, 12); if (results.Count == 0) { throw new InvalidOperationException("YouTube returned no video results. Try another search."); } MainThreadActions.Enqueue(delegate { DisplayYouTubeResults(query, results); }); } catch (Exception ex) { MelonLogger.Error($"YouTube search failed: {ex}"); FailOnMainThread("YouTube search failed: " + ex.Message); } finally { Interlocked.Exchange(ref _searchRunning, 0); } } private static void DisplayYouTubeResults(string query, List results) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (_searchResultsPage == null) { return; } _searchResultsPage.RemoveAll(); _searchResultsPage.Name = "YouTube | " + ShortMenuText(query, 34); foreach (YouTubeSearchResult result in results) { YouTubeSearchResult selectedChoice = result; FunctionElement obj = _searchResultsPage.CreateFunction(ShortMenuText(result.Title, 64), Color.white, (Action)delegate { CopyYouTubeChoice(selectedChoice); }); ((Element)obj).SetTooltip(result.Title + "\nSelect this thumbnail to copy:\n" + result.Url); AttachYouTubeThumbnail(obj, result.Id); } MelonLogger.Msg($"Loaded {results.Count} signed-out YouTube results for: {query}"); Notify("YouTube", $"Loaded {results.Count} thumbnail cards. Select a video to copy its link.", (NotificationType)3, 6f); Menu.OpenPage(_searchResultsPage); } private static void AttachYouTubeThumbnail(FunctionElement button, string videoId) { if (string.IsNullOrWhiteSpace(videoId)) { return; } bool flag = false; lock (ThumbnailGate) { if (ThumbnailCache.TryGetValue(videoId, out Texture2D value) && (Object)(object)value != (Object)null) { button.Logo = value; return; } if (!PendingThumbnailButtons.TryGetValue(videoId, out List value2)) { value2 = new List(); PendingThumbnailButtons[videoId] = value2; flag = true; } value2.Add(button); } if (flag) { Task.Run(() => DownloadYouTubeThumbnail(videoId)); } } private static async Task DownloadYouTubeThumbnail(string videoId) { await ThumbnailDownloadSlots.WaitAsync().ConfigureAwait(continueOnCapturedContext: false); try { string requestUri = "https://i.ytimg.com/vi/" + videoId + "/hqdefault.jpg"; byte[] bytes = await YouTubeHttpClient.GetByteArrayAsync(requestUri).ConfigureAwait(continueOnCapturedContext: false); if (bytes.Length < 256) { throw new InvalidOperationException("YouTube returned an empty thumbnail."); } MainThreadActions.Enqueue(delegate { CompleteYouTubeThumbnail(videoId, bytes); }); } catch (Exception ex) { MelonLogger.Warning("Could not load YouTube thumbnail " + videoId + ": " + ex.Message); MainThreadActions.Enqueue(delegate { CompleteYouTubeThumbnail(videoId, null); }); } finally { ThumbnailDownloadSlots.Release(); } } private static void CompleteYouTubeThumbnail(string videoId, byte[]? bytes) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown List list; lock (ThumbnailGate) { if (!PendingThumbnailButtons.Remove(videoId, out List value)) { return; } list = value; } if (bytes == null) { return; } try { Texture2D val = new Texture2D(2, 2, (TextureFormat)3, false) { name = "YouTube Thumbnail " + videoId, filterMode = (FilterMode)1, wrapMode = (TextureWrapMode)1 }; Il2CppStructArray val2 = Il2CppStructArray.op_Implicit(bytes); if (!ImageConversion.LoadImage(val, val2, true)) { Object.Destroy((Object)(object)val); return; } lock (ThumbnailGate) { if (ThumbnailCache.Count >= 48 && ThumbnailCacheOrder.Count > 0) { string key = ThumbnailCacheOrder.Dequeue(); ThumbnailCache.Remove(key); } ThumbnailCache[videoId] = val; ThumbnailCacheOrder.Enqueue(videoId); } foreach (FunctionElement item in list) { item.Logo = val; } } catch (Exception ex) { MelonLogger.Warning("Could not render YouTube thumbnail " + videoId + ": " + ex.Message); } } private static void CopyYouTubeChoice(YouTubeSearchResult choice) { GUIUtility.systemCopyBuffer = choice.Url; AddRecentVideo(choice.Title, choice.Url); MelonLogger.Msg($"YouTube link copied: {choice.Url} ({choice.Title})"); Notify("YouTube link copied", ShortMenuText(choice.Title, 100), (NotificationType)3, 5f); } private static void PreviewCopiedVideo() { string url = GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty; if (!IsYouTubeUrl(url)) { Warn("Copy or select a YouTube video before previewing its information."); return; } if (Interlocked.CompareExchange(ref _previewRunning, 1, 0) != 0) { Warn("A video preview is already loading."); return; } _ytDlpPath = ResolveYtDlpPath(); string quality = _settings.VideoQuality; Notify("Video preview", "Loading title, duration, channel, size, and " + quality + " format...", (NotificationType)0, 4f); Task.Run(delegate { LoadVideoPreview(url, quality); }); } private static void LoadVideoPreview(string url, string quality) { try { if (!File.Exists(_ytDlpPath)) { throw new FileNotFoundException("yt-dlp.exe is missing. Open Setup and folders to install it.", _ytDlpPath); } using Process process = Process.Start(new ProcessStartInfo { FileName = _ytDlpPath, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, ArgumentList = { "--no-playlist", "--no-warnings", "--skip-download", "--dump-single-json", "-f", VideoFormatSelector(quality), "--", url } }) ?? throw new InvalidOperationException("yt-dlp preview did not start."); Task task = process.StandardOutput.ReadToEndAsync(); Task task2 = process.StandardError.ReadToEndAsync(); if (!process.WaitForExit(60000)) { process.Kill(entireProcessTree: true); process.WaitForExit(); throw new TimeoutException("The video information preview timed out."); } Task.WaitAll(task, task2); if (process.ExitCode != 0) { throw new InvalidOperationException("Preview failed: " + LastPart(task2.Result.Trim(), 400)); } using JsonDocument jsonDocument = JsonDocument.Parse(task.Result); JsonElement rootElement = jsonDocument.RootElement; string title = JsonText(rootElement, "title", GetYouTubeVideoId(url)); string channel = JsonText(rootElement, "channel", JsonText(rootElement, "uploader", "Unknown channel")); string duration = JsonText(rootElement, "duration_string", "Unknown duration"); string resolution = JsonText(rootElement, "resolution", quality); long num = JsonInt64(rootElement, "filesize_approx") ?? JsonInt64(rootElement, "filesize").GetValueOrDefault(); string size = ((num > 0) ? FormatBytes(num) : "Unknown size"); string summary = string.Join(Environment.NewLine, title, "Channel: " + channel, "Duration: " + duration, $"Selected format: {resolution} ({quality})", "Estimated size: " + size, "URL: " + url); MainThreadActions.Enqueue(delegate { _lastPreviewSummary = summary; _settings.LastSourceUrl = url; SaveSettings(); MelonLogger.Msg("Video preview loaded:\n" + summary); Notify(ShortMenuText(title, 70), $"{duration} | {resolution} | {size} | {ShortMenuText(channel, 40)}", (NotificationType)3, 8f); }); } catch (Exception ex) { MelonLogger.Error($"Video preview failed: {ex}"); FailOnMainThread(ex.Message); } finally { Interlocked.Exchange(ref _previewRunning, 0); } } private static void CopyLastPreview() { if (string.IsNullOrWhiteSpace(_lastPreviewSummary)) { Warn("Preview a copied YouTube video first."); return; } GUIUtility.systemCopyBuffer = _lastPreviewSummary; Notify("Video preview", "The last video information report was copied.", (NotificationType)3, 4f); } private static void OpenYouTube() { try { Application.OpenURL("https://www.youtube.com/"); MelonLogger.Msg("Opened YouTube. Copy a video URL, then return to ClipLink Media in BoneMenu."); } catch (Exception ex) { FailOnMainThread("Could not open YouTube: " + ex.Message); } } private static void OpenRealYouTubeBrowser() { _realYouTubeBrowserPath = ResolveRealYouTubeBrowserPath(); if (!File.Exists(_realYouTubeBrowserPath)) { Warn("The real YouTube GUI is not installed. ClipLink never downloads executables; install it manually from the GitHub releases page."); } else { LaunchRealYouTubeBrowser(); } } private static void LaunchRealYouTubeBrowser() { try { string workingDirectory = Path.GetDirectoryName(_realYouTubeBrowserPath) ?? _dataDirectory; Process.Start(new ProcessStartInfo { FileName = _realYouTubeBrowserPath, WorkingDirectory = workingDirectory, UseShellExecute = true }); MelonLogger.Msg("Opened the real signed-out YouTube GUI: " + _realYouTubeBrowserPath); Notify("Real YouTube opened", "Use the SteamVR desktop panel to view it. Clicking a video copies its URL automatically.", (NotificationType)3, 7f); } catch (Exception ex) { FailOnMainThread("Could not open the real YouTube GUI: " + ex.Message); } } private static void OpenRealYouTubeBrowserDownloadPage() { try { Application.OpenURL("https://github.com/seeleyllp-crypto/cliplink/releases"); MelonLogger.Msg("Opened the ClipLink GitHub releases page for a manual companion download."); Notify("Manual browser setup", "ClipLink does not download executables. Download the companion yourself, then place it in the ClipLink folder.", (NotificationType)0, 9f); } catch (Exception ex) { FailOnMainThread("Could not open the ClipLink releases page: " + ex.Message); } } private static void CheckRealYouTubeBrowser() { _realYouTubeBrowserPath = ResolveRealYouTubeBrowserPath(); if (!File.Exists(_realYouTubeBrowserPath)) { Warn("The real YouTube GUI is not installed. Use Open manual GitHub download, then place the companion in the ClipLink folder yourself."); return; } string text = DllVersion(_realYouTubeBrowserPath); Notify("Real YouTube GUI ready", "Installed v" + text + ". It uses YouTube's actual signed-out website and auto-copies selected videos.", (NotificationType)3, 8f); MelonLogger.Msg($"Real YouTube browser ready: {_realYouTubeBrowserPath} (v{text})"); } private static void OpenRealYouTubeBrowserFolder() { _realYouTubeBrowserPath = ResolveRealYouTubeBrowserPath(); OpenFolder(File.Exists(_realYouTubeBrowserPath) ? (Path.GetDirectoryName(_realYouTubeBrowserPath) ?? _dataDirectory) : _dataDirectory, "real YouTube browser folder"); } private static void CopyLastRealYouTubeSelection() { if (!TryReadRealYouTubeSelection(out string url)) { Warn("No video has been clicked in the real YouTube GUI yet."); return; } GUIUtility.systemCopyBuffer = url; AddRecentVideo("Real YouTube " + GetYouTubeVideoId(url), url); Notify("YouTube URL copied", "The last video clicked in the real YouTube GUI is ready to paste.", (NotificationType)3, 5f); } private static void SpawnMediaPlayerWithRealYouTubeSelection() { if (!TryReadRealYouTubeSelection(out string url)) { Warn("Click a video in the real YouTube GUI first."); return; } GUIUtility.systemCopyBuffer = url; AddRecentVideo("Real YouTube " + GetYouTubeVideoId(url), url); SpawnMediaPlayer("Elijoe.MediaPlayer.Spawnable.MediaPlayer", "Media Player"); } private static bool TryReadRealYouTubeSelection(out string url) { url = string.Empty; try { _realYouTubeSelectionPath = ResolveRealYouTubeSelectionPath(); if (!File.Exists(_realYouTubeSelectionPath)) { return false; } string text = File.ReadAllText(_realYouTubeSelectionPath).Trim(); if (!IsYouTubeUrl(text)) { return false; } url = text; return true; } catch (Exception ex) { MelonLogger.Warning("Could not read the real YouTube selection: " + ex.Message); return false; } } private static void PollRealYouTubeSelection() { try { _realYouTubeSelectionPath = ResolveRealYouTubeSelectionPath(); if (!File.Exists(_realYouTubeSelectionPath)) { return; } DateTime lastWriteTimeUtc = File.GetLastWriteTimeUtc(_realYouTubeSelectionPath); if (!(lastWriteTimeUtc <= _lastRealBrowserSelectionWriteUtc)) { _lastRealBrowserSelectionWriteUtc = lastWriteTimeUtc; if (TryReadRealYouTubeSelection(out string url)) { GUIUtility.systemCopyBuffer = url; AddRecentVideo("Real YouTube " + GetYouTubeVideoId(url), url); Notify("YouTube video selected", "URL copied automatically. It is ready for ClipLink or Media Player.", (NotificationType)3, 6f); MelonLogger.Msg("Real YouTube GUI selected and copied: " + url); } } } catch (Exception ex) { MelonLogger.Warning("Could not poll the real YouTube browser selection: " + ex.Message); } } private static string ResolveRealYouTubeSelectionPath() { string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ClipLinkMedia", "selected-youtube-url.txt"); string text2 = (string.IsNullOrWhiteSpace(_realYouTubeBrowserPath) ? string.Empty : Path.Combine(Path.GetDirectoryName(_realYouTubeBrowserPath) ?? string.Empty, "selected-youtube-url.txt")); return new string[2] { text, text2 }.Where((string path) => !string.IsNullOrWhiteSpace(path) && File.Exists(path)).OrderByDescending(File.GetLastWriteTimeUtc).FirstOrDefault() ?? text; } private static void OpenYtDlpDownload() { try { Application.OpenURL("https://github.com/yt-dlp/yt-dlp/releases/latest"); MelonLogger.Msg("Opened the official yt-dlp GitHub releases page for a manual download."); Notify("ClipLink Media", "ClipLink does not download executables. Download yt-dlp.exe yourself, then put it in the folder opened by the next menu button.", (NotificationType)0, 9f); } catch (Exception ex) { FailOnMainThread("Could not open the yt-dlp download: " + ex.Message); } } private static void OpenYtDlpFolder() { OpenFolder(_dataDirectory, "ClipLink folder"); } private static void OpenDownloadsFolder() { OpenFolder(_downloadsDirectory, "downloads folder"); } private static void OpenFolder(string path, string label) { try { Directory.CreateDirectory(path); Process.Start(new ProcessStartInfo { FileName = path, UseShellExecute = true }); MelonLogger.Msg("Opened " + label + ": " + path); } catch (Exception ex) { FailOnMainThread("Could not open the " + label + ": " + ex.Message); } } private static void RefreshDownloadsPage() { //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) if (_downloadsPage == null) { return; } _downloadsPage.RemoveAll(); _downloadsPage.CreateFunction("Refresh MP4 library", Color.green, (Action)RefreshAndOpenDownloadsPage); _downloadsPage.CreateFunction("Open downloads folder", Color.cyan, (Action)OpenDownloadsFolder); try { Directory.CreateDirectory(_downloadsDirectory); FileInfo[] files = (from file in new DirectoryInfo(_downloadsDirectory).GetFiles("*.mp4", SearchOption.TopDirectoryOnly) orderby file.LastWriteTimeUtc descending select file).Take(20).ToArray(); if (files.Length == 0) { _downloadsPage.CreateFunction("No downloaded MP4 files", Color.gray, (Action)delegate { Warn("Use Download MP4 only first."); }); return; } long totalBytes = files.Sum((FileInfo file) => file.Length); _downloadsPage.CreateFunction($"Showing {files.Length} MP4s ({FormatBytes(totalBytes)})", Color.white, (Action)delegate { Notify("MP4 library", $"{files.Length} recent files use {FormatBytes(totalBytes)}.", (NotificationType)0, 4f); }); FileInfo[] array = files; foreach (FileInfo fileInfo in array) { string path = fileInfo.FullName; ((Element)_downloadsPage.CreateFunction(ShortMenuText(fileInfo.Name + " (" + FormatBytes(fileInfo.Length) + ")", 64), Color.white, (Action)delegate { CopyDownloadedPath(path); })).SetTooltip($"Modified: {fileInfo.LastWriteTime:F}\n{path}"); } } catch (Exception ex) { Exception ex2 = ex; Exception ex3 = ex2; _downloadsPage.CreateFunction("Could not scan downloads", Color.red, (Action)delegate { Warn(ex3.Message); }); MelonLogger.Warning("Could not refresh MP4 library: " + ex3.Message); } } private static void RefreshAndOpenDownloadsPage() { RefreshDownloadsPage(); if (_downloadsPage != null) { Menu.OpenPage(_downloadsPage); } } private static void CopyDownloadedPath(string path) { if (!File.Exists(path)) { Warn("That downloaded MP4 no longer exists. Refresh the library."); return; } GUIUtility.systemCopyBuffer = path; Notify("MP4 file path copied", ShortMenuText(Path.GetFileName(path), 100), (NotificationType)3, 4f); } private static void CopyDownloadsPath() { Directory.CreateDirectory(_downloadsDirectory); GUIUtility.systemCopyBuffer = _downloadsDirectory; Notify("ClipLink Media", "Downloads folder path copied.", (NotificationType)3, 3f); } private static void InspectCopiedUrl() { string text = GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty; if (IsYouTubeUrl(text)) { Notify("Copied YouTube URL", "Video ID: " + GetYouTubeVideoId(text), (NotificationType)3, 5f); } else if (IsHttpsUrl(text)) { Uri uri = new Uri(text); Notify("Copied HTTPS URL", "Host: " + uri.Host, (NotificationType)0, 5f); } else { Warn("The clipboard does not contain a supported web URL."); } } private static void OpenCopiedYouTubeUrl() { string text = GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty; if (!IsYouTubeUrl(text)) { Warn("Copy a youtube.com or youtu.be video URL first."); return; } try { Application.OpenURL(text); Notify("ClipLink Media", "Opened the copied YouTube video.", (NotificationType)0, 3f); } catch (Exception ex) { FailOnMainThread("Could not open the copied video: " + ex.Message); } } private static void StartClipboardJob() { TryStartClipboardJob(uploadPublicly: true); } private static void StartLocalDownloadJob() { TryStartClipboardJob(uploadPublicly: false); } private static void RetryLastFailedUpload() { if (!_rightsConfirmed) { Warn("Turn on 'I own / have permission' before retrying an upload."); return; } string savedPath = _settings.LastFailedUploadPath; if (string.IsNullOrWhiteSpace(savedPath) || !File.Exists(savedPath)) { Warn("There is no saved failed upload to retry."); return; } if (Interlocked.CompareExchange(ref _jobRunning, 1, 0) != 0) { Warn("ClipLink Media is already working on a video."); return; } string retention = (IsSupportedRetention(_settings.LastFailedUploadRetention) ? _settings.LastFailedUploadRetention : _settings.LitterboxRetention); string sourceUrl = _settings.LastFailedUploadSourceUrl; CancellationTokenSource cancellation = new CancellationTokenSource(); lock (JobGate) { _jobCancellation = cancellation; } _jobStartedUtc = DateTimeOffset.UtcNow; _jobStatus = "Retrying saved upload: " + Path.GetFileName(savedPath); Notify("ClipLink upload retry", "Uploading the saved MP4 for " + RetentionDisplay(retention) + "...", (NotificationType)0, 5f); Task.Run(delegate { ProcessSavedUploadJob(savedPath, sourceUrl, retention, cancellation); }); } private static void TryStartClipboardJob(bool uploadPublicly) { TryStartJob(GUIUtility.systemCopyBuffer?.Trim() ?? string.Empty, uploadPublicly); } private static void StartLastPublicJob() { TryStartJob(_settings.LastSourceUrl, uploadPublicly: true); } private static void StartLastLocalJob() { TryStartJob(_settings.LastSourceUrl, uploadPublicly: false); } private static void TryStartJob(string url, bool uploadPublicly, bool fromQueue = false) { if (!_rightsConfirmed) { if (fromQueue) { _queueModeActive = false; } Warn("Turn on 'I own / have permission' before downloading or uploading."); return; } if (!IsYouTubeUrl(url)) { if (fromQueue) { _queueModeActive = false; } Warn("No saved YouTube video is ready. Copy or select one first."); return; } if (Interlocked.CompareExchange(ref _jobRunning, 1, 0) != 0) { if (fromQueue) { _queueModeActive = false; } Warn("ClipLink Media is already working on a video."); return; } _ytDlpPath = ResolveYtDlpPath(); CancellationTokenSource cancellation = new CancellationTokenSource(); lock (JobGate) { _jobCancellation = cancellation; } string retention = _settings.LitterboxRetention; string quality = _settings.VideoQuality; _settings.LastSourceUrl = url; SaveSettings(); string value = (uploadPublicly ? ("public " + RetentionDisplay(retention) + " link") : "local MP4"); _jobStartedUtc = DateTimeOffset.UtcNow; _jobStatus = $"Downloading {quality} video {GetYouTubeVideoId(url)} for {value}"; MelonLogger.Msg($"Starting {quality} {value} job: {url}"); Notify("ClipLink Media", $"Downloading {quality} video for a {value}...", (NotificationType)0, 4f); Task.Run(delegate { ProcessVideoJob(url, uploadPublicly, retention, quality, cancellation, fromQueue); }); } private static void CopyLastSourceUrl() { if (!IsYouTubeUrl(_settings.LastSourceUrl)) { Warn("No saved YouTube video is ready yet."); return; } GUIUtility.systemCopyBuffer = _settings.LastSourceUrl; Notify("ClipLink Media", "Last YouTube link copied.", (NotificationType)3, 3f); } private static void OpenLastSourceUrl() { if (!IsYouTubeUrl(_settings.LastSourceUrl)) { Warn("No saved YouTube video is ready yet."); return; } try { Application.OpenURL(_settings.LastSourceUrl); Notify("ClipLink Media", "Opened the last YouTube video.", (NotificationType)0, 3f); } catch (Exception ex) { FailOnMainThread("Could not open the last YouTube video: " + ex.Message); } } private static void CancelCurrentJob() { bool queueModeActive = _queueModeActive; _queueModeActive = false; lock (JobGate) { if (Volatile.Read(in _jobRunning) == 0 || _jobCancellation == null) { Warn("There is no download or upload to cancel."); return; } _jobCancellation.Cancel(); } MelonLogger.Msg("Cancellation requested for the current video job."); Notify("ClipLink Media", queueModeActive ? "Cancelling the current job and stopping the batch queue..." : "Cancelling the current job...", (NotificationType)1, 4f); } private static void ProcessSavedUploadJob(string savedPath, string sourceUrl, string retention, CancellationTokenSource cancellation) { CancellationToken token = cancellation.Token; try { long length = new FileInfo(savedPath).Length; string publicUrl = UploadToLitterbox(savedPath, retention, token); token.ThrowIfCancellationRequested(); QueuePublicUploadCompletion(publicUrl, sourceUrl, retention, length, clearFailedUpload: true); } catch (OperationCanceledException) when (token.IsCancellationRequested) { _jobStatus = "Saved upload retry cancelled"; MainThreadActions.Enqueue(delegate { Notify("ClipLink Media", "Saved upload retry cancelled. The MP4 is still in Downloads.", (NotificationType)1, 5f); }); } catch (Exception ex2) { Exception ex3 = ex2; Exception ex4 = ex3; _jobStatus = "Saved upload retry failed: " + LastPart(ex4.Message, 120); MelonLogger.Error($"Saved upload retry failed: {ex4}"); MainThreadActions.Enqueue(delegate { Notify("Upload still unavailable", LastPart(ex4.Message, 130) + " The MP4 remains saved; retry later.", (NotificationType)2, 8f); }); } finally { lock (JobGate) { if (_jobCancellation == cancellation) { _jobCancellation = null; } } cancellation.Dispose(); Interlocked.Exchange(ref _jobRunning, 0); } } private static void CopyLastPublicUrl() { if (string.IsNullOrWhiteSpace(_lastPublicUrl)) { Warn("No public URL is ready yet."); return; } GUIUtility.systemCopyBuffer = _lastPublicUrl; MelonLogger.Msg("Copied public URL: " + _lastPublicUrl); Notify("ClipLink Media", "Public MP4 URL copied again.", (NotificationType)3, 3f); } private static void OpenLastPublicUrl() { if (string.IsNullOrWhiteSpace(_lastPublicUrl)) { Warn("No public URL is ready yet."); return; } try { Application.OpenURL(_lastPublicUrl); Notify("ClipLink Media", "Opened the last public MP4 URL.", (NotificationType)0, 3f); } catch (Exception ex) { FailOnMainThread("Could not open the public URL: " + ex.Message); } } private static void CheckSetup() { _ytDlpPath = ResolveYtDlpPath(); Directory.CreateDirectory(_dataDirectory); Directory.CreateDirectory(_downloadsDirectory); bool flag = File.Exists(_ytDlpPath); bool flag2 = typeof(NetworkPlayer).Assembly != null; string text = $"yt-dlp: {(flag ? "ready" : "missing")}; Fusion: {(flag2 ? "ready" : "missing")}; quality: {_settings.VideoQuality}; expiry: {RetentionDisplay(_settings.LitterboxRetention)}."; MelonLogger.Msg("Setup check - " + text + " Data: " + _dataDirectory); Notify("ClipLink setup check", text, (NotificationType)((!(flag && flag2)) ? 1 : 3), 7f); } private static void ShowStatistics() { string value = (_settings.LastJobCompletedUtc.HasValue ? _settings.LastJobCompletedUtc.Value.LocalDateTime.ToString("g") : "never"); string text = $"Public links: {_settings.TotalPublicLinks}; local downloads: {_settings.TotalLocalDownloads}; processed: {FormatBytes(_settings.TotalBytesProcessed)}; last: {value}."; MelonLogger.Msg("Usage statistics - " + text); Notify("ClipLink statistics", text, (NotificationType)0, 8f); } private static void ShowJobStatus() { string value = ((Volatile.Read(in _jobRunning) != 0 && _jobStartedUtc.HasValue) ? ("; elapsed: " + FormatDuration(DateTimeOffset.UtcNow - _jobStartedUtc.Value)) : string.Empty); string value2 = (_queueModeActive ? $"active {_settings.JobQueue.Count} remaining" : $"stopped {_settings.JobQueue.Count} pending"); string text = $"Job: {_jobStatus}{value}; queue: {value2}."; MelonLogger.Msg(text); Notify("ClipLink job status", LastPart(text, 220), (NotificationType)((Volatile.Read(in _jobRunning) == 0) ? 3 : 0), 8f); } private static void CopySetupReport() { //IL_029c: Unknown result type (might be due to invalid IL or missing references) //IL_02a1: Unknown result type (might be due to invalid IL or missing references) _ytDlpPath = ResolveYtDlpPath(); string ytDlpVersion = GetYtDlpVersion(); string newLine = Environment.NewLine; string[] obj = new string[23] { "ClipLink Media setup report", "Version: 5.11.0", "yt-dlp: " + ytDlpVersion, "yt-dlp path: " + _ytDlpPath, $"Fusion assembly: {typeof(NetworkPlayer).Assembly.GetName().Version}", "MP4 quality: " + _settings.VideoQuality, "Public-link expiry: " + RetentionDisplay(_settings.LitterboxRetention), $"Recent videos: {_settings.RecentVideos.Count}", $"Recent searches: {_settings.RecentSearches.Count}", $"Favorite videos: {_settings.Favorites.Count}", $"Queued videos: {_settings.JobQueue.Count}", $"Queue active: {_queueModeActive}", "Job status: " + _jobStatus, $"Personal note saved: {!string.IsNullOrWhiteSpace(_settings.PersonalNote)}", $"Low-FPS warnings: {_settings.LowFpsAlertsEnabled} below {_settings.LowFpsThreshold}", $"Measured FPS: {_measuredFps:0.0}", null, null, null, null, null, null, null }; Scene activeScene = SceneManager.GetActiveScene(); obj[16] = "Scene: " + ((Scene)(ref activeScene)).name; obj[17] = $"Saved public URLs: {LinkHistory.Count}"; obj[18] = $"Local downloads completed: {_settings.TotalLocalDownloads}"; obj[19] = $"Public links completed: {_settings.TotalPublicLinks}"; obj[20] = "Total data processed: " + FormatBytes(_settings.TotalBytesProcessed); obj[21] = "Data folder: " + _dataDirectory; obj[22] = "Downloads folder: " + _downloadsDirectory; MelonLogger.Msg(GUIUtility.systemCopyBuffer = string.Join(newLine, obj)); Notify("ClipLink setup report", "Detailed setup report copied to the clipboard.", (NotificationType)3, 5f); } private static string GetYtDlpVersion() { if (!File.Exists(_ytDlpPath)) { return "missing"; } try { using Process process = Process.Start(new ProcessStartInfo { FileName = _ytDlpPath, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, ArgumentList = { "--version" } }) ?? throw new InvalidOperationException("yt-dlp did not start."); if (!process.WaitForExit(3000)) { process.Kill(entireProcessTree: true); return "timeout"; } string text = process.StandardOutput.ReadToEnd().Trim(); return string.IsNullOrWhiteSpace(text) ? "unknown" : text; } catch (Exception ex) { return "error (" + ex.Message + ")"; } } private static void ProcessVideoJob(string youtubeUrl, bool uploadPublicly, string retention, string quality, CancellationTokenSource cancellation, bool fromQueue) { string text = null; string mp4Path = null; bool flag = false; CancellationToken token = cancellation.Token; try { text = CreateJobDirectory(); mp4Path = DownloadVideo(youtubeUrl, text, quality, token); token.ThrowIfCancellationRequested(); long fileBytes = new FileInfo(mp4Path).Length; string fileSize = FormatBytes(fileBytes); if (!uploadPublicly) { _jobStatus = "Saving downloaded MP4"; string savedPath = SaveDownloadedVideo(mp4Path, token); token.ThrowIfCancellationRequested(); MainThreadActions.Enqueue(delegate { _settings.TotalLocalDownloads++; _settings.TotalBytesProcessed += fileBytes; _settings.LastJobCompletedUtc = DateTimeOffset.UtcNow; SaveSettings(); _jobStatus = "Completed local download: " + Path.GetFileName(savedPath); RefreshDownloadsPage(); MelonLogger.Msg("Local MP4 saved: " + savedPath); Notify("Download complete", $"Saved {quality} {Path.GetFileName(savedPath)} ({fileSize}) in ClipLink Downloads.", (NotificationType)3, 7f); }); } else { MainThreadActions.Enqueue(delegate { _jobStatus = "Uploading " + fileSize + " MP4 for " + RetentionDisplay(retention); MelonLogger.Msg($"Download finished: {Path.GetFileName(mp4Path)} ({fileSize}). Uploading for {RetentionDisplay(retention)}..."); Notify("Download finished", fileSize + " MP4 ready. Uploading for " + RetentionDisplay(retention) + "...", (NotificationType)0, 5f); }); flag = true; string publicUrl = UploadToLitterbox(mp4Path, retention, token); token.ThrowIfCancellationRequested(); QueuePublicUploadCompletion(publicUrl, youtubeUrl, retention, fileBytes, clearFailedUpload: false); } } catch (OperationCanceledException) when (token.IsCancellationRequested) { _jobStatus = "Job cancelled"; MelonLogger.Msg("Video job cancelled by the user."); MainThreadActions.Enqueue(delegate { Notify("ClipLink Media", "Download/upload job cancelled.", (NotificationType)1, 4f); }); } catch (Exception ex2) { Exception ex3 = ex2; Exception ex4 = ex3; _jobStatus = "Job failed: " + LastPart(ex4.Message, 120); MelonLogger.Error($"Video job failed: {ex4}"); if (uploadPublicly && flag && !string.IsNullOrWhiteSpace(mp4Path) && File.Exists(mp4Path)) { try { string savedPath2 = SaveDownloadedVideo(mp4Path, CancellationToken.None); MainThreadActions.Enqueue(delegate { _settings.LastFailedUploadPath = savedPath2; _settings.LastFailedUploadSourceUrl = youtubeUrl; _settings.LastFailedUploadRetention = retention; SaveSettings(); RefreshDownloadsPage(); _jobStatus = "Upload failed; MP4 saved: " + Path.GetFileName(savedPath2); Notify("Upload failed - MP4 saved", LastPart(ex4.Message, 105) + " Use 'Retry saved failed upload'; no re-download needed.", (NotificationType)2, 10f); }); MelonLogger.Warning("Upload failed, so the downloaded MP4 was preserved at " + savedPath2); return; } catch (Exception ex5) { MelonLogger.Error($"Could not preserve the downloaded MP4 after upload failure: {ex5}"); FailOnMainThread(ex4.Message + " The downloaded MP4 also could not be saved: " + ex5.Message); return; } } FailOnMainThread(ex4.Message); } finally { if (!string.IsNullOrEmpty(text)) { try { Directory.Delete(text, recursive: true); } catch (Exception ex6) { MelonLogger.Warning("Could not remove temporary video: " + ex6.Message); } } lock (JobGate) { if (_jobCancellation == cancellation) { _jobCancellation = null; } } cancellation.Dispose(); Interlocked.Exchange(ref _jobRunning, 0); if (fromQueue && _queueModeActive) { MainThreadActions.Enqueue(ContinueQueue); } } } private static void QueuePublicUploadCompletion(string publicUrl, string sourceUrl, string retention, long fileBytes, bool clearFailedUpload) { DateTimeOffset createdUtc = DateTimeOffset.UtcNow; DateTimeOffset expiresUtc = createdUtc.Add(RetentionDuration(retention)); MainThreadActions.Enqueue(delegate { _lastPublicUrl = publicUrl; GUIUtility.systemCopyBuffer = publicUrl; LinkHistory.RemoveAll((LinkHistoryEntry entry) => string.Equals(entry.DirectUrl, publicUrl, StringComparison.OrdinalIgnoreCase)); LinkHistory.Insert(0, new LinkHistoryEntry { DirectUrl = publicUrl, SourceUrl = sourceUrl, CreatedUtc = createdUtc, ExpiresUtc = expiresUtc }); while (LinkHistory.Count > 8) { LinkHistory.RemoveAt(LinkHistory.Count - 1); } _settings.TotalPublicLinks++; _settings.TotalBytesProcessed += fileBytes; _settings.LastJobCompletedUtc = createdUtc; if (clearFailedUpload) { _settings.LastFailedUploadPath = string.Empty; _settings.LastFailedUploadSourceUrl = string.Empty; _settings.LastFailedUploadRetention = string.Empty; } SaveSettings(); RefreshHistoryPage(); _jobStatus = "Completed public link: " + publicUrl; MelonLogger.Msg("Public MP4 URL copied: " + publicUrl); Notify("ClipLink Media finished", $"{RetentionDisplay(retention)} MP4 URL copied. Expires {expiresUtc.LocalDateTime:g}.", (NotificationType)3, 8f); }); } private static string DownloadVideo(string youtubeUrl, string jobDirectory, string quality, CancellationToken token) { if (!File.Exists(_ytDlpPath)) { throw new FileNotFoundException("yt-dlp.exe is missing. Download it from the official GitHub link in BoneMenu and put it in the ClipLinkMedia folder.", _ytDlpPath); } (string, string) tuple = RunYtDlpAttempt(youtubeUrl, Path.Combine(jobDirectory, "default-client"), quality, null, token); if (!string.IsNullOrWhiteSpace(tuple.Item1)) { return ValidateDownloadedMp4(tuple.Item1); } MelonLogger.Warning("Default yt-dlp client attempt failed; retrying with Android VR and Safari clients. " + LastPart(tuple.Item2, 350)); (string, string) tuple2 = RunYtDlpAttempt(youtubeUrl, Path.Combine(jobDirectory, "fallback-client"), quality, "youtube:player_client=android_vr,web_safari", token); if (!string.IsNullOrWhiteSpace(tuple2.Item1)) { return ValidateDownloadedMp4(tuple2.Item1); } throw new InvalidOperationException("Download failed after two YouTube client attempts: " + LastPart(tuple2.Item2, 420)); } private static (string? path, string error) RunYtDlpAttempt(string youtubeUrl, string attemptDirectory, string quality, string? extractorArgs, CancellationToken token) { Directory.CreateDirectory(attemptDirectory); string text = Path.Combine(attemptDirectory, "download-result.txt"); ProcessStartInfo processStartInfo = new ProcessStartInfo { FileName = _ytDlpPath, UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, WorkingDirectory = attemptDirectory }; processStartInfo.ArgumentList.Add("--no-playlist"); processStartInfo.ArgumentList.Add("--windows-filenames"); processStartInfo.ArgumentList.Add("--newline"); if (!string.IsNullOrWhiteSpace(extractorArgs)) { processStartInfo.ArgumentList.Add("--extractor-args"); processStartInfo.ArgumentList.Add(extractorArgs); } processStartInfo.ArgumentList.Add("-f"); processStartInfo.ArgumentList.Add(VideoFormatSelector(quality)); processStartInfo.ArgumentList.Add("--max-filesize"); processStartInfo.ArgumentList.Add("1000M"); processStartInfo.ArgumentList.Add("--print-to-file"); processStartInfo.ArgumentList.Add("after_move:%(filepath)s"); processStartInfo.ArgumentList.Add(text); processStartInfo.ArgumentList.Add("-o"); processStartInfo.ArgumentList.Add(Path.Combine(attemptDirectory, "%(title).80B [%(id)s].%(ext)s")); processStartInfo.ArgumentList.Add(youtubeUrl); Process process = Process.Start(processStartInfo) ?? throw new InvalidOperationException("yt-dlp did not start."); try { Task task = process.StandardOutput.ReadToEndAsync(); Task task2 = process.StandardError.ReadToEndAsync(); using (token.Register(delegate { try { if (!process.HasExited) { process.Kill(entireProcessTree: true); } } catch { } })) { process.WaitForExit(); Task.WaitAll(task, task2); token.ThrowIfCancellationRequested(); if (process.ExitCode != 0) { return (path: null, error: string.IsNullOrWhiteSpace(task2.Result) ? task.Result.Trim() : task2.Result.Trim()); } string text2 = ((!File.Exists(text)) ? null : File.ReadLines(text).LastOrDefault((string line) => !string.IsNullOrWhiteSpace(line))?.Trim()); if (string.IsNullOrEmpty(text2) || !File.Exists(text2)) { return (path: null, error: "The download finished but no MP4 file was found."); } return (path: text2, error: string.Empty); } } finally { if (process != null) { ((IDisposable)process).Dispose(); } } } private static string ValidateDownloadedMp4(string mp4Path) { if (!string.Equals(Path.GetExtension(mp4Path), ".mp4", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException("YouTube did not provide a compatible MP4 for this video."); } if (new FileInfo(mp4Path).Length > 1000000000) { throw new InvalidOperationException("The MP4 is larger than ClipLink Media's 1 GB safety limit."); } return mp4Path; } private static string SaveDownloadedVideo(string mp4Path, CancellationToken token) { Directory.CreateDirectory(_downloadsDirectory); string text = UniqueFilePath(_downloadsDirectory, Path.GetFileName(mp4Path)); try { using FileStream fileStream = File.OpenRead(mp4Path); using FileStream destination = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None); fileStream.CopyToAsync(destination, 1048576, token).GetAwaiter().GetResult(); token.ThrowIfCancellationRequested(); return text; } catch { try { if (File.Exists(text)) { File.Delete(text); } } catch { } throw; } } private static string UniqueFilePath(string directory, string fileName) { string text = (string.IsNullOrWhiteSpace(fileName) ? $"ClipLink-{DateTime.Now:yyyyMMdd-HHmmss}.mp4" : fileName); string text2 = Path.Combine(directory, text); if (!File.Exists(text2)) { return text2; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); string extension = Path.GetExtension(text); for (int i = 2; i < 1000; i++) { text2 = Path.Combine(directory, $"{fileNameWithoutExtension} ({i}){extension}"); if (!File.Exists(text2)) { return text2; } } return Path.Combine(directory, $"{fileNameWithoutExtension}-{Guid.NewGuid():N}{extension}"); } private static string UploadToLitterbox(string mp4Path, string retention, CancellationToken token) { string fileName = $"cliplink-{Guid.NewGuid():N}.mp4"; string text = "Litterbox did not return a URL."; for (int i = 1; i <= 3; i++) { token.ThrowIfCancellationRequested(); try { using HttpClient httpClient = CreateUploadHttpClient(); using MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent(); multipartFormDataContent.Add(new StringContent("fileupload"), "reqtype"); multipartFormDataContent.Add(new StringContent(retention), "time"); using StreamContent streamContent = new StreamContent(File.OpenRead(mp4Path)); streamContent.Headers.ContentType = new MediaTypeHeaderValue("video/mp4"); streamContent.Headers.ContentLength = new FileInfo(mp4Path).Length; multipartFormDataContent.Add(streamContent, "fileToUpload", fileName); using HttpResponseMessage httpResponseMessage = httpClient.PostAsync("https://litterbox.catbox.moe/resources/internals/api.php", multipartFormDataContent, token).GetAwaiter().GetResult(); string text2 = httpResponseMessage.Content.ReadAsStringAsync(token).GetAwaiter().GetResult() .Trim(); if (httpResponseMessage.IsSuccessStatusCode && Uri.TryCreate(text2, UriKind.Absolute, out Uri result) && result.Scheme == Uri.UriSchemeHttps && (result.Host.Equals("catbox.moe", StringComparison.OrdinalIgnoreCase) || result.Host.EndsWith(".catbox.moe", StringComparison.OrdinalIgnoreCase))) { return result.AbsoluteUri; } string value = (text2.StartsWith("<", StringComparison.Ordinal) ? "Litterbox returned an HTML server/WAF error instead of a URL." : LastPart(text2, 220)); text = $"attempt {i}/3, HTTP {httpResponseMessage.StatusCode}: {value}"; } catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex2) { text = $"attempt {i}/3: {LastPart(ex2.Message, 240)}"; } MelonLogger.Warning("Litterbox upload " + text); if (i < 3) { Task.Delay(TimeSpan.FromSeconds(i * 2), token).GetAwaiter().GetResult(); } } throw new InvalidOperationException("Litterbox upload failed after 3 attempts (" + text + ")."); } private static void Warn(string message) { MelonLogger.Warning(message); Notify("ClipLink Media", message, (NotificationType)1, 5f); } private static void FailOnMainThread(string message) { string shortMessage = LastPart(message, 180); MainThreadActions.Enqueue(delegate { Notify("ClipLink Media error", shortMessage, (NotificationType)2, 7f); }); } private static void Notify(string title, string message, NotificationType type, float seconds) { //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) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_0037: Expected O, but got Unknown Notifier.Send(new Notification { Title = new NotificationText(title), Message = new NotificationText(message), ShowTitleOnPopup = true, PopupLength = seconds, Type = type }); } private static bool IsYouTubeUrl(string? value) { if (!Uri.TryCreate(value, UriKind.Absolute, out Uri result)) { return false; } string text = result.Host.TrimEnd('.').ToLowerInvariant(); if (!(text == "youtube.com") && !text.EndsWith(".youtube.com", StringComparison.Ordinal)) { return text == "youtu.be"; } return true; } private static bool IsHttpsUrl(string? value) { if (Uri.TryCreate(value, UriKind.Absolute, out Uri result)) { return result.Scheme == Uri.UriSchemeHttps; } return false; } private static bool IsSupportedRetention(string? value) { switch (value) { case "1h": case "12h": case "24h": case "72h": return true; default: return false; } } private static bool IsSupportedVideoQuality(string? value) { switch (value) { case "Best": case "720p": case "480p": case "360p": return true; default: return false; } } private static string VideoFormatSelector(string quality) { return quality switch { "720p" => "b[ext=mp4][height<=720]", "480p" => "b[ext=mp4][height<=480]", "360p" => "b[ext=mp4][height<=360]", _ => "b[ext=mp4]", }; } private static string RetentionDisplay(string retention) { return retention switch { "1h" => "1 hour", "12h" => "12 hours", "24h" => "24 hours", _ => "72 hours", }; } private static TimeSpan RetentionDuration(string retention) { return retention switch { "1h" => TimeSpan.FromHours(1.0), "12h" => TimeSpan.FromHours(12.0), "24h" => TimeSpan.FromHours(24.0), _ => TimeSpan.FromHours(72.0), }; } private static string GetYouTubeVideoId(string sourceUrl) { if (!Uri.TryCreate(sourceUrl, UriKind.Absolute, out Uri result)) { return "video"; } if (result.Host.Equals("youtu.be", StringComparison.OrdinalIgnoreCase)) { return result.AbsolutePath.Trim('/'); } Match match = Regex.Match(result.Query, "(?:^|[?&])v=(?[A-Za-z0-9_-]{11})(?:&|$)"); if (!match.Success) { return "video"; } return match.Groups["id"].Value; } private static string FormatBytes(long bytes) { if (bytes < 1073741824) { if (bytes < 1048576) { if (bytes < 1024) { return $"{bytes} B"; } return $"{(double)bytes / 1024.0:0.0} KB"; } return $"{(double)bytes / 1048576.0:0.0} MB"; } return $"{(double)bytes / 1073741824.0:0.00} GB"; } private static string FormatDuration(TimeSpan duration) { if (!(duration.TotalHours >= 1.0)) { if (!(duration.TotalMinutes >= 1.0)) { return $"{Math.Max(0, duration.Seconds)}s"; } return $"{duration.Minutes}m {duration.Seconds}s"; } return $"{(int)duration.TotalHours}h {duration.Minutes}m {duration.Seconds}s"; } private static string JsonText(JsonElement root, string propertyName, string fallback) { if (!root.TryGetProperty(propertyName, out var value) || value.ValueKind == JsonValueKind.Null) { return fallback; } if (value.ValueKind != JsonValueKind.String) { return value.ToString(); } return value.GetString() ?? fallback; } private static long? JsonInt64(JsonElement root, string propertyName) { if (!root.TryGetProperty(propertyName, out var value) || value.ValueKind != JsonValueKind.Number) { return null; } if (value.TryGetInt64(out var value2)) { return value2; } if (!value.TryGetDouble(out var value3)) { return null; } return (long)Math.Max(0.0, value3); } private static string ResolveYtDlpPath() { string text = Path.Combine(_dataDirectory, "yt-dlp.exe"); if (File.Exists(text)) { return text; } string userDataDirectory = MelonEnvironment.UserDataDirectory; if (Directory.Exists(userDataDirectory)) { try { string text2 = Directory.EnumerateFiles(userDataDirectory, "yt-dlp.exe", SearchOption.AllDirectories).FirstOrDefault((string path) => path.Contains("ClipLinkMedia", StringComparison.OrdinalIgnoreCase) || path.Contains("ClipLink_Media", StringComparison.OrdinalIgnoreCase)); if (!string.IsNullOrEmpty(text2)) { return text2; } } catch (Exception ex) { MelonLogger.Warning("Could not search UserData for yt-dlp: " + ex.Message); } } return Path.Combine(userDataDirectory, "yt-dlp.exe"); } private static string ResolveRealYouTubeBrowserPath() { string text = Path.Combine(_dataDirectory, "ClipLinkYouTubeBrowser.exe"); if (File.Exists(text)) { return text; } string path = Path.Combine(_dataDirectory, "youtube-browser-path.txt"); try { if (File.Exists(path)) { string text2 = Environment.ExpandEnvironmentVariables(File.ReadAllText(path).Trim().Trim('"')); if (File.Exists(text2)) { return text2; } } } catch (Exception ex) { MelonLogger.Warning("Could not read YouTube browser path: " + ex.Message); } string userDataDirectory = MelonEnvironment.UserDataDirectory; if (Directory.Exists(userDataDirectory)) { try { string text3 = Directory.EnumerateFiles(userDataDirectory, "ClipLinkYouTubeBrowser.exe", SearchOption.AllDirectories).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(text3)) { return text3; } } catch (Exception ex2) { MelonLogger.Warning("Could not search UserData for the real YouTube browser: " + ex2.Message); } } string text4 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads", "ClipLinkYouTubeBrowser.exe"); if (!File.Exists(text4)) { return text; } return text4; } private static string CreateJobDirectory() { string text = ChooseJobsRoot(); Directory.CreateDirectory(text); string text2 = Path.Combine(text, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(text2); return text2; } private static string ChooseJobsRoot() { string text = Path.Combine(Path.GetTempPath(), "ClipLinkMediaJobs"); try { string text2 = Path.GetPathRoot(text) ?? string.Empty; if (!string.IsNullOrEmpty(text2) && new DriveInfo(text2).AvailableFreeSpace >= 1258291200) { return text; } } catch { } DriveInfo driveInfo = (from drive in DriveInfo.GetDrives() where drive.IsReady && drive.DriveType == DriveType.Fixed && drive.AvailableFreeSpace >= 1258291200 orderby drive.AvailableFreeSpace descending select drive).FirstOrDefault(); if (driveInfo != null) { return Path.Combine(driveInfo.RootDirectory.FullName, "ClipLinkMediaJobs"); } return text; } private static HttpClient CreateUploadHttpClient() { HttpClient httpClient = new HttpClient(new HttpClientHandler { UseCookies = false, AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.All }); httpClient.Timeout = TimeSpan.FromMinutes(15.0); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/127 Safari/537.36 ClipLinkMedia/5.11.0"); httpClient.DefaultRequestHeaders.Accept.ParseAdd("text/plain"); httpClient.DefaultRequestHeaders.Accept.ParseAdd("*/*"); httpClient.DefaultRequestHeaders.Referrer = new Uri("https://litterbox.catbox.moe/"); httpClient.DefaultRequestHeaders.ExpectContinue = false; httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Origin", "https://litterbox.catbox.moe"); return httpClient; } private static HttpClient CreateYouTubeHttpClient() { HttpClient httpClient = new HttpClient(new HttpClientHandler { UseCookies = false, AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.All }); httpClient.Timeout = TimeSpan.FromSeconds(30.0); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/127 Safari/537.36"); httpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.9"); return httpClient; } private static HttpClient CreateDiagnosticsHttpClient() { HttpClient httpClient = new HttpClient(new HttpClientHandler { AllowAutoRedirect = true, AutomaticDecompression = DecompressionMethods.All }); httpClient.Timeout = TimeSpan.FromSeconds(45.0); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("ClipLinkMedia/5.11.0"); httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/vnd.github+json"); return httpClient; } private static string ShortMenuText(string value, int maxLength) { string text = Regex.Replace(value ?? string.Empty, "\\s+", " ").Trim(); if (text.Length <= maxLength) { return text; } return text.Substring(0, Math.Max(1, maxLength - 3)) + "..."; } private static string LastPart(string value, int maxLength) { if (string.IsNullOrWhiteSpace(value)) { return "Unknown error."; } if (value.Length > maxLength) { return value.Substring(value.Length - maxLength); } return value; } } public sealed class ClipLinkSettings { public string LitterboxRetention { get; set; } = "72h"; public string VideoQuality { get; set; } = "Best"; public string LastSourceUrl { get; set; } = string.Empty; public string LastFailedUploadPath { get; set; } = string.Empty; public string LastFailedUploadSourceUrl { get; set; } = string.Empty; public string LastFailedUploadRetention { get; set; } = string.Empty; public List RecentSearches { get; set; } = new List(); public List RecentVideos { get; set; } = new List(); public List Favorites { get; set; } = new List(); public List JobQueue { get; set; } = new List(); public string PersonalNote { get; set; } = string.Empty; public bool LowFpsAlertsEnabled { get; set; } public int LowFpsThreshold { get; set; } = 45; public int TotalPublicLinks { get; set; } public int TotalLocalDownloads { get; set; } public long TotalBytesProcessed { get; set; } public DateTimeOffset? LastJobCompletedUtc { get; set; } } public sealed class ClipLinkPresenceInfo { public string Version { get; } public DateTimeOffset LastSeenUtc { get; } public ClipLinkPresenceInfo(string version, DateTimeOffset lastSeenUtc) { Version = version; LastSeenUtc = lastSeenUtc; } } public sealed class ClipLinkPresenceData : INetSerializable { public string Version { get; private set; } = string.Empty; public bool RequestReply { get; private set; } public ClipLinkPresenceData() { } public ClipLinkPresenceData(string version, bool requestReply) { Version = version; RequestReply = requestReply; } public void Serialize(INetSerializer serializer) { string version = Version; bool requestReply = RequestReply; serializer.SerializeValue(ref version); serializer.SerializeValue(ref requestReply); if (serializer.IsReader) { Version = version; RequestReply = requestReply; } } } public sealed class ClipLinkPresenceMessage : ModuleMessageHandler { protected override void OnHandleMessage(ReceivedMessage received) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) Core.ReceiveClipLinkPresence(received); } } public sealed class RecentVideoEntry { public string Title { get; set; } = string.Empty; public string Url { get; set; } = string.Empty; public DateTimeOffset SelectedUtc { get; set; } } public sealed class LinkHistoryEntry { public string DirectUrl { get; set; } = string.Empty; public string SourceUrl { get; set; } = string.Empty; public DateTimeOffset CreatedUtc { get; set; } public DateTimeOffset ExpiresUtc { get; set; } } public sealed class OwnerTagElement : IPopupLayoutElement { private readonly RigNameTag _tag = new RigNameTag { Username = "OWNER", Color = Color.white, CrownVisible = false, Visible = true }; public int Priority => -100; public Transform Transform => _tag.Transform; public bool Visible { get { return _tag.Visible; } set { _tag.Visible = value; } } public void Spawn(Transform parent) { _tag.Spawn(parent); } public void Despawn() { _tag.Despawn(); } } public sealed class YouTubeSearchResult { public string Id { get; } public string Title { get; } public string Url => "https://www.youtube.com/watch?v=" + Id; public YouTubeSearchResult(string id, string title) { Id = id; Title = title; } } public static class YouTubeSearchParser { private static readonly Regex VideoResultPattern = new Regex("\\\"videoRenderer\\\":\\{\\\"videoId\\\":\\\"(?[A-Za-z0-9_-]{11})\\\".*?\\\"title\\\":\\{\\\"runs\\\":\\[\\{\\\"text\\\":\\\"(?(?:\\\\\\\\.|[^\\\"\\\\\\\\])*)\\\"", RegexOptions.Compiled | RegexOptions.Singleline); public static List<YouTubeSearchResult> Parse(string html, int limit) { List<YouTubeSearchResult> list = new List<YouTubeSearchResult>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (Match item in VideoResultPattern.Matches(html)) { string value = item.Groups["id"].Value; if (hashSet.Add(value)) { string value2 = item.Groups["title"].Value; string input; try { input = JsonSerializer.Deserialize<string>("\"" + value2 + "\"") ?? value2; } catch { input = value2; } input = Regex.Replace(input, "\\s+", " ").Trim(); if (string.IsNullOrWhiteSpace(input)) { input = value; } list.Add(new YouTubeSearchResult(value, input)); if (list.Count >= limit) { break; } } } return list; } } }