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