Decompiled source of YoutubeVideoPlayer v1.3.0

Mods/YoutubeVideoPlayer.dll

Decompiled 2 months ago
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<VideoPlayer, List<Grip>> _playerGrips = new Dictionary<VideoPlayer, List<Grip>>();

		public static void TrackGripsForPlayer(VideoPlayer vp)
		{
			if ((Object)(object)vp == (Object)null)
			{
				return;
			}
			List<Grip> list = new List<Grip>();
			Transform transform = ((Component)vp).transform;
			Poolee componentInParent = ((Component)vp).GetComponentInParent<Poolee>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				transform = ((Component)componentInParent).transform;
			}
			else
			{
				Rigidbody componentInParent2 = ((Component)vp).GetComponentInParent<Rigidbody>();
				if ((Object)(object)componentInParent2 != (Object)null)
				{
					transform = ((Component)componentInParent2).transform;
				}
			}
			Il2CppArrayBase<Grip> componentsInChildren = ((Component)transform).GetComponentsInChildren<Grip>(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<Hand> localHands = GetLocalHands();
			if (localHands == null || localHands.Count == 0)
			{
				return null;
			}
			foreach (KeyValuePair<VideoPlayer, List<Grip>> 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<Hand> attachedHands = item.attachedHands;
					if (attachedHands == null)
					{
						continue;
					}
					Enumerator<Hand> 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<Hand> GetLocalHands()
		{
			List<Hand> list = new List<Hand>();
			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<Enum>)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<VideoPlayer> _players = new List<VideoPlayer>();

		public static void ScanScene()
		{
			_players.Clear();
			GripTracker.Clear();
			Il2CppArrayBase<VideoPlayer> val = Object.FindObjectsOfType<VideoPlayer>();
			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<MeshRenderer>();
			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<Action> MainThreadQueue = new ConcurrentQueue<Action>();

		public static Core Instance { get; private set; }

		public override void OnInitializeMelon()
		{
			Instance = this;
			Hooking.OnLevelLoaded += OnLevelLoaded;
			((MelonBase)this).HarmonyInstance.PatchAll();
			ModSettings.SetupBoneMenu();
			if (MelonTypeBase<MelonMod>.RegisteredMelons.Any((MelonMod m) => ((MelonBase)m).Info.Name == "LabFusion"))
			{
				NativeMessageHandler.RegisterHandler<YoutubeSyncMessage>();
			}
		}

		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<VideoPlayer>();
				if ((Object)(object)componentInChildren != (Object)null)
				{
					VideoManager.RegisterVideoPlayer(componentInChildren);
				}
			}
		}
	}
}