using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading.Tasks; using BoneLib; using BoneLib.BoneMenu; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSLZ.Marrow; using Il2CppSLZ.Marrow.Pool; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using LabFusion.Network; using LabFusion.Network.Serialization; using LabFusion.Safety; using MelonLoader; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Video; using YoutubeVideoPlayer; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonOptionalDependencies(new string[] { "LabFusion" })] [assembly: MelonInfo(typeof(Core), "YoutubeVideoPlayer", "1.2.0", "larkos", null)] [assembly: MelonGame("Stress Level Zero", "BONELAB")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("YoutubeVideoPlayer")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("YoutubeVideoPlayer")] [assembly: AssemblyTitle("YoutubeVideoPlayer")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 YoutubeVideoPlayer { public static class GripTracker { private static readonly Dictionary> _playerGrips = new Dictionary>(); public static void TrackGripsForPlayer(VideoPlayer vp) { if ((Object)(object)vp == (Object)null) { return; } List list = new List(); Transform transform = ((Component)vp).transform; Poolee componentInParent = ((Component)vp).GetComponentInParent(); if ((Object)(object)componentInParent != (Object)null) { transform = ((Component)componentInParent).transform; } else { Rigidbody componentInParent2 = ((Component)vp).GetComponentInParent(); if ((Object)(object)componentInParent2 != (Object)null) { transform = ((Component)componentInParent2).transform; } } Il2CppArrayBase componentsInChildren = ((Component)transform).GetComponentsInChildren(true); if (componentsInChildren != null) { foreach (Grip item in componentsInChildren) { if ((Object)(object)item != (Object)null && !list.Contains(item)) { list.Add(item); } } } _playerGrips[vp] = list; if (list.Count > 0) { MelonLogger.Msg($"[YoutubeVideoPlayer] Cached {list.Count} grip(s) for '{((Object)((Component)vp).gameObject).name}' (Root: {((Object)transform).name})"); } else { MelonLogger.Warning("[YoutubeVideoPlayer] No grips found for '" + ((Object)((Component)vp).gameObject).name + "' — will fall back to first registered player on URL input"); } } public static VideoPlayer GetLocallyGrippedPlayer() { List localHands = GetLocalHands(); if (localHands == null || localHands.Count == 0) { return null; } foreach (KeyValuePair> playerGrip in _playerGrips) { VideoPlayer key = playerGrip.Key; if ((Object)(object)key == (Object)null) { continue; } foreach (Grip item in playerGrip.Value) { if ((Object)(object)item == (Object)null) { continue; } List attachedHands = item.attachedHands; if (attachedHands == null) { continue; } Enumerator enumerator3 = attachedHands.GetEnumerator(); while (enumerator3.MoveNext()) { Hand current3 = enumerator3.Current; if ((Object)(object)current3 == (Object)null) { continue; } foreach (Hand item2 in localHands) { if ((Object)(object)item2 != (Object)null && ((Object)current3).Equals((Object)(object)item2)) { MelonLogger.Msg("[YoutubeVideoPlayer] Local hand confirmed on grip for '" + ((Object)((Component)key).gameObject).name + "'"); return key; } } } } } return null; } public static void Clear() { _playerGrips.Clear(); } private static List GetLocalHands() { List list = new List(); try { if (!Player.HandsExist) { return list; } if ((Object)(object)Player.LeftHand != (Object)null) { list.Add(Player.LeftHand); } if ((Object)(object)Player.RightHand != (Object)null) { list.Add(Player.RightHand); } } catch (Exception ex) { MelonLogger.Warning("[YoutubeVideoPlayer] Could not get local hands: " + ex.Message); } return list; } } public class UrlResolver { private static readonly string YtDlpPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UserData", "YoutubeVideoPlayer", "yt-dlp.exe"); public static bool NeedsResolution(string url) { if (string.IsNullOrEmpty(url)) { return false; } return url.Contains("youtube.com") || url.Contains("youtu.be") || url.Contains("twitch.tv") || url.Contains("vimeo.com"); } public string Resolve(string url) { if (!NeedsResolution(url)) { return url; } if (!File.Exists(YtDlpPath)) { MelonLogger.Msg("[YoutubeVideoPlayer] yt-dlp.exe is not found, please download at Github and place it in UserData/YoutubeVideoPlayer."); } if (!File.Exists(YtDlpPath)) { MelonLogger.Error("[YoutubeVideoPlayer] yt-dlp.exe is not found, please download at Github and place it in UserData/YoutubeVideoPlayer."); return null; } try { ProcessStartInfo startInfo = new ProcessStartInfo { FileName = YtDlpPath, Arguments = "-g -f \"best[ext=mp4][vcodec^=avc1]/best[ext=mp4]/best\" \"" + url + "\"", RedirectStandardOutput = true, UseShellExecute = false, CreateNoWindow = true }; using Process process = Process.Start(startInfo); string text = process.StandardOutput.ReadToEnd(); process.WaitForExit(); string text2 = text.Split('\n')[0].Trim(); if (string.IsNullOrEmpty(text2)) { MelonLogger.Error("[YoutubeVideoPlayer] yt-dlp returned empty output."); return null; } return text2; } catch (Exception ex) { MelonLogger.Error("[YoutubeVideoPlayer] Exception: " + ex.Message); return null; } } } public enum VideoSyncMode { YTVideoPlayer, NOT_YTVideoPlayer, Both } public static class ModSettings { public static VideoSyncMode SyncMode = VideoSyncMode.Both; public static void SetupBoneMenu() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) Page val = Page.Root.CreatePage("YoutubeVideoPlayer", Color.red, 0, true); val.CreateEnum("Video type", Color.white, (Enum)SyncMode, (Action)delegate(Enum value) { SyncMode = (VideoSyncMode)(object)value; }); } } public class YoutubeSyncMessage : NativeMessageHandler { public override byte Tag => 225; public static void BroadcastLink(string link, VideoPlayer triggeringPlayer) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(link)) { return; } int num = VideoManager._players.IndexOf(triggeringPlayer); MelonLogger.Msg("[YoutubeVideoPlayer] Broadcasting video to clients..."); if (NetworkInfo.HasServer) { NetWriter val = NetWriter.Create(); try { val.Write(link); val.Write(num); RelayType val2 = (RelayType)((!NetworkInfo.IsHost) ? 1 : 3); NetMessage val3 = NetMessage.Create((byte)225, val, new MessageRoute(val2, (NetworkChannel)0), (byte?)null); try { if (NetworkInfo.IsHost) { MessageSender.BroadcastMessageExceptSelf((NetworkChannel)0, val3); } else { MessageSender.SendToServer((NetworkChannel)0, val3); } } finally { ((IDisposable)val3)?.Dispose(); } } finally { ((IDisposable)val)?.Dispose(); } } ProcessLink(link, triggeringPlayer); } protected override void OnHandleMessage(ReceivedMessage received) { //IL_00df: Unknown result type (might be due to invalid IL or missing references) string text = string.Empty; int num = 0; try { NetReader val = NetReader.Create(((ReceivedMessage)(ref received)).Bytes); try { text = val.ReadString(); num = val.ReadInt32(); } finally { ((IDisposable)val)?.Dispose(); } } catch (Exception ex) { MelonLogger.Error("[YoutubeVideoPlayer] Failed to read network message: " + ex.Message); return; } if (string.IsNullOrEmpty(text)) { return; } MelonLogger.Msg($"[YoutubeVideoPlayer] Network received link: {text} for player index: {num}"); if (NetworkInfo.IsHost) { NetWriter val2 = NetWriter.Create(); try { val2.Write(text); val2.Write(num); NetMessage val3 = NetMessage.Create((byte)225, val2, new MessageRoute((RelayType)3, (NetworkChannel)0), (byte?)null); try { MessageSender.BroadcastMessageExcept(((ReceivedMessage)(ref received)).Sender.GetValueOrDefault(), (NetworkChannel)0, val3, false); } finally { ((IDisposable)val3)?.Dispose(); } } finally { ((IDisposable)val2)?.Dispose(); } } string capturedLink = text; int capturedIndex = num; Task.Run(delegate { string resolved = VideoManager.ResolveUrl(capturedLink); Core.MainThreadQueue.Enqueue(delegate { if (!string.IsNullOrEmpty(resolved)) { VideoPlayer val4 = null; if (capturedIndex >= 0 && capturedIndex < VideoManager._players.Count) { val4 = VideoManager._players[capturedIndex]; } if ((Object)(object)val4 == (Object)null) { VideoManager.ScanScene(); if (capturedIndex >= 0 && capturedIndex < VideoManager._players.Count) { val4 = VideoManager._players[capturedIndex]; } } if ((Object)(object)val4 == (Object)null) { MelonLogger.Warning($"[YoutubeVideoPlayer] No VideoPlayer at index {capturedIndex}."); } else { MelonLogger.Msg($"[YoutubeVideoPlayer] Applying MP4 to player [{capturedIndex}]: {((Object)((Component)val4).gameObject).name}"); VideoPlayerEarlyPatch.IsRPCProcessing = true; val4.source = (VideoSource)1; val4.url = resolved; val4.Play(); VideoPlayerEarlyPatch.IsRPCProcessing = false; } } }); }); } private static void ProcessLink(string originalUrl, VideoPlayer target) { Task.Run(delegate { string resolved = VideoManager.ResolveUrl(originalUrl); Core.MainThreadQueue.Enqueue(delegate { if (!((Object)(object)target == (Object)null) && !string.IsNullOrEmpty(resolved)) { int value = VideoManager._players.IndexOf(target); MelonLogger.Msg($"[YoutubeVideoPlayer] Applying MP4 to player [{value}]: {((Object)((Component)target).gameObject).name}"); VideoPlayerEarlyPatch.IsRPCProcessing = true; target.source = (VideoSource)1; target.url = resolved; target.Play(); VideoPlayerEarlyPatch.IsRPCProcessing = false; } }); }); } } [HarmonyPatch(typeof(URLWhitelistManager), "IsURLWhitelisted")] public static class WhitelistBypassPatch { private static bool Prefix(string url, ref bool __result) { if (!string.IsNullOrEmpty(url) && (url.Contains("googlevideo.com") || url.Contains("ttvnw.net") || url.Contains("jtvnw.net"))) { __result = true; return false; } return true; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPriority(800)] public static class VideoPlayerEarlyPatch { public static bool IsRPCProcessing; public static string GuardedUrl; private static string _lastTriggeredUrl; private static float _lastTriggerTime; private const float DebounceSeconds = 1f; private static bool Prefix(VideoPlayer __instance, ref string value) { if (IsRPCProcessing) { GuardedUrl = value; return true; } string text = value; string systemCopyBuffer = GUIUtility.systemCopyBuffer; if (!UrlResolver.NeedsResolution(text) && UrlResolver.NeedsResolution(systemCopyBuffer)) { text = systemCopyBuffer; } bool flag = UrlResolver.NeedsResolution(text); bool flag2 = false; switch (ModSettings.SyncMode) { case VideoSyncMode.Both: flag2 = true; break; case VideoSyncMode.YTVideoPlayer: flag2 = flag; break; case VideoSyncMode.NOT_YTVideoPlayer: flag2 = !flag; break; } if (!flag2) { return false; } if (!string.IsNullOrEmpty(text)) { VideoPlayer locallyGrippedPlayer = GripTracker.GetLocallyGrippedPlayer(); if ((Object)(object)locallyGrippedPlayer == (Object)null || (Object)(object)locallyGrippedPlayer != (Object)(object)__instance) { if (flag) { return false; } return true; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (text == _lastTriggeredUrl && realtimeSinceStartup - _lastTriggerTime < 1f) { return false; } _lastTriggeredUrl = text; _lastTriggerTime = realtimeSinceStartup; MelonLogger.Msg($"[YoutubeVideoPlayer] Link found: {text} | target: '{((Object)((Component)__instance).gameObject).name}' (index {VideoManager._players.IndexOf(__instance)})"); YoutubeSyncMessage.BroadcastLink(text, __instance); return false; } return true; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPriority(0)] public static class VideoPlayerLatePatch { private static void Prefix(ref string value) { if (VideoPlayerEarlyPatch.IsRPCProcessing && VideoPlayerEarlyPatch.GuardedUrl != null && value != VideoPlayerEarlyPatch.GuardedUrl) { MelonLogger.Msg("[YoutubeVideoPlayer] Restoring guarded URL."); value = VideoPlayerEarlyPatch.GuardedUrl; } } } [HarmonyPatch(typeof(VideoPlayer), "Prepare")] public static class VideoPlayerPreparePatch { private static bool Prefix(VideoPlayer __instance) { return !string.IsNullOrEmpty(__instance.url); } } [HarmonyPatch(typeof(VideoPlayer), "Play")] public static class VideoPlayerPlayPatch { private static bool Prefix(VideoPlayer __instance) { return !string.IsNullOrEmpty(__instance.url); } } public static class VideoManager { public static readonly List _players = new List(); public static void ScanScene() { _players.Clear(); GripTracker.Clear(); Il2CppArrayBase val = Object.FindObjectsOfType(); foreach (VideoPlayer item in val) { RegisterVideoPlayer(item); } MelonLogger.Msg($"[YoutubeVideoPlayer] Found {_players.Count} VideoPlayer(s) in scene."); } public static void RegisterVideoPlayer(VideoPlayer vp) { if (!((Object)(object)vp == (Object)null) && !_players.Contains(vp)) { _players.Add(vp); SetupVideoPlayer(vp); GripTracker.TrackGripsForPlayer(vp); MelonLogger.Msg($"[YoutubeVideoPlayer] Registered [{_players.Count - 1}]: {((Object)((Component)vp).gameObject).name}"); } } private static void SetupVideoPlayer(VideoPlayer vp) { MeshRenderer component = ((Component)vp).gameObject.GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)((Renderer)component).material != (Object)null) { vp.renderMode = (VideoRenderMode)3; vp.targetMaterialRenderer = (Renderer)(object)component; if (((Renderer)component).material.HasProperty("_BaseMap")) { vp.targetMaterialProperty = "_BaseMap"; } else if (((Renderer)component).material.HasProperty("_MainTex")) { vp.targetMaterialProperty = "_MainTex"; } } } public static string ResolveUrl(string url) { if (string.IsNullOrEmpty(url)) { return url; } return new UrlResolver().Resolve(url); } } public class Core : MelonMod { public static readonly ConcurrentQueue MainThreadQueue = new ConcurrentQueue(); public static Core Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; Hooking.OnLevelLoaded += OnLevelLoaded; ((MelonBase)this).HarmonyInstance.PatchAll(); ModSettings.SetupBoneMenu(); if (MelonTypeBase.RegisteredMelons.Any((MelonMod m) => ((MelonBase)m).Info.Name == "LabFusion")) { NativeMessageHandler.RegisterHandler(); } } public override void OnUpdate() { Action result; while (MainThreadQueue.TryDequeue(out result)) { result?.Invoke(); } } private void OnLevelLoaded(LevelInfo levelInfo) { VideoManager.ScanScene(); } } [HarmonyPatch(typeof(Poolee), "OnSpawn")] public static class PooleeOnSpawnPatch { [HarmonyPostfix] public static void Postfix(Poolee __instance) { GameObject gameObject = ((Component)__instance).gameObject; if (!((Object)(object)gameObject == (Object)null)) { VideoPlayer componentInChildren = gameObject.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { VideoManager.RegisterVideoPlayer(componentInChildren); } } } } }