Decompiled source of WorkableTV v2.0.0

Mods/WorkableTV.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using BoneLib;
using BoneLib.BoneMenu;
using BoneLib.Notifications;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Marrow.Pool;
using LabFusion.Entities;
using LabFusion.Marrow.Extenders;
using LabFusion.Network;
using LabFusion.Network.Serialization;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Video;
using WorkableTV;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "WorkableTV", "1.0.0", "CAitStudio", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonOptionalDependencies(new string[] { "LabFusion" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("WorkableTV")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("WorkableTV")]
[assembly: AssemblyTitle("WorkableTV")]
[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]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 WorkableTV
{
	public class Core : MelonMod
	{
		[HarmonyPatch(typeof(Poolee), "OnEnable")]
		private static class PooleeOnEnablePatch
		{
			private static void Postfix(Poolee __instance)
			{
				if (!((Object)(object)__instance == (Object)null) && !((Il2CppObjectBase)__instance).WasCollected)
				{
					TVController.MarkObjectSpawned(((Component)__instance).gameObject);
					Instance?.QueueSpawnedObject(((Component)__instance).gameObject);
				}
			}
		}

		[HarmonyPatch(typeof(Poolee), "Despawn", new Type[] { })]
		private static class PooleeDespawnPatch
		{
			private static void Prefix(Poolee __instance)
			{
				if (!((Object)(object)__instance == (Object)null) && !((Il2CppObjectBase)__instance).WasCollected)
				{
					TVController.StopTVsForObject(((Component)__instance).gameObject);
				}
			}
		}

		public const string MediaFolderName = "Media";

		public const string PreferencesCategory = "WorkableTV";

		public const string SelectedVideoKey = "SelectedVideo";

		public const string UrlKey = "URL";

		public const string YtDlpFileName = "yt-dlp.exe";

		private const string UrlPrefix = "URL:";

		private const float SpawnedObjectScanDelay = 0.15f;

		private static readonly string[] SupportedMediaExtensions = new string[4] { ".mp4", ".png", ".jpg", ".jpeg" };

		public static readonly ConcurrentQueue<Action> MainThreadQueue = new ConcurrentQueue<Action>();

		private static MelonPreferences_Category _preferences;

		private static MelonPreferences_Entry<string> _selectedVideoEntry;

		private static MelonPreferences_Entry<string> _urlEntry;

		private static bool _shouldAutoSelectFirstMedia;

		private static Page _menuPage;

		private static Page _mediaSelectionPage;

		private static Page _mediaManagerPage;

		private static StringElement _urlElement;

		private static List<FunctionElement> _videoButtons = new List<FunctionElement>();

		private static List<string> _videoFileNames = new List<string>();

		private static readonly List<Page> _screenPages = new List<Page>();

		private readonly HashSet<int> _queuedSpawnedObjectIds = new HashSet<int>();

		private bool _levelReady;

		public static string MediaFolderPath { get; private set; }

		public static string RuntimeFolderPath { get; private set; }

		public static string YtDlpPath { get; private set; }

		public static string SelectedVideo { get; private set; }

		public static string Url { get; private set; }

		public static Core Instance { get; private set; }

		public override void OnInitializeMelon()
		{
			Instance = this;
			MediaFolderPath = Path.Combine(MelonEnvironment.UserDataDirectory, "Media");
			Directory.CreateDirectory(MediaFolderPath);
			PrepareRuntimeTools();
			_shouldAutoSelectFirstMedia = !CfgAlreadyHasSelection();
			_preferences = MelonPreferences.CreateCategory("WorkableTV", "WorkableTV");
			_selectedVideoEntry = _preferences.CreateEntry<string>("SelectedVideo", "None", "Selected Video", (string)null, false, false, (ValueValidator)null, (string)null);
			_urlEntry = _preferences.CreateEntry<string>("URL", "", "URL", (string)null, false, false, (ValueValidator)null, (string)null);
			SelectedVideo = _selectedVideoEntry.Value;
			Url = _urlEntry.Value;
			Hooking.OnLevelLoaded += OnLevelLoaded;
			Hooking.OnLevelLoading += OnLevelLoading;
			Hooking.OnLevelUnloaded += OnLevelUnloaded;
			((MelonBase)this).HarmonyInstance.PatchAll();
			FusionSync.TryInitialize();
			MelonLogger.Msg("WorkableTV initialized. Media folder: " + MediaFolderPath);
		}

		public override void OnLateInitializeMelon()
		{
			CreateMenu();
			RefreshVideoList();
		}

		public override void OnUpdate()
		{
			Action result;
			while (MainThreadQueue.TryDequeue(out result))
			{
				result?.Invoke();
			}
		}

		private void OnLevelLoading(LevelInfo levelInfo)
		{
			_levelReady = false;
			_queuedSpawnedObjectIds.Clear();
			TVController.StopAllTVs();
		}

		private void OnLevelLoaded(LevelInfo levelInfo)
		{
			TVController.FindAndSetupTVs();
			_levelReady = true;
		}

		private void OnLevelUnloaded()
		{
			_levelReady = false;
			_queuedSpawnedObjectIds.Clear();
			TVController.StopAllTVs();
		}

		public void QueueSpawnedObject(GameObject spawned)
		{
			if (_levelReady && !((Object)(object)spawned == (Object)null) && !((Il2CppObjectBase)spawned).WasCollected)
			{
				int instanceID = ((Object)spawned).GetInstanceID();
				if (_queuedSpawnedObjectIds.Add(instanceID))
				{
					MelonCoroutines.Start(ScanSpawnedObjectAfterDelay(spawned, instanceID));
				}
			}
		}

		private IEnumerator ScanSpawnedObjectAfterDelay(GameObject spawned, int objectId)
		{
			yield return (object)new WaitForSeconds(0.15f);
			yield return null;
			_queuedSpawnedObjectIds.Remove(objectId);
			if (!((Object)(object)spawned == (Object)null))
			{
				TVController.ScanSpawnedObject(spawned);
			}
		}

		private void CreateMenu()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_005b: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			_menuPage = Page.Root.CreatePage("WorkableTV", Color.white, 0, true);
			_mediaSelectionPage = _menuPage.CreatePage("Media Selection", Color.cyan, 0, true);
			_mediaManagerPage = _menuPage.CreatePage("Media Manager", Color.green, 0, true);
			_mediaSelectionPage.CreateFunction("Refresh", Color.blue, (Action)delegate
			{
				RefreshVideoList();
				MelonLogger.Msg("Refreshed video list.");
			});
			_mediaSelectionPage.CreateFunction("None", Color.white, (Action)delegate
			{
				SelectVideo("None");
			});
			_urlElement = _mediaSelectionPage.CreateString("URL", Color.cyan, Url, (Action<string>)delegate(string value)
			{
				SetUrl(value);
			});
			_mediaSelectionPage.CreateFunction("Select URL", Color.cyan, (Action)delegate
			{
				SelectUrl();
			});
			RefreshMediaManager();
		}

		public static void RefreshVideoList()
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			foreach (FunctionElement videoButton in _videoButtons)
			{
				_mediaSelectionPage.Remove((Element)(object)videoButton);
			}
			_videoButtons.Clear();
			try
			{
				RefreshMediaFileCache();
				foreach (string fileName in _videoFileNames)
				{
					FunctionElement item = _mediaSelectionPage.CreateFunction(fileName, Color.white, (Action)delegate
					{
						SelectVideo(fileName);
					});
					_videoButtons.Add(item);
				}
				MelonLogger.Msg($"Loaded {_videoFileNames.Count} media file(s) from {MediaFolderPath}");
				SelectFirstMediaIfThisIsFirstRun();
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Error loading video files: {value}");
			}
		}

		private static void RefreshMediaFileCache()
		{
			_videoFileNames.Clear();
			string[] files = Directory.GetFiles(MediaFolderPath, "*", SearchOption.TopDirectoryOnly);
			foreach (string text in files)
			{
				if (IsSupportedMediaFile(text))
				{
					_videoFileNames.Add(Path.GetFileName(text));
				}
			}
			_videoFileNames.Sort(StringComparer.OrdinalIgnoreCase);
		}

		public static void SelectVideo(string videoName)
		{
			SelectVideo(videoName, showNotification: true);
		}

		public static void SelectVideo(string videoName, bool showNotification)
		{
			_shouldAutoSelectFirstMedia = false;
			SelectedVideo = videoName;
			_selectedVideoEntry.Value = videoName;
			_preferences.SaveToFile(false);
			MelonLogger.Msg("Selected video: " + videoName);
			if (showNotification)
			{
				SendSelectedNotification(GetSelectionTitle(videoName), (NotificationType)3);
			}
			if (videoName == "None")
			{
				MelonLogger.Msg("None selected. Existing monitors will keep their current media; new monitors will stay unchanged.");
			}
			else
			{
				MelonLogger.Msg("Media selected. It will only be applied to monitors spawned after this selection.");
			}
		}

		public static string GetVideoFilePath(string videoName)
		{
			if (videoName == "None")
			{
				return null;
			}
			if (IsUrlSelection(videoName))
			{
				return videoName.Substring("URL:".Length);
			}
			return Path.Combine(MediaFolderPath, videoName);
		}

		public static bool IsUrlPath(string path)
		{
			if (string.IsNullOrWhiteSpace(path))
			{
				return false;
			}
			if (!path.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
			{
				return path.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		public static void RunOnMainThread(Action action)
		{
			if (action != null)
			{
				MainThreadQueue.Enqueue(action);
			}
		}

		private static bool IsUrlSelection(string videoName)
		{
			if (!string.IsNullOrWhiteSpace(videoName))
			{
				return videoName.StartsWith("URL:", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		public static bool IsNetworkUrlSelection(string videoName)
		{
			return IsUrlSelection(videoName);
		}

		private static void SetUrl(string value)
		{
			Url = value?.Trim() ?? "";
			_urlEntry.Value = Url;
			_preferences.SaveToFile(false);
			MelonLogger.Msg("Saved URL: " + Url);
		}

		private static void SelectUrl()
		{
			if (_urlElement != null)
			{
				SetUrl(_urlElement.Value);
			}
			if (ValidateUrlForSelection(Url, notify: true))
			{
				_shouldAutoSelectFirstMedia = false;
				SelectedVideo = "URL:" + Url;
				_selectedVideoEntry.Value = SelectedVideo;
				_preferences.SaveToFile(false);
				MelonLogger.Msg("Selected URL: " + Url);
				MelonLogger.Msg("URL selected. It will only be applied to monitors spawned after this selection.");
				SendSelectedNotification(Url, (NotificationType)3);
			}
		}

		private static bool ValidateUrlForSelection(string url, bool notify)
		{
			if (string.IsNullOrWhiteSpace(url))
			{
				MelonLogger.Warning("URL is empty.");
				if (notify)
				{
					SendSelectedNotification("URL Empty", (NotificationType)1);
				}
				return false;
			}
			if (!IsUrlPath(url))
			{
				MelonLogger.Warning("URL must start with http:// or https:// : " + url);
				if (notify)
				{
					SendSelectedNotification("Invalid URL", (NotificationType)1);
				}
				return false;
			}
			if (FusionSync.IsInFusionServer() && url.Contains("pornhub", StringComparison.OrdinalIgnoreCase))
			{
				MelonLogger.Warning("Blocked PornHub URL selection in Fusion server: " + url);
				if (notify)
				{
					SendWorkableNotification("Cannot Select URL", "PornHub URLs are blocked in Fusion servers.", (NotificationType)1);
				}
				return false;
			}
			return true;
		}

		private static bool IsSupportedMediaFile(string filePath)
		{
			string extension = Path.GetExtension(filePath);
			string[] supportedMediaExtensions = SupportedMediaExtensions;
			foreach (string b in supportedMediaExtensions)
			{
				if (string.Equals(extension, b, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static void SelectFirstMediaIfThisIsFirstRun()
		{
			if (_shouldAutoSelectFirstMedia)
			{
				if (SelectedVideo != "None")
				{
					_shouldAutoSelectFirstMedia = false;
					return;
				}
				if (_videoFileNames.Count <= 0)
				{
					MelonLogger.Msg("No saved media selection yet, but Media folder is empty.");
					return;
				}
				string text = _videoFileNames[0];
				MelonLogger.Msg("No saved media selection found. Auto selecting first media file: " + text);
				SelectVideo(text, showNotification: false);
			}
		}

		private static string GetSelectionTitle(string videoName)
		{
			if (videoName == "None")
			{
				return "Game Default";
			}
			if (IsUrlSelection(videoName))
			{
				return videoName.Substring("URL:".Length);
			}
			return videoName;
		}

		public static string GetSelectionDisplayName(string videoName)
		{
			return GetSelectionTitle(videoName);
		}

		private static void SendSelectedNotification(string title, NotificationType type = (NotificationType)3)
		{
			//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_0017: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//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_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit(title),
				Message = NotificationText.op_Implicit("Selected"),
				Type = type,
				ShowTitleOnPopup = true,
				PopupLength = 2.5f
			});
		}

		public static void SendWorkableNotification(string title, string message, NotificationType type = (NotificationType)0)
		{
			//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_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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_003b: Expected O, but got Unknown
			Notifier.Send(new Notification
			{
				Title = NotificationText.op_Implicit(title),
				Message = NotificationText.op_Implicit(message),
				Type = type,
				ShowTitleOnPopup = true,
				PopupLength = 3f
			});
		}

		private static bool CfgAlreadyHasSelection()
		{
			try
			{
				string path = Path.Combine(MelonEnvironment.UserDataDirectory, "MelonPreferences.cfg");
				if (!File.Exists(path))
				{
					return false;
				}
				bool flag = false;
				string[] array = File.ReadAllLines(path);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length > 0)
					{
						if (text.StartsWith("[") && text.EndsWith("]"))
						{
							flag = string.Equals(text, "[WorkableTV]", StringComparison.OrdinalIgnoreCase);
						}
						else if (flag && text.StartsWith("SelectedVideo", StringComparison.OrdinalIgnoreCase))
						{
							return true;
						}
					}
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not check WorkableTV config for saved media selection: " + ex.Message);
			}
			return false;
		}

		private static void PrepareRuntimeTools()
		{
			try
			{
				RuntimeFolderPath = Path.Combine(MediaFolderPath, "Runtime");
				Directory.CreateDirectory(RuntimeFolderPath);
				YtDlpPath = Path.Combine(RuntimeFolderPath, "yt-dlp.exe");
				if (!File.Exists(YtDlpPath))
				{
					MelonLogger.Warning("yt-dlp.exe not found. Put it at " + YtDlpPath + " to play YouTube and BiliBili links.");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not prepare runtime media tools: " + ex.Message);
			}
		}

		public static void RefreshMediaManager()
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			if (_mediaManagerPage == null)
			{
				return;
			}
			try
			{
				_mediaManagerPage.RemoveAll();
				foreach (Page screenPage in _screenPages)
				{
					try
					{
						_mediaManagerPage.RemovePage(screenPage);
					}
					catch
					{
					}
				}
				_screenPages.Clear();
				_mediaManagerPage.CreateFunction("Refresh Screens", Color.blue, (Action)RefreshMediaManager);
				List<TVController.TVGroup> managedTVGroups = TVController.GetManagedTVGroups();
				if (managedTVGroups.Count <= 0)
				{
					_mediaManagerPage.CreateFunction("No screens found", Color.gray, (Action)delegate
					{
					});
					MelonLogger.Msg("Media Manager refreshed: no supported screens found.");
					return;
				}
				for (int num = 0; num < managedTVGroups.Count; num++)
				{
					TVController.TVGroup tVGroup = managedTVGroups[num];
					Page val = _mediaManagerPage.CreatePage(tVGroup.GetMenuName(num), Color.white, 0, true);
					_screenPages.Add(val);
					BuildScreenManagerPage(val, tVGroup);
				}
				MelonLogger.Msg($"Media Manager refreshed: {managedTVGroups.Count} screen group(s).");
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Error refreshing Media Manager: {value}");
			}
		}

		private static void BuildScreenManagerPage(Page page, TVController.TVGroup group)
		{
			//IL_0019: 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_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			page.RemoveAll();
			page.CreateFunction("Play Current Selection", Color.green, (Action)delegate
			{
				TVController.ApplySelectionToGroup(group, SelectedVideo, broadcast: true);
			});
			page.CreateFunction("Pause", Color.yellow, (Action)group.Pause);
			page.CreateFunction("Resume", Color.green, (Action)group.Resume);
			page.CreateFunction("Game Default", Color.white, (Action)delegate
			{
				TVController.ApplySelectionToGroup(group, "None", broadcast: true);
			});
			BuildScreenMediaPage(page.CreatePage("Play Media", Color.cyan, 0, true), group);
		}

		private static void BuildScreenMediaPage(Page page, TVController.TVGroup group)
		{
			//IL_002f: 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_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			page.RemoveAll();
			RefreshMediaFileCache();
			page.CreateFunction("Refresh Media", Color.blue, (Action)delegate
			{
				BuildScreenMediaPage(page, group);
			});
			page.CreateFunction("None", Color.white, (Action)delegate
			{
				TVController.ApplySelectionToGroup(group, "None", broadcast: true);
			});
			StringElement screenUrlElement = null;
			screenUrlElement = page.CreateString("URL", Color.cyan, Url, (Action<string>)delegate(string value)
			{
				SetUrl(value);
			});
			page.CreateFunction("Play URL", Color.cyan, (Action)delegate
			{
				if (screenUrlElement != null)
				{
					SetUrl(screenUrlElement.Value);
				}
				if (ValidateUrlForSelection(Url, notify: true))
				{
					TVController.ApplySelectionToGroup(group, "URL:" + Url, broadcast: true);
					SendSelectedNotification(Url, (NotificationType)3);
				}
			});
			foreach (string videoFileName in _videoFileNames)
			{
				string capturedName = videoFileName;
				page.CreateFunction(capturedName, GetMediaButtonColor(capturedName), (Action)delegate
				{
					TVController.ApplySelectionToGroup(group, capturedName, broadcast: true);
					SendSelectedNotification(capturedName, (NotificationType)3);
				});
			}
		}

		private static Color GetMediaButtonColor(string fileName)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			string extension = Path.GetExtension(fileName);
			if (string.Equals(extension, ".mp4", StringComparison.OrdinalIgnoreCase))
			{
				return Color.white;
			}
			if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase))
			{
				return Color.cyan;
			}
			return Color.gray;
		}
	}
	public static class FusionSync
	{
		public class WorkableTVFusionMessage : NativeMessageHandler
		{
			public override byte Tag => 226;

			protected override void OnHandleMessage(ReceivedMessage received)
			{
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				string selection;
				FusionTarget target;
				try
				{
					NetReader val = NetReader.Create(((ReceivedMessage)(ref received)).Bytes);
					try
					{
						selection = val.ReadString();
						target = ReadTarget(val);
					}
					finally
					{
						if (val != null)
						{
							val.Dispose();
						}
					}
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("WorkableTV Fusion message read failed: " + ex.Message);
					return;
				}
				if (!string.IsNullOrWhiteSpace(selection))
				{
					if (NetworkInfo.IsHost)
					{
						RebroadcastFromHost(received, selection, target);
					}
					Core.RunOnMainThread(delegate
					{
						TVController.ApplyFusionSelection(selection, target);
					});
				}
			}

			private static void RebroadcastFromHost(ReceivedMessage received, string selection, FusionTarget target)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				NetWriter val = NetWriter.Create();
				try
				{
					WritePayload(val, selection, target);
					NetMessage val2 = NetMessage.Create((byte)226, val, new MessageRoute((RelayType)2, (NetworkChannel)0), (byte?)null);
					try
					{
						MessageSender.BroadcastMessageExcept(((ReceivedMessage)(ref received)).Sender.GetValueOrDefault(), (NetworkChannel)0, val2, false);
					}
					finally
					{
						if (val2 != null)
						{
							val2.Dispose();
						}
					}
				}
				finally
				{
					if (val != null)
					{
						val.Dispose();
					}
				}
			}
		}

		private const byte MessageTag = 226;

		private static bool _registered;

		private static string _lastSentSelection;

		private static string _lastSentTarget;

		private static float _lastSentAt;

		public static void TryInitialize()
		{
			try
			{
				if (!_registered)
				{
					if (!IsFusionLoaded())
					{
						MelonLogger.Msg("LabFusion not found. WorkableTV Fusion sync is disabled.");
						return;
					}
					NativeMessageHandler.RegisterHandler<WorkableTVFusionMessage>();
					_registered = true;
					MelonLogger.Msg("WorkableTV Fusion sync ready.");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("WorkableTV Fusion sync could not start: " + ex.Message);
			}
		}

		public static void BroadcastSelection(string selection)
		{
			BroadcastSelection(selection, FusionTarget.None);
		}

		public static bool IsInFusionServer()
		{
			try
			{
				return _registered && NetworkInfo.HasServer;
			}
			catch
			{
				return false;
			}
		}

		public static void BroadcastSelection(string selection, FusionTarget target)
		{
			if (!_registered || string.IsNullOrWhiteSpace(selection))
			{
				return;
			}
			try
			{
				if (!NetworkInfo.HasServer)
				{
					return;
				}
				if (!target.HasTarget)
				{
					MelonLogger.Msg("Skipping WorkableTV Fusion broadcast because this monitor has no Fusion target yet.");
					return;
				}
				string debugName = target.GetDebugName();
				if (!(_lastSentSelection == selection) || !(_lastSentTarget == debugName) || !(Time.realtimeSinceStartup - _lastSentAt < 1f))
				{
					_lastSentSelection = selection;
					_lastSentTarget = debugName;
					_lastSentAt = Time.realtimeSinceStartup;
					SendSelection(selection, target);
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("WorkableTV Fusion broadcast failed: " + ex.Message);
			}
		}

		private static void SendSelection(string selection, FusionTarget target)
		{
			//IL_0019: 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)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			NetWriter val = NetWriter.Create();
			try
			{
				WritePayload(val, selection, target);
				RelayType val2 = (RelayType)((!NetworkInfo.IsHost) ? 1 : 2);
				NetMessage val3 = NetMessage.Create((byte)226, val, new MessageRoute(val2, (NetworkChannel)0), (byte?)null);
				try
				{
					if (NetworkInfo.IsHost)
					{
						MessageSender.BroadcastMessageExceptSelf((NetworkChannel)0, val3);
					}
					else
					{
						MessageSender.SendToServer((NetworkChannel)0, val3);
					}
				}
				finally
				{
					if (val3 != null)
					{
						val3.Dispose();
					}
				}
			}
			finally
			{
				if (val != null)
				{
					val.Dispose();
				}
			}
		}

		private static void WritePayload(NetWriter writer, string selection, FusionTarget target)
		{
			writer.Write(selection);
			writer.Write(target.HasTarget);
			if (target.HasTarget)
			{
				writer.Write(target.EntityId);
				writer.Write(target.RendererPath);
				writer.Write(target.MaterialIndex);
			}
		}

		private static FusionTarget ReadTarget(NetReader reader)
		{
			if (reader.Position >= reader.Length)
			{
				return FusionTarget.None;
			}
			if (!reader.ReadBoolean())
			{
				return FusionTarget.None;
			}
			ushort entityId = reader.ReadUInt16();
			string rendererPath = reader.ReadString();
			int materialIndex = reader.ReadInt32();
			return new FusionTarget(hasTarget: true, entityId, rendererPath, materialIndex);
		}

		private static bool IsFusionLoaded()
		{
			foreach (MelonMod registeredMelon in MelonTypeBase<MelonMod>.RegisteredMelons)
			{
				if (((MelonBase)registeredMelon).Info.Name == "LabFusion")
				{
					return true;
				}
			}
			return false;
		}
	}
	public static class TVController
	{
		private static class UrlResolver
		{
			private const string StreamFormat = "best[ext=mp4][vcodec^=avc1]/best[ext=mp4]/best";

			private const string BiliBiliVideoFormat = "bv*[ext=mp4][vcodec^=avc1]/bv*[ext=mp4]/bv*";

			private const string BiliBiliAudioFormat = "ba[ext=m4a]/ba";

			public static bool NeedsResolution(string url)
			{
				if (string.IsNullOrWhiteSpace(url))
				{
					return false;
				}
				string text = url.ToLowerInvariant();
				if (!text.Contains("youtube.com") && !text.Contains("youtu.be") && !text.Contains("bilibili.com"))
				{
					return text.Contains("b23.tv");
				}
				return true;
			}

			public static ResolvedUrl Resolve(string url)
			{
				if (!NeedsResolution(url))
				{
					return ResolvedUrl.Single(url);
				}
				if (string.IsNullOrWhiteSpace(Core.YtDlpPath) || !File.Exists(Core.YtDlpPath))
				{
					MelonLogger.Error("yt-dlp.exe is missing: " + Core.YtDlpPath);
					return null;
				}
				if (IsBiliBiliUrl(url))
				{
					return DownloadBiliBiliSplitToCache(url);
				}
				string item = "best[ext=mp4][vcodec^=avc1]/best[ext=mp4]/best";
				try
				{
					using Process process = Process.Start(new ProcessStartInfo
					{
						FileName = Core.YtDlpPath,
						RedirectStandardOutput = true,
						RedirectStandardError = true,
						UseShellExecute = false,
						CreateNoWindow = true,
						ArgumentList = { "-g", "-f", item, "--no-playlist", url }
					});
					if (process == null)
					{
						MelonLogger.Error("Failed to start yt-dlp.exe.");
						return null;
					}
					Task<string> task = process.StandardOutput.ReadToEndAsync();
					Task<string> task2 = process.StandardError.ReadToEndAsync();
					if (!process.WaitForExit(30000))
					{
						try
						{
							process.Kill();
						}
						catch
						{
						}
						MelonLogger.Error("yt-dlp timed out while resolving URL.");
						return null;
					}
					string result = task.Result;
					string result2 = task2.Result;
					List<string> outputLines = GetOutputLines(result);
					if (outputLines.Count == 0)
					{
						MelonLogger.Error("yt-dlp returned empty output. " + result2);
						return null;
					}
					return ResolvedUrl.Single(outputLines[0]);
				}
				catch (Exception value)
				{
					MelonLogger.Error($"yt-dlp resolve error: {value}");
					return null;
				}
			}

			private static bool IsBiliBiliUrl(string url)
			{
				if (!string.IsNullOrWhiteSpace(url))
				{
					if (!url.Contains("bilibili.com", StringComparison.OrdinalIgnoreCase))
					{
						return url.Contains("b23.tv", StringComparison.OrdinalIgnoreCase);
					}
					return true;
				}
				return false;
			}

			private static ResolvedUrl DownloadBiliBiliSplitToCache(string url)
			{
				try
				{
					string text = Path.Combine(Core.RuntimeFolderPath, "Cache", "BiliBiliSplit");
					Directory.CreateDirectory(text);
					string text2 = "bilibili_" + GetStableName(url);
					string text3 = FindCachedFile(text, text2 + "_video", ".mp4");
					string text4 = FindCachedFile(text, text2 + "_audio", ".m4a");
					if (string.IsNullOrWhiteSpace(text3))
					{
						text3 = DownloadBiliBiliTrack(url, "bv*[ext=mp4][vcodec^=avc1]/bv*[ext=mp4]/bv*", text, text2 + "_video");
					}
					if (string.IsNullOrWhiteSpace(text4))
					{
						text4 = DownloadBiliBiliTrack(url, "ba[ext=m4a]/ba", text, text2 + "_audio");
					}
					if (string.IsNullOrWhiteSpace(text3) || !File.Exists(text3))
					{
						MelonLogger.Error("yt-dlp did not create a playable BiliBili video track.");
						return null;
					}
					if (string.IsNullOrWhiteSpace(text4) || !File.Exists(text4))
					{
						MelonLogger.Warning("yt-dlp did not create a BiliBili audio track. Playing cached video without audio.");
						return ResolvedUrl.Single(text3);
					}
					MelonLogger.Msg("Downloaded BiliBili split tracks with yt-dlp.");
					return new ResolvedUrl(text3, text4);
				}
				catch (Exception value)
				{
					MelonLogger.Error($"BiliBili split download error: {value}");
					return null;
				}
			}

			private static string DownloadBiliBiliTrack(string url, string format, string cacheFolder, string outputName)
			{
				string item = Path.Combine(cacheFolder, outputName + ".%(ext)s");
				DeleteCachedTrackFiles(cacheFolder, outputName);
				using Process process = Process.Start(new ProcessStartInfo
				{
					FileName = Core.YtDlpPath,
					RedirectStandardOutput = true,
					RedirectStandardError = true,
					UseShellExecute = false,
					CreateNoWindow = true,
					ArgumentList = { "-f", format, "--no-playlist", "--no-part", "-o", item, url }
				});
				if (process == null)
				{
					MelonLogger.Error("Failed to start yt-dlp.exe for BiliBili track download.");
					return null;
				}
				Task<string> task = process.StandardOutput.ReadToEndAsync();
				Task<string> task2 = process.StandardError.ReadToEndAsync();
				if (!process.WaitForExit(180000))
				{
					try
					{
						process.Kill();
					}
					catch
					{
					}
					MelonLogger.Error("yt-dlp timed out while downloading BiliBili track.");
					return null;
				}
				string result = task.Result;
				string result2 = task2.Result;
				if (process.ExitCode != 0)
				{
					MelonLogger.Error("yt-dlp BiliBili track download failed. " + result + " " + result2);
					return null;
				}
				string text = FindCachedFile(cacheFolder, outputName, null);
				if (string.IsNullOrWhiteSpace(text))
				{
					MelonLogger.Error($"yt-dlp finished but no cached BiliBili track was found for {outputName}. {result} {result2}");
				}
				return text;
			}

			private static string FindCachedFile(string cacheFolder, string outputName, string preferredExtension)
			{
				if (!Directory.Exists(cacheFolder))
				{
					return null;
				}
				string result = null;
				DateTime dateTime = DateTime.MinValue;
				string[] files = Directory.GetFiles(cacheFolder, outputName + ".*", SearchOption.TopDirectoryOnly);
				foreach (string text in files)
				{
					FileInfo fileInfo = new FileInfo(text);
					if (fileInfo.Length > 0 && (string.IsNullOrWhiteSpace(preferredExtension) || string.Equals(fileInfo.Extension, preferredExtension, StringComparison.OrdinalIgnoreCase)) && fileInfo.LastWriteTime > dateTime)
					{
						dateTime = fileInfo.LastWriteTime;
						result = text;
					}
				}
				return result;
			}

			private static void DeleteCachedTrackFiles(string cacheFolder, string outputName)
			{
				string[] files = Directory.GetFiles(cacheFolder, outputName + ".*", SearchOption.TopDirectoryOnly);
				foreach (string path in files)
				{
					try
					{
						File.Delete(path);
					}
					catch
					{
					}
				}
			}

			private static string GetStableName(string text)
			{
				using SHA256 sHA = SHA256.Create();
				byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(text));
				StringBuilder stringBuilder = new StringBuilder();
				for (int i = 0; i < 12; i++)
				{
					stringBuilder.Append(array[i].ToString("x2"));
				}
				return stringBuilder.ToString();
			}

			private static List<string> GetOutputLines(string output)
			{
				List<string> list = new List<string>();
				if (string.IsNullOrWhiteSpace(output))
				{
					return list;
				}
				string[] array = output.Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (Core.IsUrlPath(text))
					{
						list.Add(text);
					}
				}
				return list;
			}
		}

		private class ResolvedUrl
		{
			public string VideoUrl { get; }

			public string AudioUrl { get; }

			public bool HasSeparateAudio => !string.IsNullOrWhiteSpace(AudioUrl);

			public ResolvedUrl(string videoUrl, string audioUrl = null)
			{
				VideoUrl = videoUrl;
				AudioUrl = audioUrl;
			}

			public static ResolvedUrl Single(string videoUrl)
			{
				return new ResolvedUrl(videoUrl);
			}
		}

		public class TextureSlot
		{
			public Material Material { get; }

			public string PropertyName { get; }

			public Texture OriginalTexture { get; }

			public bool AlsoApplyToBaseMap { get; }

			public TextureSlot(Material material, string propertyName, Texture originalTexture, bool alsoApplyToBaseMap = false)
			{
				Material = material;
				PropertyName = propertyName;
				OriginalTexture = originalTexture;
				AlsoApplyToBaseMap = alsoApplyToBaseMap;
			}
		}

		public class TVInstance
		{
			private VideoPlayer _videoPlayer;

			private VideoPlayer _audioVideoPlayer;

			private RenderTexture _renderTexture;

			private AudioSource _audioSource;

			private int _playId;

			private bool _hasManagedImage;

			private string _selectionKey;

			private readonly List<TVInstance> _mirrorTargets = new List<TVInstance>();

			public Renderer Renderer { get; private set; }

			public TextureSlot Slot { get; }

			public int MaterialIndex { get; }

			public FusionTarget Target { get; private set; }

			public bool IsPlaying
			{
				get
				{
					if ((Object)(object)_videoPlayer != (Object)null && !((Il2CppObjectBase)_videoPlayer).WasCollected)
					{
						return _videoPlayer.isPlaying;
					}
					return false;
				}
			}

			public TVInstance(Renderer renderer, TextureSlot slot, int materialIndex, FusionTarget target)
			{
				Renderer = renderer;
				Slot = slot;
				MaterialIndex = materialIndex;
				Target = target;
			}

			public void Refresh(Renderer renderer, FusionTarget target)
			{
				Renderer = renderer;
				Target = target;
				ClearMirrorTargets();
				StopVideo();
				_hasManagedImage = false;
				_selectionKey = null;
			}

			public void SetTarget(FusionTarget target)
			{
				Target = target;
			}

			public string GetDebugSlotName()
			{
				string value = "null";
				if ((Object)(object)Slot.OriginalTexture != (Object)null && !((Il2CppObjectBase)Slot.OriginalTexture).WasCollected)
				{
					value = ((Object)Slot.OriginalTexture).name;
				}
				string value2 = "null";
				if ((Object)(object)Slot.Material.shader != (Object)null && !((Il2CppObjectBase)Slot.Material.shader).WasCollected)
				{
					value2 = ((Object)Slot.Material.shader).name;
				}
				return $"{((Object)Slot.Material).name}:{value2}:{Slot.PropertyName}={value}";
			}

			public string GetMenuName(int index)
			{
				string value = (((Object)(object)Renderer != (Object)null && !((Il2CppObjectBase)Renderer).WasCollected) ? ((Object)((Component)Renderer).gameObject).name : "Missing Screen");
				string value2 = (IsPlaying ? "Playing" : "Stopped");
				return $"{index + 1}. {value} ({value2})";
			}

			public bool IsRendererGameObjectActive()
			{
				return TVController.IsRendererGameObjectActive(Renderer);
			}

			public float GetPrimaryScore()
			{
				//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_006b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0070: Unknown result type (might be due to invalid IL or missing references)
				//IL_0077: Unknown result type (might be due to invalid IL or missing references)
				//IL_007d: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)Renderer == (Object)null || ((Il2CppObjectBase)Renderer).WasCollected)
				{
					return -1f;
				}
				float num = 0f;
				if (Renderer.enabled)
				{
					num += 100000f;
				}
				if (IsRendererGameObjectActive())
				{
					num += 10000f;
				}
				if (IsPlaying)
				{
					num += 1000f;
				}
				try
				{
					Bounds bounds = Renderer.bounds;
					Vector3 size = ((Bounds)(ref bounds)).size;
					num += Mathf.Max(0f, size.x * size.y);
				}
				catch
				{
				}
				return num;
			}

			public void Pause()
			{
				if ((Object)(object)_videoPlayer != (Object)null && !((Il2CppObjectBase)_videoPlayer).WasCollected)
				{
					_videoPlayer.Pause();
				}
				if ((Object)(object)_audioVideoPlayer != (Object)null && !((Il2CppObjectBase)_audioVideoPlayer).WasCollected)
				{
					_audioVideoPlayer.Pause();
				}
			}

			public void Resume()
			{
				if ((Object)(object)_audioVideoPlayer != (Object)null && !((Il2CppObjectBase)_audioVideoPlayer).WasCollected)
				{
					_audioVideoPlayer.Play();
				}
				if ((Object)(object)_videoPlayer != (Object)null && !((Il2CppObjectBase)_videoPlayer).WasCollected)
				{
					_videoPlayer.Play();
				}
			}

			public bool MatchesSelection(string selectionKey)
			{
				return string.Equals(_selectionKey, selectionKey, StringComparison.Ordinal);
			}

			public void SetMirrorTargets(List<TVInstance> mirrors)
			{
				_mirrorTargets.Clear();
				if (mirrors == null)
				{
					return;
				}
				foreach (TVInstance mirror in mirrors)
				{
					if (mirror != null && mirror != this && !_mirrorTargets.Contains(mirror))
					{
						_mirrorTargets.Add(mirror);
					}
				}
			}

			public void ClearMirrorTargets()
			{
				_mirrorTargets.Clear();
			}

			public void ApplyMedia(string filePath)
			{
				ApplyMedia(filePath, filePath);
			}

			public void ApplyMedia(string filePath, string selectionKey)
			{
				_hasManagedImage = false;
				_selectionKey = selectionKey;
				if (Core.IsUrlPath(filePath))
				{
					if (IsImageUrl(filePath))
					{
						ApplyImageUrl(filePath);
					}
					else
					{
						PlayUrl(filePath);
					}
				}
				else if (IsVideoFile(filePath))
				{
					PlayVideo(filePath);
				}
				else if (IsImageFile(filePath))
				{
					ApplyImage(filePath);
				}
				else
				{
					MelonLogger.Warning("Unsupported media file: " + filePath);
				}
			}

			private void PlayUrl(string url)
			{
				StopVideo();
				ShowLoadingTexture();
				if (!UrlResolver.NeedsResolution(url))
				{
					PlayVideo(url, showLoading: false);
					return;
				}
				int resolveId = ++_playId;
				MelonLogger.Msg("Resolving URL: " + url);
				Task.Run(delegate
				{
					ResolvedUrl resolved = UrlResolver.Resolve(url);
					Core.RunOnMainThread(delegate
					{
						if (resolveId == _playId)
						{
							if (resolved == null || string.IsNullOrWhiteSpace(resolved.VideoUrl))
							{
								MelonLogger.Warning("Could not resolve URL for monitor: " + ((Object)((Component)Renderer).gameObject).name);
								ShowFailedTexture();
							}
							else
							{
								MelonLogger.Msg("Resolved URL for monitor: " + ((Object)((Component)Renderer).gameObject).name);
								PlayVideo(resolved.VideoUrl, showLoading: false, resolved.AudioUrl);
							}
						}
					});
				});
			}

			private void PlayVideo(string filePath)
			{
				PlayVideo(filePath, showLoading: true);
			}

			private void PlayVideo(string filePath, bool showLoading)
			{
				PlayVideo(filePath, showLoading, null);
			}

			private void PlayVideo(string filePath, bool showLoading, string audioUrl)
			{
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_0042: Expected O, but got Unknown
				StopVideo();
				try
				{
					_playId++;
					GameObject playbackObject = GetPlaybackObject();
					RemoveStalePlaybackComponents(playbackObject);
					if (showLoading)
					{
						ShowLoadingTexture();
					}
					_renderTexture = new RenderTexture(1280, 720, 0, (RenderTextureFormat)0);
					((Object)_renderTexture).name = "WorkableTV_RenderTexture";
					((Texture)_renderTexture).filterMode = (FilterMode)1;
					((Texture)_renderTexture).wrapMode = (TextureWrapMode)1;
					_renderTexture.Create();
					_videoPlayer = playbackObject.AddComponent<VideoPlayer>();
					_videoPlayer.source = (VideoSource)1;
					_videoPlayer.url = ToVideoPlayerUrl(filePath);
					_videoPlayer.renderMode = (VideoRenderMode)2;
					_videoPlayer.targetTexture = _renderTexture;
					_videoPlayer.isLooping = true;
					_videoPlayer.playOnAwake = false;
					_videoPlayer.waitForFirstFrame = true;
					_videoPlayer.skipOnDrop = true;
					GameObject val = GetAudioObject() ?? playbackObject;
					_audioSource = val.AddComponent<AudioSource>();
					ConfigureAudioSource(_audioSource);
					if (!string.IsNullOrWhiteSpace(audioUrl))
					{
						SetupSeparateAudioPlayer(playbackObject, audioUrl);
					}
					SetupVideoAudio();
					_videoPlayer.Prepare();
					if ((Object)(object)_audioVideoPlayer != (Object)null)
					{
						_audioVideoPlayer.Prepare();
					}
					MelonCoroutines.Start(WaitForVideoPrepare(_playId));
				}
				catch (Exception value)
				{
					MelonLogger.Error($"Error playing video on monitor {((Object)((Component)Renderer).gameObject).name}: {value}");
					ShowFailedTexture();
				}
			}

			private GameObject GetPlaybackObject()
			{
				if (!((Object)(object)Renderer != (Object)null) || ((Il2CppObjectBase)Renderer).WasCollected)
				{
					return null;
				}
				return ((Component)Renderer).gameObject;
			}

			private GameObject GetAudioObject()
			{
				GameObject groupRoot = GetGroupRoot(Renderer);
				if ((Object)(object)groupRoot != (Object)null && !((Il2CppObjectBase)groupRoot).WasCollected && groupRoot.activeInHierarchy)
				{
					return groupRoot;
				}
				return GetPlaybackObject();
			}

			private static void ConfigureAudioSource(AudioSource audioSource)
			{
				if (!((Object)(object)audioSource == (Object)null) && !((Il2CppObjectBase)audioSource).WasCollected)
				{
					audioSource.playOnAwake = false;
					audioSource.spatialBlend = 1f;
					audioSource.rolloffMode = (AudioRolloffMode)1;
					audioSource.minDistance = 1f;
					audioSource.maxDistance = 12f;
					audioSource.volume = 1f;
					audioSource.pitch = 1f;
					audioSource.loop = false;
					audioSource.mute = false;
					((Behaviour)audioSource).enabled = true;
				}
			}

			private void SetupVideoAudio()
			{
				try
				{
					if ((Object)(object)_audioVideoPlayer != (Object)null)
					{
						_videoPlayer.audioOutputMode = (VideoAudioOutputMode)0;
						return;
					}
					_videoPlayer.audioOutputMode = (VideoAudioOutputMode)1;
					_videoPlayer.controlledAudioTrackCount = 1;
					_videoPlayer.EnableAudioTrack((ushort)0, true);
					_videoPlayer.SetTargetAudioSource((ushort)0, _audioSource);
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Audio setup failed on monitor " + ((Object)((Component)Renderer).gameObject).name + "; video will still play. " + ex.Message);
				}
			}

			private void SetupSeparateAudioPlayer(GameObject tvObject, string audioUrl)
			{
				try
				{
					_videoPlayer.audioOutputMode = (VideoAudioOutputMode)0;
					_audioVideoPlayer = tvObject.AddComponent<VideoPlayer>();
					_audioVideoPlayer.source = (VideoSource)1;
					_audioVideoPlayer.url = ToVideoPlayerUrl(audioUrl);
					_audioVideoPlayer.renderMode = (VideoRenderMode)4;
					_audioVideoPlayer.isLooping = true;
					_audioVideoPlayer.playOnAwake = false;
					_audioVideoPlayer.waitForFirstFrame = false;
					_audioVideoPlayer.skipOnDrop = true;
					_audioVideoPlayer.audioOutputMode = (VideoAudioOutputMode)1;
					_audioVideoPlayer.controlledAudioTrackCount = 1;
					_audioVideoPlayer.EnableAudioTrack((ushort)0, true);
					_audioVideoPlayer.SetTargetAudioSource((ushort)0, _audioSource);
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Separate audio setup failed on monitor " + ((Object)((Component)Renderer).gameObject).name + "; video will play without split-stream audio. " + ex.Message);
					if ((Object)(object)_audioVideoPlayer != (Object)null)
					{
						Object.Destroy((Object)(object)_audioVideoPlayer);
						_audioVideoPlayer = null;
					}
				}
			}

			private void ApplyImage(string filePath)
			{
				StopVideo();
				ShowLoadingTexture();
				Texture2D imageTexture = GetImageTexture(filePath);
				if ((Object)(object)imageTexture == (Object)null)
				{
					ShowFailedTexture();
					return;
				}
				ApplyTexture((Texture)(object)imageTexture);
				_hasManagedImage = true;
				MelonLogger.Msg("Applied image on monitor: " + ((Object)((Component)Renderer).gameObject).name);
			}

			private void ApplyImageUrl(string url)
			{
				StopVideo();
				ShowLoadingTexture();
				MelonCoroutines.Start(DownloadImageUrl(url, ++_playId));
			}

			private IEnumerator DownloadImageUrl(string url, int requestId)
			{
				UnityWebRequest request = UnityWebRequestTexture.GetTexture(url);
				yield return request.SendWebRequest();
				if (requestId != _playId)
				{
					request.Dispose();
					yield break;
				}
				if ((int)request.result != 1)
				{
					MelonLogger.Warning("Image URL download failed: " + request.error);
					ShowFailedTexture();
					request.Dispose();
					yield break;
				}
				Texture2D content = DownloadHandlerTexture.GetContent(request);
				if ((Object)(object)content == (Object)null || ((Il2CppObjectBase)content).WasCollected)
				{
					ShowFailedTexture();
					request.Dispose();
					yield break;
				}
				((Object)content).name = "WorkableTV_URL_Image";
				((Object)content).hideFlags = (HideFlags)32;
				((Texture)content).filterMode = (FilterMode)1;
				((Texture)content).wrapMode = (TextureWrapMode)1;
				ApplyTexture((Texture)(object)content);
				_hasManagedImage = true;
				request.Dispose();
				MelonLogger.Msg("Applied image URL on monitor: " + ((Object)((Component)Renderer).gameObject).name);
			}

			private IEnumerator WaitForVideoPrepare(int playId)
			{
				float timer = 0f;
				while ((Object)(object)_videoPlayer != (Object)null && playId == _playId && (!IsVideoPrepared() || !IsAudioPrepared()))
				{
					timer += Time.unscaledDeltaTime;
					if (timer > 20f)
					{
						if (IsVideoPrepared() && (Object)(object)_audioVideoPlayer != (Object)null && !_audioVideoPlayer.isPrepared)
						{
							MelonLogger.Warning("Separate audio prepare timed out on monitor: " + ((Object)((Component)Renderer).gameObject).name + ". Playing video without split-stream audio.");
							StopSeparateAudioPlayer();
							break;
						}
						MelonLogger.Warning("Video prepare timed out on monitor: " + ((Object)((Component)Renderer).gameObject).name);
						ShowFailedTexture();
						yield break;
					}
					yield return null;
				}
				if ((Object)(object)_videoPlayer != (Object)null && playId == _playId)
				{
					EnsureAudioBinding();
					if ((Object)(object)_audioVideoPlayer != (Object)null)
					{
						_audioVideoPlayer.Play();
					}
					_videoPlayer.Play();
					_hasManagedImage = false;
					ApplyTexture((Texture)(object)_renderTexture);
					MelonLogger.Msg("Started playing video on monitor: " + ((Object)((Component)Renderer).gameObject).name);
				}
			}

			private void EnsureAudioBinding()
			{
				try
				{
					ConfigureAudioSource(_audioSource);
					if ((Object)(object)_audioVideoPlayer != (Object)null && !((Il2CppObjectBase)_audioVideoPlayer).WasCollected)
					{
						_videoPlayer.audioOutputMode = (VideoAudioOutputMode)0;
						_audioVideoPlayer.audioOutputMode = (VideoAudioOutputMode)1;
						_audioVideoPlayer.controlledAudioTrackCount = 1;
						_audioVideoPlayer.EnableAudioTrack((ushort)0, true);
						_audioVideoPlayer.SetTargetAudioSource((ushort)0, _audioSource);
					}
					else if ((Object)(object)_videoPlayer != (Object)null && !((Il2CppObjectBase)_videoPlayer).WasCollected)
					{
						_videoPlayer.audioOutputMode = (VideoAudioOutputMode)1;
						_videoPlayer.controlledAudioTrackCount = 1;
						_videoPlayer.EnableAudioTrack((ushort)0, true);
						_videoPlayer.SetTargetAudioSource((ushort)0, _audioSource);
					}
				}
				catch (Exception ex)
				{
					MelonLogger.Warning("Audio rebinding failed on monitor " + ((Object)((Component)Renderer).gameObject).name + ": " + ex.Message);
				}
			}

			private bool IsVideoPrepared()
			{
				if ((Object)(object)_videoPlayer != (Object)null)
				{
					return _videoPlayer.isPrepared;
				}
				return false;
			}

			private bool IsAudioPrepared()
			{
				if (!((Object)(object)_audioVideoPlayer == (Object)null))
				{
					return _audioVideoPlayer.isPrepared;
				}
				return true;
			}

			private static string ToVideoPlayerUrl(string pathOrUrl)
			{
				if (Core.IsUrlPath(pathOrUrl))
				{
					return pathOrUrl;
				}
				return Path.GetFullPath(pathOrUrl).Replace('\\', '/');
			}

			private void ShowLoadingTexture()
			{
				Texture2D loadingTexture = GetLoadingTexture();
				if ((Object)(object)loadingTexture != (Object)null && !((Il2CppObjectBase)loadingTexture).WasCollected)
				{
					ApplyTexture((Texture)(object)loadingTexture);
				}
			}

			private void ShowFailedTexture()
			{
				StopVideo();
				Texture2D failedTexture = GetFailedTexture();
				if ((Object)(object)failedTexture != (Object)null && !((Il2CppObjectBase)failedTexture).WasCollected)
				{
					ApplyTexture((Texture)(object)failedTexture);
				}
			}

			public bool IsHealthyDuplicateScan(Renderer renderer)
			{
				if ((Object)(object)renderer == (Object)null || ((Il2CppObjectBase)renderer).WasCollected || (Object)(object)Renderer == (Object)null || ((Il2CppObjectBase)Renderer).WasCollected)
				{
					return false;
				}
				if ((Object)(object)Renderer != (Object)(object)renderer)
				{
					return false;
				}
				Texture currentSlotTexture = GetCurrentSlotTexture();
				if ((Object)(object)currentSlotTexture == (Object)null || ((Il2CppObjectBase)currentSlotTexture).WasCollected)
				{
					return false;
				}
				if (currentSlotTexture is RenderTexture)
				{
					if ((Object)(object)_videoPlayer != (Object)null && !((Il2CppObjectBase)_videoPlayer).WasCollected && (Object)(object)_renderTexture != (Object)null && !((Il2CppObjectBase)_renderTexture).WasCollected && (Object)(object)_videoPlayer.targetTexture == (Object)(object)_renderTexture)
					{
						if (!_videoPlayer.isPlaying)
						{
							return _videoPlayer.isPrepared;
						}
						return true;
					}
					return false;
				}
				if (!_hasManagedImage)
				{
					return (Object)(object)currentSlotTexture == (Object)(object)Slot.OriginalTexture;
				}
				return true;
			}

			private Texture GetCurrentSlotTexture()
			{
				if ((Object)(object)Slot.Material == (Object)null || ((Il2CppObjectBase)Slot.Material).WasCollected || string.IsNullOrWhiteSpace(Slot.PropertyName))
				{
					return null;
				}
				if (!Slot.Material.HasProperty(Slot.PropertyName))
				{
					return null;
				}
				return Slot.Material.GetTexture(Slot.PropertyName);
			}

			private void ApplyTexture(Texture texture)
			{
				ApplyTextureDirect(texture);
				for (int num = _mirrorTargets.Count - 1; num >= 0; num--)
				{
					TVInstance tVInstance = _mirrorTargets[num];
					if (tVInstance == null || (Object)(object)tVInstance.Renderer == (Object)null || ((Il2CppObjectBase)tVInstance.Renderer).WasCollected)
					{
						_mirrorTargets.RemoveAt(num);
					}
					else
					{
						tVInstance.ApplyTextureDirect(texture);
						tVInstance._hasManagedImage = _hasManagedImage;
						tVInstance._selectionKey = _selectionKey;
					}
				}
			}

			private void ApplyTextureDirect(Texture texture)
			{
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
				//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)Slot.Material == (Object)null || ((Il2CppObjectBase)Slot.Material).WasCollected)
				{
					return;
				}
				Slot.Material.SetTexture(Slot.PropertyName, texture);
				Slot.Material.SetTextureScale(Slot.PropertyName, Vector2.one);
				if (Slot.AlsoApplyToBaseMap)
				{
					if (Slot.Material.HasProperty("_BaseMap1"))
					{
						Slot.Material.SetTexture("_BaseMap1", texture);
						Slot.Material.SetTextureScale("_BaseMap1", Vector2.one);
					}
					if (Slot.Material.HasProperty("_BaseMap"))
					{
						Slot.Material.SetTexture("_BaseMap", texture);
						Slot.Material.SetTextureScale("_BaseMap", Vector2.one);
					}
				}
			}

			public void StopVideo()
			{
				_playId++;
				if ((Object)(object)_videoPlayer != (Object)null)
				{
					_videoPlayer.Stop();
					_videoPlayer.targetTexture = null;
					((Behaviour)_videoPlayer).enabled = false;
					Object.Destroy((Object)(object)_videoPlayer);
					_videoPlayer = null;
				}
				if ((Object)(object)_audioVideoPlayer != (Object)null)
				{
					StopSeparateAudioPlayer();
				}
				if ((Object)(object)_audioSource != (Object)null)
				{
					_audioSource.Stop();
					_audioSource.mute = true;
					((Behaviour)_audioSource).enabled = false;
					Object.Destroy((Object)(object)_audioSource);
					_audioSource = null;
				}
				if ((Object)(object)_renderTexture != (Object)null)
				{
					_renderTexture.Release();
					Object.Destroy((Object)(object)_renderTexture);
					_renderTexture = null;
				}
			}

			private void RemoveStalePlaybackComponents(GameObject tvObject)
			{
				if ((Object)(object)tvObject == (Object)null || ((Il2CppObjectBase)tvObject).WasCollected)
				{
					return;
				}
				VideoPlayer[] array = Il2CppArrayBase<VideoPlayer>.op_Implicit(tvObject.GetComponents<VideoPlayer>());
				foreach (VideoPlayer val in array)
				{
					if (!((Object)(object)val == (Object)null) && !((Il2CppObjectBase)val).WasCollected && !((Object)(object)val == (Object)(object)_videoPlayer) && !((Object)(object)val == (Object)(object)_audioVideoPlayer))
					{
						val.Stop();
						((Behaviour)val).enabled = false;
						Object.Destroy((Object)(object)val);
					}
				}
				AudioSource[] array2 = Il2CppArrayBase<AudioSource>.op_Implicit(tvObject.GetComponents<AudioSource>());
				foreach (AudioSource val2 in array2)
				{
					if (!((Object)(object)val2 == (Object)null) && !((Il2CppObjectBase)val2).WasCollected && !((Object)(object)val2 == (Object)(object)_audioSource) && !((Object)(object)val2.clip != (Object)null))
					{
						val2.Stop();
						val2.mute = true;
						((Behaviour)val2).enabled = false;
						Object.Destroy((Object)(object)val2);
					}
				}
			}

			private void StopSeparateAudioPlayer()
			{
				if (!((Object)(object)_audioVideoPlayer == (Object)null))
				{
					_audioVideoPlayer.Stop();
					((Behaviour)_audioVideoPlayer).enabled = false;
					Object.Destroy((Object)(object)_audioVideoPlayer);
					_audioVideoPlayer = null;
				}
			}

			public void RestoreOriginal()
			{
				ClearMirrorTargets();
				StopVideo();
				ApplyTextureDirect(Slot.OriginalTexture);
				_hasManagedImage = false;
				_selectionKey = "None";
			}
		}

		public class TVGroup
		{
			public string Name { get; }

			public List<TVInstance> Screens { get; } = new List<TVInstance>();

			public TVGroup(string name)
			{
				Name = (string.IsNullOrWhiteSpace(name) ? "Screen" : name);
			}

			public void Add(TVInstance tv)
			{
				if (tv != null && !Screens.Contains(tv))
				{
					Screens.Add(tv);
				}
			}

			public TVInstance GetPrimaryScreen()
			{
				TVInstance tVInstance = null;
				float num = -1f;
				foreach (TVInstance screen in Screens)
				{
					if (screen != null)
					{
						float primaryScore = screen.GetPrimaryScore();
						if (!(primaryScore <= num))
						{
							tVInstance = screen;
							num = primaryScore;
						}
					}
				}
				TVInstance tVInstance2 = tVInstance;
				if (tVInstance2 == null)
				{
					if (Screens.Count <= 0)
					{
						return null;
					}
					tVInstance2 = Screens[0];
				}
				return tVInstance2;
			}

			public string GetMenuName(int index)
			{
				bool flag = false;
				foreach (TVInstance screen in Screens)
				{
					if (screen != null && screen.IsPlaying)
					{
						flag = true;
						break;
					}
				}
				string value = (flag ? "Playing" : "Stopped");
				return $"{index + 1}. {Name} ({Screens.Count}) ({value})";
			}

			public void Pause()
			{
				foreach (TVInstance screen in Screens)
				{
					screen?.Pause();
				}
			}

			public void Resume()
			{
				foreach (TVInstance screen in Screens)
				{
					screen?.Resume();
				}
			}
		}

		private class PendingFusionSelection
		{
			public string Selection { get; }

			public string MediaPath { get; }

			public FusionTarget Target { get; }

			public float ExpiresAt { get; }

			public PendingFusionSelection(string selection, string mediaPath, FusionTarget target, float expiresAt)
			{
				Selection = selection;
				MediaPath = mediaPath;
				Target = target;
				ExpiresAt = expiresAt;
			}
		}

		private const string ScanlineShaderName = "scanlin";

		private const string SpawnGunScreenShaderName = "spawngunscreen";

		private const string EmissionMapProperty = "_EmissionMap";

		private const string BaseMapProperty = "_BaseMap";

		private const string SpawnGunBaseMapProperty = "_BaseMap1";

		private const string MainTexProperty = "_MainTex";

		private const string PixelsProperty = "_Pixels";

		private const string SpawnGunPixelProperty = "_Pixel";

		private const string SpawnGunPixelPowerProperty = "_PixelPower";

		private const string SpawnGunNoisePowerProperty = "_NoisePower";

		private const string GlossinessProperty = "_Glossiness";

		private const string MetallicProperty = "_Metallic";

		private const string ColorProperty = "_Color";

		private const string EmissionColorProperty = "_EmissionColor";

		private const string TargetTextureName = "monitor01_d_lockdown";

		private const string SpawnGunBaseTextureName = "untitled_tv_resized_initialshadinggroup1_albedotransparency";

		private const string WorkableRenderTextureName = "workabletv_rendertexture";

		private const string WorkableUrlImageTextureName = "workabletv_url_image";

		private const string PixelsResourceName = "WorkableTV.Pixels_crt-01.png";

		private const string LoadingImageName = "Video_Loading.png";

		private const string FailedImageName = "Video_LoadFailed.png";

		private static readonly List<TVInstance> _tvInstances = new List<TVInstance>();

		private static readonly Dictionary<int, TVInstance> _materialIds = new Dictionary<int, TVInstance>();

		private static readonly Dictionary<string, Texture2D> _imageCache = new Dictionary<string, Texture2D>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<PendingFusionSelection> _pendingFusionSelections = new List<PendingFusionSelection>();

		private static readonly HashSet<int> _despawnedRootIds = new HashSet<int>();

		private static Texture2D _pixelsTexture;

		private static string _fusionSelection;

		private static float _fusionSelectionExpiresAt;

		private const float TargetedFusionSelectionLifetime = 10f;

		public static void FindAndSetupTVs()
		{
			_tvInstances.Clear();
			_materialIds.Clear();
			_despawnedRootIds.Clear();
			MelonLogger.Msg("WorkableTV is ready. Existing monitors are not changed; the selected media only applies to newly spawned monitors.");
		}

		public static void ScanForNewTVs()
		{
			try
			{
				Renderer[] array = Il2CppArrayBase<Renderer>.op_Implicit(Object.FindObjectsOfType<Renderer>(true));
				int count = _tvInstances.Count;
				Renderer[] array2 = array;
				foreach (Renderer renderer in array2)
				{
					if (IsRendererOnActivePoolee(renderer))
					{
						TryAddMonitor(renderer, forceRefreshKnownMaterial: false);
					}
				}
				int num = _tvInstances.Count - count;
				if (num > 0)
				{
					MelonLogger.Msg($"Found {num} new WorkableTV monitor(s).");
				}
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Error scanning WorkableTV monitors: {value}");
			}
		}

		public static void ScanSpawnedObject(GameObject spawned)
		{
			if ((Object)(object)spawned == (Object)null || ((Il2CppObjectBase)spawned).WasCollected)
			{
				return;
			}
			MarkObjectSpawned(spawned);
			try
			{
				Renderer[] array = Il2CppArrayBase<Renderer>.op_Implicit(spawned.GetComponentsInChildren<Renderer>(true));
				int count = _tvInstances.Count;
				Renderer[] array2 = array;
				foreach (Renderer renderer in array2)
				{
					if (IsRendererOnActivePoolee(renderer))
					{
						TryAddMonitor(renderer, forceRefreshKnownMaterial: true);
					}
				}
				int num = _tvInstances.Count - count;
				if (num > 0)
				{
					MelonLogger.Msg($"Spawned object \"{((Object)spawned).name}\" added {num} WorkableTV monitor(s).");
				}
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Error scanning spawned object \"{((Object)spawned).name}\": {value}");
			}
		}

		private static bool TryAddMonitor(Renderer renderer, bool forceRefreshKnownMaterial)
		{
			if (!IsRendererOnActivePoolee(renderer))
			{
				return false;
			}
			Material[] array = Il2CppArrayBase<Material>.op_Implicit((Il2CppArrayBase<Material>)(object)renderer.materials);
			if (array == null || array.Length == 0)
			{
				return false;
			}
			bool result = false;
			for (int i = 0; i < array.Length; i++)
			{
				Material val = array[i];
				if ((Object)(object)val == (Object)null || ((Il2CppObjectBase)val).WasCollected)
				{
					continue;
				}
				int instanceID = ((Object)val).GetInstanceID();
				TVInstance value;
				bool flag = _materialIds.TryGetValue(instanceID, out value);
				TextureSlot monitorSlot = GetMonitorSlot(val, ShouldUseMediaOnNewMonitor(), flag);
				if (monitorSlot == null)
				{
					if (flag)
					{
						if (!forceRefreshKnownMaterial && value.IsHealthyDuplicateScan(renderer) && value.MatchesSelection(GetCurrentSelectionKey()))
						{
							result = true;
							continue;
						}
						value.Refresh(renderer, CreateFusionTarget(renderer, i));
						MelonLogger.Msg("Reactivated pooled monitor player with stale texture slot: " + ((Object)((Component)renderer).gameObject).name + " Target=" + value.Target.GetDebugName());
						ApplySelectionForReactivatedMonitor(value, broadcast: true);
						result = true;
					}
					continue;
				}
				FusionTarget target = CreateFusionTarget(renderer, i);
				if (flag)
				{
					if (!forceRefreshKnownMaterial && value.IsHealthyDuplicateScan(renderer) && value.MatchesSelection(GetCurrentSelectionKey()))
					{
						value.SetTarget(target);
						result = true;
						continue;
					}
					value.Refresh(renderer, target);
					MelonLogger.Msg("Reactivated pooled monitor player: " + ((Object)((Component)renderer).gameObject).name + " Target=" + value.Target.GetDebugName());
					ApplySelectionForReactivatedMonitor(value, broadcast: true);
					result = true;
				}
				else
				{
					TVInstance tVInstance = new TVInstance(renderer, monitorSlot, i, target);
					_materialIds[instanceID] = tVInstance;
					_tvInstances.Add(tVInstance);
					MelonLogger.Msg($"Created monitor player: {((Object)((Component)renderer).gameObject).name} ({tVInstance.GetDebugSlotName()}) Target={tVInstance.Target.GetDebugName()}");
					ApplySelectionForSpawnedMonitor(tVInstance, broadcast: true);
					result = true;
				}
			}
			return result;
		}

		private static TextureSlot GetMonitorSlot(Material material, bool canConvertSpawnGunScreen, bool wasKnownMaterial)
		{
			if ((Object)(object)material == (Object)null || ((Il2CppObjectBase)material).WasCollected)
			{
				return null;
			}
			Shader shader = material.shader;
			if ((Object)(object)shader == (Object)null || ((Il2CppObjectBase)shader).WasCollected)
			{
				return null;
			}
			string text = ((Object)shader).name.ToLowerInvariant();
			if (text.Contains("spawngunscreen"))
			{
				return GetSpawnGunScreenSlot(material, canConvertSpawnGunScreen, wasKnownMaterial);
			}
			if (!text.Contains("scanlin"))
			{
				return null;
			}
			if (!material.HasProperty("_EmissionMap"))
			{
				return null;
			}
			Texture texture = material.GetTexture("_EmissionMap");
			if ((Object)(object)texture == (Object)null || ((Il2CppObjectBase)texture).WasCollected)
			{
				return null;
			}
			string text2 = ((Object)texture).name.ToLowerInvariant();
			if (!wasKnownMaterial && !text2.Contains("monitor01_d_lockdown") && !IsWorkableMediaTexture(texture))
			{
				return null;
			}
			return new TextureSlot(material, "_EmissionMap", texture);
		}

		private static TextureSlot GetSpawnGunScreenSlot(Material material, bool canConvert, bool wasKnownMaterial)
		{
			Texture firstTexture = GetFirstTexture(material, "_BaseMap1", "_BaseMap");
			Texture val = (material.HasProperty("_EmissionMap") ? material.GetTexture("_EmissionMap") : null);
			if (!wasKnownMaterial && !canConvert)
			{
				return null;
			}
			if (!material.HasProperty("_EmissionMap"))
			{
				return null;
			}
			bool flag = false;
			if ((Object)(object)firstTexture != (Object)null && !((Il2CppObjectBase)firstTexture).WasCollected)
			{
				flag = ((Object)firstTexture).name.ToLowerInvariant().Contains("untitled_tv_resized_initialshadinggroup1_albedotransparency");
			}
			bool flag2 = IsWorkableMediaTexture(firstTexture) || IsWorkableMediaTexture(val);
			if (!wasKnownMaterial && !flag && !flag2)
			{
				return null;
			}
			SetupSpawnGunScreenMaterial(material);
			return new TextureSlot(material, "_EmissionMap", val, alsoApplyToBaseMap: true);
		}

		private static bool IsWorkableMediaTexture(Texture texture)
		{
			if ((Object)(object)texture == (Object)null || ((Il2CppObjectBase)texture).WasCollected)
			{
				return false;
			}
			string text = ((Object)texture).name?.ToLowerInvariant() ?? "";
			if (text == "workabletv_rendertexture" || text == "workabletv_url_image" || text == Path.GetFileNameWithoutExtension("Video_Loading.png").ToLowerInvariant() || text == Path.GetFileNameWithoutExtension("Video_LoadFailed.png").ToLowerInvariant())
			{
				return true;
			}
			foreach (Texture2D value in _imageCache.Values)
			{
				if ((Object)(object)value != (Object)null && !((Il2CppObjectBase)value).WasCollected && (Object)(object)value == (Object)(object)texture)
				{
					return true;
				}
			}
			return false;
		}

		private static Texture GetFirstTexture(Material material, params string[] propertyNames)
		{
			foreach (string text in propertyNames)
			{
				if (material.HasProperty(text))
				{
					Texture texture = material.GetTexture(text);
					if ((Object)(object)texture != (Object)null && !((Il2CppObjectBase)texture).WasCollected)
					{
						return texture;
					}
				}
			}
			return null;
		}

		private static void SetupScanlineMaterial(Material material, Texture oldBaseTexture)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			if (material.HasProperty("_MainTex") && (Object)(object)oldBaseTexture != (Object)null && !((Il2CppObjectBase)oldBaseTexture).WasCollected)
			{
				material.SetTexture("_MainTex", oldBaseTexture);
			}
			if (material.HasProperty("_Glossiness"))
			{
				material.SetFloat("_Glossiness", 0.263f);
			}
			if (material.HasProperty("_Metallic"))
			{
				material.SetFloat("_Metallic", 0.561f);
			}
			if (material.HasProperty("_Color"))
			{
				material.SetColor("_Color", Color.white);
			}
			if (material.HasProperty("_EmissionColor"))
			{
				material.SetColor("_EmissionColor", Color.white);
			}
			Texture2D pixelsTexture = GetPixelsTexture();
			if ((Object)(object)pixelsTexture != (Object)null && material.HasProperty("_Pixels"))
			{
				material.SetTexture("_Pixels", (Texture)(object)pixelsTexture);
				material.SetTextureScale("_Pixels", new Vector2(256f, 256f));
			}
		}

		private static void SetupSpawnGunScreenMaterial(Material material)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			if (material.HasProperty("_EmissionMap"))
			{
				Texture2D pixelsTexture = GetPixelsTexture();
				if ((Object)(object)pixelsTexture != (Object)null && material.HasProperty("_Pixel"))
				{
					material.SetTexture("_Pixel", (Texture)(object)pixelsTexture);
					material.SetTextureScale("_Pixel", new Vector2(512f, 512f));
				}
				if (material.HasProperty("_PixelPower"))
				{
					material.SetFloat("_PixelPower", 1f);
				}
				if (material.HasProperty("_NoisePower"))
				{
					material.SetFloat("_NoisePower", 0f);
				}
				if (material.HasProperty("_EmissionColor"))
				{
					material.SetColor("_EmissionColor", Color.white);
				}
				material.SetTextureScale("_EmissionMap", Vector2.one);
			}
		}

		public static void ApplyVideoToAllTVs(string videoName)
		{
			string videoFilePath = Core.GetVideoFilePath(videoName);
			if (string.IsNullOrEmpty(videoFilePath) || (!Core.IsUrlPath(videoFilePath) && !File.Exists(videoFilePath)))
			{
				MelonLogger.Warning("Media does not exist or URL is empty: " + videoFilePath);
				return;
			}
			foreach (TVInstance tvInstance in _tvInstances)
			{
				if (IsTVActive(tvInstance))
				{
					tvInstance.ApplyMedia(videoFilePath, videoName);
				}
			}
		}

		public static void ApplySelectionToTV(TVInstance tv, string mediaName, bool broadcast)
		{
			if (!IsTVActive(tv))
			{
				return;
			}
			if (mediaName == "None")
			{
				tv.RestoreOriginal();
				if (broadcast)
				{
					BroadcastMediaSelection(tv, mediaName);
				}
			}
			else
			{
				ApplyMediaToTV(tv, mediaName, broadcast);
			}
		}

		public static void ApplySelectionToGroup(TVGroup group, string mediaName, bool broadcast)
		{
			if (group == null)
			{
				return;
			}
			if (mediaName == "None")
			{
				foreach (TVInstance screen in group.Screens)
				{
					if (IsTVActive(screen))
					{
						ApplySelectionToTV(screen, mediaName, broadcast);
					}
				}
				return;
			}
			TVInstance primaryScreen = group.GetPrimaryScreen();
			if (primaryScreen == null)
			{
				return;
			}
			List<TVInstance> list = new List<TVInstance>();
			foreach (TVInstance screen2 in group.Screens)
			{
				if (screen2 != null && screen2 != primaryScreen)
				{
					screen2.StopVideo();
					screen2.ClearMirrorTargets();
					if (IsTVActive(screen2))
					{
						list.Add(screen2);
					}
				}
			}
			primaryScreen.SetMirrorTargets(list);
			ApplySelectionToTV(primaryScreen, mediaName, broadcast);
		}

		public static List<TVGroup> GetManagedTVGroups()
		{
			Dictionary<int, TVGroup> dictionary = new Dictionary<int, TVGroup>();
			for (int num = _tvInstances.Count - 1; num >= 0; num--)
			{
				TVInstance tVInstance = _tvInstances[num];
				if (tVInstance == null || (Object)(object)tVInstance.Renderer == (Object)null || ((Il2CppObjectBase)tVInstance.Renderer).WasCollected)
				{
					_tvInstances.RemoveAt(num);
				}
				else if (IsTVActive(tVInstance))
				{
					GameObject groupRoot = GetGroupRoot(tVInstance.Renderer);
					if (!IsRootMarkedDespawned(groupRoot))
					{
						int key = (((Object)(object)groupRoot != (Object)null && !((Il2CppObjectBase)groupRoot).WasCollected) ? ((Object)groupRoot).GetInstanceID() : ((Object)tVInstance.Renderer).GetInstanceID());
						if (!dictionary.TryGetValue(key, out var value))
						{
							value = (dictionary[key] = new TVGroup(((Object)(object)groupRoot != (Object)null && !((Il2CppObjectBase)groupRoot).WasCollected) ? ((Object)groupRoot).name : ((Object)((Component)tVInstance.Renderer).gameObject).name));
						}
						value.Add(tVInstance);
					}
				}
			}
			List<TVGroup> list = new List<TVGroup>(dictionary.Values);
			list.Sort((TVGroup a, TVGroup b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase));
			return list;
		}

		private static bool IsTVActive(TVInstance tv)
		{
			if (tv != null && (Object)(object)tv.Renderer != (Object)null && !((Il2CppObjectBase)tv.Renderer).WasCollected && !IsRootMarkedDespawned(GetGroupRoot(tv.Renderer)))
			{
				return IsRendererOnActivePoolee(tv.Renderer);
			}
			return false;
		}

		private static bool IsRendererGameObjectActive(Renderer renderer)
		{
			if ((Object)(object)renderer != (Object)null && !((Il2CppObjectBase)renderer).WasCollected && (Object)(object)((Component)renderer).gameObject != (Object)null && !((Il2CppObjectBase)((Component)renderer).gameObject).WasCollected)
			{
				return ((Component)renderer).gameObject.activeInHierarchy;
			}
			return false;
		}

		private static bool IsRendererOnActivePoolee(Renderer renderer)
		{
			if ((Object)(object)renderer == (Object)null || ((Il2CppObjectBase)renderer).WasCollected)
			{
				return false;
			}
			Poolee componentInParent = ((Component)renderer).GetComponentInParent<Poolee>();
			if ((Object)(object)componentInParent != (Object)null && !((Il2CppObjectBase)componentInParent).WasCollected)
			{
				GameObject gameObject = ((Component)componentInParent).gameObject;
				if (IsRootMarkedDespawned(gameObject))
				{
					return false;
				}
				if ((Object)(object)gameObject != (Object)null && !((Il2CppObjectBase)gameObject).WasCollected)
				{
					return gameObject.activeInHierarchy;
				}
				return false;
			}
			GameObject transformRoot = GetTransformRoot(((Component)renderer).transform);
			if (IsRootMarkedDespawned(transformRoot))
			{
				return false;
			}
			if ((Object)(object)transformRoot != (Object)null && !((Il2CppObjectBase)transformRoot).WasCollected)
			{
				return transformRoot.activeInHierarchy;
			}
			return false;
		}

		public static void MarkObjectSpawned(GameObject root)
		{
			if (!((Object)(object)root == (Object)null) && !((Il2CppObjectBase)root).WasCollected)
			{
				GameObject val = GetGroupRoot(root.GetComponentInChildren<Renderer>(true)) ?? root;
				if (!((Object)(object)val == (Object)null) && !((Il2CppObjectBase)val).WasCollected)
				{
					_despawnedRootIds.Remove(((Object)val).GetInstanceID());
				}
			}
		}

		private static bool IsRootMarkedDespawned(GameObject root)
		{
			if ((Object)(object)root != (Object)null && !((Il2CppObjectBase)root).WasCollected)
			{
				return _despawnedRootIds.Contains(((Object)root).GetInstanceID());
			}
			return false;
		}

		private static GameObject GetGroupRoot(Renderer renderer)
		{
			if ((Object)(object)renderer == (Object)null || ((Il2CppObjectBase)renderer).WasCollected)
			{
				return null;
			}
			Poolee componentInParent = ((Component)renderer).GetComponentInParent<Poolee>();
			if ((Object)(object)componentInParent != (Object)null && !((Il2CppObjectBase)componentInParent).WasCollected)
			{
				return ((Component)componentInParent).gameObject;
			}
			return GetTransformRoot(((Component)renderer).transform) ?? ((Component)renderer).gameObject;
		}

		private static GameObject GetTransformRoot(Transform transform)
		{
			if ((Object)(object)transform == (Object)null || ((Il2CppObjectBase)transform).WasCollected)
			{
				return null;
			}
			Transform val = transform;
			Transform val2 = val;
			while ((Object)(object)val != (Object)null && !((Il2CppObjectBase)val).WasCollected)
			{
				val2 = val;
				if ((Object)(object)val.parent == (Object)null || ((Il2CppObjectBase)val.parent).WasCollected)
				{
					break;
				}
				val = val.parent;
			}
			if (!((Object)(object)val2 != (Object)null) || ((Il2CppObjectBase)val2).WasCollected)
			{
				return null;
			}
			return ((Component)val2).gameObject;
		}

		private static void ApplyMediaToTV(TVInstance tv, string mediaName, bool broadcast)
		{
			string videoFilePath = Core.GetVideoFilePath(mediaName);
			if (!string.IsNullOrEmpty(videoFilePath) && (Core.IsUrlPath(videoFilePath) || File.Exists(videoFilePath)) && IsTVActive(tv))
			{
				tv.ApplyMedia(videoFilePath, mediaName);
				if (broadcast)
				{
					BroadcastMediaSelection(tv, mediaName);
				}
			}
		}

		private static void ApplySelectionForSpawnedMonitor(TVInstance tv, bool broadcast)
		{
			if (!TryApplyPendingFusionSelection(tv))
			{
				if (TryGetFusionMediaPath(out var mediaPath))
				{
					tv.ApplyMedia(mediaPath, _fusionSelection);
				}
				else if (Core.SelectedVideo != "None")
				{
					ApplyMediaToTV(tv, Core.SelectedVideo, broadcast);
				}
				else
				{
					tv.RestoreOriginal();
				}
			}
		}

		private static void ApplySelectionForReactivatedMonitor(TVInstance tv, bool broadcast)
		{
			MelonCoroutines.Start(ApplySelectionForReactivatedMonitorNextFrame(tv, broadcast));
		}

		private static IEnumerator ApplySelectionForReactivatedMonitorNextFrame(TVInstance tv, bool broadcast)
		{
			yield return null;
			if (IsTVActive(tv))
			{
				ApplySelectionForSpawnedMonitor(tv, broadcast);
			}
		}

		private static void BroadcastMediaSelection(TVInstance tv, string mediaName)
		{
			if (tv.Target.HasTarget)
			{
				FusionSync.BroadcastSelection(mediaName, tv.Target);
			}
			else
			{
				MelonCoroutines.Start(BroadcastWhenFusionTargetExists(tv, mediaName));
			}
		}

		private static IEnumerator BroadcastWhenFusionTargetExists(TVInstance tv, string mediaName)
		{
			float timer = 0f;
			while (timer < 5f)
			{
				if (!IsTVActive(tv))
				{
					yield break;
				}
				FusionTarget target = CreateFusionTarget(tv.Renderer, tv.MaterialIndex);
				if (target.HasTarget)
				{
					tv.SetTarget(target);
					FusionSync.BroadcastSelection(mediaName, target);
					yield break;
				}
				timer += 0.25f;
				yield return (object)new WaitForSeconds(0.25f);
			}
			MelonLogger.Msg("Could not find Fusion target for monitor broadcast: " + mediaName);
		}

		public static void ApplyFusionSelection(string selection)
		{
			ApplyFusionSelection(selection, FusionTarget.None);
		}

		public static void ApplyFusionSelection(string selection, FusionTarget target)
		{
			if (selection == "None")
			{
				ApplyFusionDefaultSelection(target);
				return;
			}
			if (target.HasTarget)
			{
				ApplyTargetedFusionSelection(selection, target);
				return;
			}
			_fusionSelection = selection;
			_fusionSelectionExpiresAt = Time.realtimeSinceStartup + 10f;
			string selectionDisplayName = Core.GetSelectionDisplayName(selection);
			if (!TryResolveFusionMediaPath(selection, out var mediaPath))
			{
				Core.SendWorkableNotification(selectionDisplayName, "Fusion media received, but no matching file was found in Media.", (NotificationType)1);
				return;
			}
			Core.SendWorkableNotification(selectionDisplayName, "Fusion media received.", (NotificationType)0);
			foreach (TVInstance tvInstance in _tvInstances)
			{
				if (IsTVActive(tvInstance))
				{
					tvInstance.ApplyMedia(mediaPath, selection);
				}
			}
		}

		private static void ApplyFusionDefaultSelection(FusionTarget target)
		{
			if (!target.HasTarget)
			{
				foreach (TVInstance tvInstance in _tvInstances)
				{
					if (IsTVActive(tvInstance))
					{
						tvInstance.RestoreOriginal();
					}
				}
				return;
			}
			foreach (TVInstance tvInstance2 in _tvInstances)
			{
				if (IsTVActive(tvInstance2) && tvInstance2.Target.Matches(target))
				{
					tvInstance2.RestoreOriginal();
					return;
				}
			}
			RemoveExpiredPendingFusionSelections();
			_pendingFusionSelections.Add(new PendingFusionSelection("None", null, target, Time.realtimeSinceStartup + 10f));
		}

		private static void ApplyTargetedFusionSelection(string selection, FusionTarget target)
		{
			string selectionDisplayName = Core.GetSelectionDisplayName(selection);
			if (!TryResolveFusionMediaPath(selection, out var mediaPath))
			{
				Core.SendWorkableNotification(selectionDisplayName, "Fusion media received, but no matching file was found in Media.", (NotificationType)1);
				return;
			}
			foreach (TVInstance tvInstance in _tvInstances)
			{
				if (IsTVActive(tvInstance) && tvInstance.Target.Matches(target))
				{
					Core.SendWorkableNotification(selectionDisplayName, "Fusion media received for " + ((Object)((Component)tvInstance.Renderer).gameObject).name + ".", (NotificationType)0);
					tvInstance.ApplyMedia(mediaPath, selection);
					return;
				}
			}
			RemoveExpiredPendingFusionSelections();
			_pendingFusionSelections.Add(new PendingFusionSelection(selection, mediaPath, target, Time.realtimeSinceStartup + 10f));
			MelonLogger.Msg("Queued targeted Fusion media until monitor exists: " + target.GetDebugName());
		}

		private static bool ShouldUseMediaOnNewMonitor()
		{
			if (!(Core.SelectedVideo != "None"))
			{
				return HasActiveFusionSelection();
			}
			return true;
		}

		private static bool HasActiveFusionSelection()
		{
			if (!string.IsNullOrWhiteSpace(_fusionSelection))
			{
				return Time.realtimeSinceStartup <= _fusionSelectionExpiresAt;
			}
			return false;
		}

		private static bool TryGetFusionMediaPath(out string mediaPath)
		{
			mediaPath = null;
			if (!HasActiveFusionSelection())
			{
				return false;
			}
			return TryResolveFusionMediaPath(_fusionSelection, out mediaPath);
		}

		private static bool TryResolveFusionMediaPath(string selection, out string mediaPath)
		{
			mediaPath = null;
			if (string.IsNullOrWhiteSpace(selection) || selection == "None")
			{
				return false;
			}
			if (Core.IsNetworkUrlSelection(selection))
			{
				mediaPath = Core.GetVideoFilePath(selection);
				return !string.IsNullOrWhiteSpace(mediaPath);
			}
			string fileName = Path.GetFileName(selection);
			if (string.IsNullOrWhiteSpace(fileName))
			{
				return false;
			}
			string text = Path.Combine(Core.MediaFolderPath, fileName);
			if (!File.Exists(text))
			{
				return false;
			}
			mediaPath = text;
			return true;
		}

		public static void RestoreAllTVs()
		{
			foreach (TVInstance tvInstance in _tvInstances)
			{
				if (IsTVActive(tvInstance))
				{
					tvInstance.RestoreOriginal();
				}
			}
		}

		public static void StopTVsForObject(GameObject root)
		{
			if ((Object)(object)root == (Object)null || ((Il2CppObjectBase)root).WasCollected)
			{
				return;
			}
			int instanceID = ((Object)root).GetInstanceID();
			_despawnedRootIds.Add(instanceID);
			foreach (TVInstance tvInstance in _tvInstances)
			{
				if (tvInstance != null && !((Object)(object)tvInstance.Renderer == (Object)null) && !((Il2CppObjectBase)tvInstance.Renderer).WasCollected)
				{
					GameObject groupRoot = GetGroupRoot(tvInstance.Renderer);
					if (!((Object)(object)groupRoot == (Object)null) && !((Il2CppObjectBase)groupRoot).WasCollected && ((Object)groupRoot).GetInstanceID() == instanceID)
					{
						tvInstance.StopVideo();
					}
				}
			}
		}

		public static void StopAllTVs()
		{
			foreach (TVInstance tvInstance in _tvInstances)
			{
				tvInstance.StopVideo();
			}
			_tvInstances.Clear();
			_materialIds.Clear();
			_despawnedRootIds.Clear();
			_fusionSelection = null;
			_fusionSelectionExpiresAt = 0f;
			_pendingFusionSelections.Clear();
		}

		private static bool TryApplyPendingFusionSelection(TVInstance tv)
		{
			RemoveExpiredPendingFusionSelections();
			for (int num = _pendingFusionSelections.Count - 1; num >= 0; num--)
			{
				PendingFusionSelection pendingFusionSelection = _pendingFusionSelections[num];
				if (tv.Target.Matches(pendingFusionSelection.Target))
				{
					if (pendingFusionSelection.Selection == "None")
					{
						tv.RestoreOriginal();
						_pendingFusionSelections.RemoveAt(num);
						Core.SendWorkableNotification("Game Default", "Fusion media received for " + ((Object)((Component)tv.Renderer).gameObject).name + ".", (NotificationType)0);
						return true;
					}
					tv.ApplyMedia(pendingFusionSelection.MediaPath, pendingFusionSelection.Selection);
					_pendingFusionSelections.RemoveAt(num);
					Core.SendWorkableNotification(Core.GetSelectionDisplayName(pendingFusionSelection.Selection), "Fusion media received for " + ((Object)((Component)tv.Renderer).gameObject).name + ".", (NotificationType)0);
					return true;
				}
			}
			return false;
		}

		private static string GetCurrentSelectionKey()
		{
			if (!string.IsNullOrWhiteSpace(_fusionSelection) && HasActiveFusionSelection())
			{
				return _fusionSelection;
			}
			return Core.SelectedVideo;
		}

		private static void RemoveExpiredPendingFusionSelections()
		{
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			for (int num = _pendingFusionSelections.Count - 1; num >= 0; num--)
			{
				if (_pendingFusionSelections[num].ExpiresAt <= realtimeSinceStartup)
				{
					_pendingFusionSelections.RemoveAt(num);
				}
			}
		}

		private static FusionTarget CreateFusionTarget(Renderer renderer, int materialIndex)
		{
			try
			{
				Poolee componentInParent = ((Component)renderer).GetComponentInParent<Poolee>();
				if ((Object)(object)componentInParent == (Object)null || ((Il2CppObjectBase)componentInParent).WasCollected)
				{
					return FusionTarget.None;
				}
				NetworkEntity val = default(NetworkEntity);
				if (!PooleeExtender.Cache.TryGet(componentInParent, ref val) || val == null)
				{
					return FusionTarget.None;
				}
				string transformPath = GetTransformPath(((Component)componentInParent).transform, ((Component)renderer).transform);
				if (string.IsNullOrWhiteSpace(transformPath))
				{
					return FusionTarget.None;
				}
				return new FusionTarget(hasTarget: true, val.ID, transformPath, materialIndex);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Could not create Fusion target for monitor: " + ex.Message);
				return FusionTarget.None;
			}
		}

		private static string GetTransformPath(Transform root, Transform target)
		{
			if ((Object)(object)root == (Object)null || (Object)(object)target == (Object)null)
			{
				return null;
			}
			if ((Object)(object)root == (Object)(object)target)
			{
				return ".";
			}
			Stack<string> stack = new Stack<string>();
			Transform val = target;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)root)
			{
				stack.Push($"{((Object)val).name}[{val.GetSiblingIndex()}]");
				val = val.parent;
			}
			if ((Object)(object)val != (Object)(object)root)
			{
				return null;
			}
			return string.Join("/", stack);
		}

		private static Texture2D GetImageTexture(string filePath)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			if (_imageCache.TryGetValue(filePath, out var value) && (Object)(object)value != (Object)null && !((Il2CppObjectBase)value).WasCollected)
			{
				return value;
			}
			try
			{
				byte[] array = File.ReadAllBytes(filePath);
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array)))
				{
					Object.Destroy((Object)(object)val);
					MelonLogger.Warning("Failed to load image media: " + filePath);
					return null;
				}
				((Object)val).name = Path.GetFileNameWithoutExtension(filePath);
				((Object)val).hideFlags = (HideFlags)32;
				((Texture)val).filterMode = (FilterMode)1;
				((Texture)val).wrapMode = (TextureWrapMode)1;
				_imageCache[filePath] = val;
				return val;
			}
			catch (Exception value2)
			{
				MelonLogger.Error($"Error loading image media \"{filePath}\": {value2}");
				return null;
			}
		}

		private static Texture2D GetPixelsTexture()
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Expected O, but got Unknown
			if ((Object)(object)_pixelsTexture != (Object)null && !((Il2CppObjectBase)_pixelsTexture).WasCollected)
			{
				return _pixelsTexture;
			}
			try
			{
				using Stream stream = typeof(TVController).Assembly.GetManifestResourceStream("WorkableTV.Pixels_crt-01.png");
				if (stream == null)
				{
					MelonLogger.Warning("Missing embedded pixels texture resource: WorkableTV.Pixels_crt-01.png");
					return null;
				}
				byte[] array = new byte[stream.Length];
				stream.Read(array, 0, array.Length);
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				if (!ImageConversion.LoadImage(val, Il2CppStructArray<byte>.op_Implicit(array)))
				{
					Object.Destroy((Object)(object)val);
					MelonLogger.Warning("Failed to load embedded pixels texture.");
					return null;
				}
				((Object)val).name = "Pixels_crt-01";
				((Object)val).hideFlags = (HideFlags)32;
				((Texture)val).filterMode = (FilterMode)1;
				((Texture)val).wrapMode = (TextureWrapMode)0;
				_pixelsTexture = val;
				return _pixelsTexture;
			}
			catch (Exception value)
			{
				MelonLogger.Error($"Error loading embedded pixels texture: {value}");
				return null;
			}
		}

		private static Texture2D GetRuntimeTexture(string fileName)
		{
			if (string.IsNullOrWhiteSpace(Core.RuntimeFolderPath))
			{
				return null;
			}
			string text = Path.Combine(Core.RuntimeFolderPath, fileName);
			if (!File.Exists(text))
			{
				MelonLogger.Warning("Runtime texture missing: " + text);
				return null;
			}
			return GetImageTexture(text);
		}

		private static Texture2D GetLoadingTexture()
		{
			return GetRuntimeTexture("Video_Loading.png");
		}

		private static Texture2D GetFailedTexture()
		{
			return GetRuntimeTexture("Video_LoadFailed.png");
		}

		private static bool IsVideoFile(string filePath)
		{
			return string.Equals(Path.GetExtension(filePath), ".mp4", StringComparison.OrdinalIgnoreCase);
		}

		private static bool IsImageFile(string filePath)
		{
			string extension = Path.GetExtension(filePath);
			if (!string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase) && !string.Equals(extension, ".jpg", StringComparison.OrdinalIgnoreCase))
			{
				return string.Equals(extension, ".jpeg", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool IsImageUrl(string url)
		{
			if (string.IsNullOrWhiteSpace(url))
			{
				return false;
			}
			string text = url.ToLowerInvariant();
			int num = text.IndexOf('?');
			if (num >= 0)
			{
				text = text.Substring(0, num);
			}
			if (!text.EndsWith(".png") && !text.EndsWith(".jpg") && !text.EndsWith(".jpeg"))
			{
				return text.EndsWith(".webp");
			}
			return true;
		}
	}
	public readonly struct FusionTarget
	{
		public static FusionTarget None => new FusionTarget(hasTarget: false, 0, null, -1);

		public bool HasTarget { get; }

		public ushort EntityId { get; }

		public string RendererPath { get; }

		public int MaterialIndex { get; }

		public FusionTarget(bool hasTarget, ushort entityId, string rendererPath, int materialIndex)
		{
			HasTarget = hasTarget && entityId != 0 && !string.IsNullOrWhiteSpace(rendererPath) && materialIndex >= 0;
			EntityId = entityId;
			RendererPath = rendererPath;
			MaterialIndex = materialIndex;
		}

		public bool Matches(FusionTarget other)
		{
			if (HasTarget && other.HasTarget && EntityId == other.EntityId && MaterialIndex == other.MaterialIndex)
			{
				return string.Equals(RendererPath, other.RendererPath, StringComparison.Ordinal);
			}
			return false;
		}

		public string GetDebugName()
		{
			if (!HasTarget)
			{
				return "None";
			}
			return $"{EntityId}:{RendererPath}:{MaterialIndex}";
		}
	}
}