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 MainThreadQueue = new ConcurrentQueue(); private static MelonPreferences_Category _preferences; private static MelonPreferences_Entry _selectedVideoEntry; private static MelonPreferences_Entry _urlEntry; private static bool _shouldAutoSelectFirstMedia; private static Page _menuPage; private static Page _mediaSelectionPage; private static Page _mediaManagerPage; private static StringElement _urlElement; private static List _videoButtons = new List(); private static List _videoFileNames = new List(); private static readonly List _screenPages = new List(); private readonly HashSet _queuedSpawnedObjectIds = new HashSet(); 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("SelectedVideo", "None", "Selected Video", (string)null, false, false, (ValueValidator)null, (string)null); _urlEntry = _preferences.CreateEntry("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)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 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)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(); _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.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 task = process.StandardOutput.ReadToEndAsync(); Task 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 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 task = process.StandardOutput.ReadToEndAsync(); Task 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 GetOutputLines(string output) { List list = new List(); 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 _mirrorTargets = new List(); 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 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.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(); 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(); _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.op_Implicit(tvObject.GetComponents()); 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.op_Implicit(tvObject.GetComponents()); 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 Screens { get; } = new List(); 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 _tvInstances = new List(); private static readonly Dictionary _materialIds = new Dictionary(); private static readonly Dictionary _imageCache = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly List _pendingFusionSelections = new List(); private static readonly HashSet _despawnedRootIds = new HashSet(); 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.op_Implicit(Object.FindObjectsOfType(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.op_Implicit(spawned.GetComponentsInChildren(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.op_Implicit((Il2CppArrayBase)(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 list = new List(); 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 GetManagedTVGroups() { Dictionary dictionary = new Dictionary(); 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 list = new List(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(); 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(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(); 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(); 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 stack = new Stack(); 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.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.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}"; } } }