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);
}
}
}
}
}