using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using TVSync.Networking; using TVSync.Patches; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Video; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("TVSync")] [assembly: AssemblyDescription("Synchronized YouTube TV playback for Lethal Company multiplayer")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCompany("TVSync")] [assembly: AssemblyProduct("TVSync")] [assembly: AssemblyCopyright("Copyright 2025")] [assembly: ComVisible(false)] [assembly: Guid("ad955809-1ecb-4d74-a640-73a1179c4823")] [assembly: AssemblyInformationalVersion("2.0.0")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.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 TVSync { public static class ChatHelper { private static string _lastMessage = ""; public static void ShowMessage(HUDManager hud, string message, string senderName) { if (!((Object)(object)hud == (Object)null) && !string.IsNullOrEmpty(message) && !(_lastMessage == message)) { _lastMessage = message; hud.PingHUDElement(hud.Chat, 4f, 1f, 0.2f); if (hud.ChatMessageHistory.Count >= 4) { hud.ChatMessageHistory.RemoveAt(0); } string item = (string.IsNullOrEmpty(senderName) ? ("" + message + "") : ("" + senderName + ": " + message + "")); hud.ChatMessageHistory.Add(item); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < hud.ChatMessageHistory.Count; i++) { stringBuilder.Append("\n"); stringBuilder.Append(hud.ChatMessageHistory[i]); } ((TMP_Text)hud.chatText).text = stringBuilder.ToString(); } } public static void ResetDedup() { _lastMessage = ""; } } public class TVSyncConfig { public ConfigEntry Language { get; private set; } public ConfigEntry DriftThresholdMs { get; private set; } public ConfigEntry SyncIntervalSec { get; private set; } public ConfigEntry ReadyTimeoutSec { get; private set; } public ConfigEntry MaxSeekThresholdMs { get; private set; } public Lang LangData { get; private set; } public void Init() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "TVSync.cfg"), true); Language = val.Bind("General", "Language", "en", "en = English, ru = Русский"); DriftThresholdMs = val.Bind("Sync", "DriftThresholdMs", 150f, "Maximum drift in ms before correction is applied"); SyncIntervalSec = val.Bind("Sync", "SyncIntervalSec", 3f, "How often the host broadcasts sync data (seconds)"); ReadyTimeoutSec = val.Bind("Sync", "ReadyTimeoutSec", 30f, "Maximum time to wait for all clients to be ready (seconds)"); MaxSeekThresholdMs = val.Bind("Sync", "MaxSeekThresholdMs", 500f, "Drift above this threshold causes a hard seek instead of speed adjustment"); LangData = new Lang(); if (Language.Value.ToLower() == "ru") { LangData.InitRU(); } else { LangData.InitEN(); } } } public class Lang { public ConfigEntry LibLoading; public ConfigEntry TVTooltip; public ConfigEntry LibReady; public ConfigEntry VideoLoading; public ConfigEntry HelpText; public ConfigEntry InvalidUrl; public ConfigEntry PleaseWait; public ConfigEntry VideoReady; public ConfigEntry VolumeChanged; public ConfigEntry InvalidVolume; public ConfigEntry LinkInvalid; public ConfigEntry TimeChanged; public ConfigEntry MemoryToggle; public ConfigEntry WaitingForPlayers; public ConfigEntry AllPlayersReady; public ConfigEntry SyncStarting; public ConfigEntry PlayerTimedOut; public ConfigEntry HostOnly; public ConfigEntry Paused; public ConfigEntry Resumed; public ConfigEntry Stopped; public void InitEN() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown ConfigFile val = new ConfigFile("TVSync\\lang\\television_en.cfg", true); LibLoading = val.Bind("General", "Main_1", "Please wait, downloading libraries...", (ConfigDescription)null); TVTooltip = val.Bind("General", "Main_2", "Toggle TV : [E]\n@2 - @3\n@1 volume\nVol Up [PG UP]\nVol Down [PG DN]", (ConfigDescription)null); LibReady = val.Bind("General", "Main_3", "Libraries loaded.", (ConfigDescription)null); VideoLoading = val.Bind("General", "Main_4", "Video is loading!", (ConfigDescription)null); HelpText = val.Bind("General", "Main_5", "/tplay [LINK] - Play video\n/ttime [TIME] - Seek to time\n/treset - Restart\n/tpause - Pause\n/tresume - Resume\n/tstop - Stop\n/tvolume [0-100] - Volume\n/tposition [BOOL] - Remember position", (ConfigDescription)null); InvalidUrl = val.Bind("General", "Main_6", "Invalid URL!", (ConfigDescription)null); PleaseWait = val.Bind("General", "Main_7", "Please wait...", (ConfigDescription)null); VideoReady = val.Bind("General", "Main_8", "Video loaded.", (ConfigDescription)null); VolumeChanged = val.Bind("General", "Main_9", "@1 set volume to @2", (ConfigDescription)null); InvalidVolume = val.Bind("General", "Main_10", "Invalid Volume!", (ConfigDescription)null); LinkInvalid = val.Bind("General", "Main_11", "Link is invalid!", (ConfigDescription)null); TimeChanged = val.Bind("General", "Main_12", "Time: @1", (ConfigDescription)null); MemoryToggle = val.Bind("General", "Main_13", "Memory: @1", (ConfigDescription)null); WaitingForPlayers = val.Bind("General", "Main_14", "Waiting for all players to be ready...", (ConfigDescription)null); AllPlayersReady = val.Bind("General", "Main_15", "All players ready! Starting playback.", (ConfigDescription)null); SyncStarting = val.Bind("General", "Main_16", "Synchronized playback starting...", (ConfigDescription)null); PlayerTimedOut = val.Bind("General", "Main_17", "Timed out waiting for players. Starting anyway.", (ConfigDescription)null); HostOnly = val.Bind("General", "Main_18", "Only the host can use this command.", (ConfigDescription)null); Paused = val.Bind("General", "Main_19", "Playback paused.", (ConfigDescription)null); Resumed = val.Bind("General", "Main_20", "Playback resumed.", (ConfigDescription)null); Stopped = val.Bind("General", "Main_21", "Playback stopped.", (ConfigDescription)null); } public void InitRU() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown ConfigFile val = new ConfigFile("TVSync\\lang\\television_ru.cfg", true); LibLoading = val.Bind("General", "Main_1", "Пожалуйста, подождите, загружаются библиотеки...", (ConfigDescription)null); TVTooltip = val.Bind("General", "Main_2", "Вкл/Выкл ТВ : [E]\n@2 - @3\n@1 громкость\nГромче [PG UP]\nТише [PG DN]", (ConfigDescription)null); LibReady = val.Bind("General", "Main_3", "Библиотеки загружены.", (ConfigDescription)null); VideoLoading = val.Bind("General", "Main_4", "Видео загружается!", (ConfigDescription)null); HelpText = val.Bind("General", "Main_5", "/tplay [ССЫЛКА] - Проиграть\n/ttime [ВРЕМЯ] - Перемотать\n/treset - Сброс\n/tpause - Пауза\n/tresume - Продолжить\n/tstop - Остановить\n/tvolume [0-100] - Громкость\n/tposition [BOOL] - Запомнить позицию", (ConfigDescription)null); InvalidUrl = val.Bind("General", "Main_6", "Неверный URL!", (ConfigDescription)null); PleaseWait = val.Bind("General", "Main_7", "Пожалуйста подождите...", (ConfigDescription)null); VideoReady = val.Bind("General", "Main_8", "Видео готово.", (ConfigDescription)null); VolumeChanged = val.Bind("General", "Main_9", "@1 изменил громкость @2", (ConfigDescription)null); InvalidVolume = val.Bind("General", "Main_10", "Неверная громкость!", (ConfigDescription)null); LinkInvalid = val.Bind("General", "Main_11", "Ссылка недействительная!", (ConfigDescription)null); TimeChanged = val.Bind("General", "Main_12", "Позиция: @1", (ConfigDescription)null); MemoryToggle = val.Bind("General", "Main_13", "Запоминание: @1", (ConfigDescription)null); WaitingForPlayers = val.Bind("General", "Main_14", "Ожидание готовности всех игроков...", (ConfigDescription)null); AllPlayersReady = val.Bind("General", "Main_15", "Все игроки готовы! Начинаем воспроизведение.", (ConfigDescription)null); SyncStarting = val.Bind("General", "Main_16", "Синхронное воспроизведение начинается...", (ConfigDescription)null); PlayerTimedOut = val.Bind("General", "Main_17", "Время ожидания игроков истекло. Начинаем.", (ConfigDescription)null); HostOnly = val.Bind("General", "Main_18", "Только хост может использовать эту команду.", (ConfigDescription)null); Paused = val.Bind("General", "Main_19", "Воспроизведение приостановлено.", (ConfigDescription)null); Resumed = val.Bind("General", "Main_20", "Воспроизведение продолжено.", (ConfigDescription)null); Stopped = val.Bind("General", "Main_21", "Воспроизведение остановлено.", (ConfigDescription)null); } } public static class PluginInfo { public const string PLUGIN_GUID = "TVSync.SynchronizedTV"; public const string PLUGIN_NAME = "TVSync"; public const string PLUGIN_VERSION = "2.0.0"; } [BepInPlugin("TVSync.SynchronizedTV", "TVSync", "2.0.0")] public class Plugin : BaseUnityPlugin { private Harmony _harmony; public static readonly string DataDir = Path.Combine("TVSync"); public static readonly string LangDir = Path.Combine(DataDir, "lang"); public static readonly string OtherDir = Path.Combine(DataDir, "other"); public static readonly string YtDlpPath = Path.Combine(OtherDir, "yt-dlp.exe"); public static readonly string CachePath = Path.Combine(DataDir, "cache"); public static readonly string VersionPath = Path.Combine(OtherDir, "yt-dlp.version"); public static readonly string VideoPath = Path.Combine(OtherDir, "test.mp4"); public static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static TVSyncConfig SyncConfig { get; private set; } public static bool YtDlpReady { get; private set; } private void Awake() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; SyncConfig = new TVSyncConfig(); SyncConfig.Init(); EnsureDirectories(); CleanTempFiles(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"TVSync v2.0.0 loaded!"); Thread thread = new Thread(UpdateYtDlp); thread.IsBackground = true; thread.Start(); _harmony = new Harmony("TVSync.SynchronizedTV"); _harmony.PatchAll(typeof(TVPatches)); _harmony.PatchAll(typeof(HUDPatches)); _harmony.PatchAll(typeof(NetworkPatches)); } private void EnsureDirectories() { if (!Directory.Exists(LangDir)) { Directory.CreateDirectory(LangDir); } if (!Directory.Exists(OtherDir)) { Directory.CreateDirectory(OtherDir); } } private void CleanTempFiles() { TryDeleteFile(VideoPath); TryDeleteFile(VideoPath + ".part"); if (!File.Exists(CachePath)) { File.WriteAllText(CachePath, ""); } } private static void TryDeleteFile(string path) { try { if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { Log.LogWarning((object)("Could not delete " + path + ": " + ex.Message)); } } private void UpdateYtDlp() { try { string latestYtDlpTag = GetLatestYtDlpTag(); if (string.IsNullOrEmpty(latestYtDlpTag)) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"TVSync: Could not determine latest yt-dlp version. Skipping update."); YtDlpReady = File.Exists(YtDlpPath); return; } bool flag = true; if (File.Exists(YtDlpPath) && File.Exists(VersionPath) && File.ReadAllText(VersionPath).Trim() == latestYtDlpTag) { flag = false; } if (flag) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("TVSync: Downloading yt-dlp version " + latestYtDlpTag + "...")); DownloadFileSync(new Uri("https://github.com/yt-dlp/yt-dlp/releases/download/" + latestYtDlpTag + "/yt-dlp.exe"), YtDlpPath); if (File.Exists(YtDlpPath)) { File.WriteAllText(VersionPath, latestYtDlpTag); } } YtDlpReady = File.Exists(YtDlpPath); ((BaseUnityPlugin)this).Logger.LogInfo((object)"TVSync: yt-dlp ready."); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("TVSync: yt-dlp update failed: " + ex.Message)); YtDlpReady = File.Exists(YtDlpPath); } } private string GetLatestYtDlpTag() { try { HttpWebRequest obj = (HttpWebRequest)WebRequest.Create("https://github.com/yt-dlp/yt-dlp/releases/latest"); obj.Method = "HEAD"; obj.AllowAutoRedirect = false; obj.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"; obj.Timeout = 10000; using WebResponse webResponse = obj.GetResponse(); string text = webResponse.Headers["Location"]; if (!string.IsNullOrEmpty(text)) { return text.Substring(text.LastIndexOf('/') + 1); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("TVSync: Failed to fetch latest yt-dlp tag: " + ex.Message)); } return null; } public static void DownloadFileSync(Uri uri, string filename) { try { TryDeleteFile(filename); using WebClient webClient = new WebClient(); webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); webClient.DownloadFile(uri, filename); } catch (Exception ex) { Log.LogError((object)$"TVSync: Download failed for {uri}: {ex.Message}"); throw; } } } public class TVController : MonoBehaviour { private bool _isDownloading; private bool _isWaitingForReady; private bool _isPlaying; private bool _isPaused; private double _scheduledStartNetworkTime; private string _currentUrl = ""; private double _hostPlaybackTime; private float _lastSyncTime; private float _syncInterval; private float _driftThresholdSec; private float _maxSeekThresholdSec; private float _readyTimeoutSec; public static TVController Instance { get; private set; } public TVScript TV { get; private set; } public bool PositionMemory { get; set; } public double SavedPosition { get; set; } public float Volume { get { if (!((Object)(object)TV != (Object)null)) { return 0.5f; } return TV.tvSFX.volume; } set { if ((Object)(object)TV != (Object)null) { TV.tvSFX.volume = Mathf.Clamp01(value); } SaveVolume(value); } } public double CurrentTime { get { if (!((Object)(object)TV != (Object)null) || !((Object)(object)TV.video != (Object)null)) { return 0.0; } return TV.video.time; } } public double TotalTime { get { if (!((Object)(object)TV != (Object)null) || !((Object)(object)TV.video != (Object)null)) { return 0.0; } return TV.video.length; } } public bool IsPlaying => _isPlaying; public bool IsPaused => _isPaused; private void Awake() { Instance = this; } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } } public void Init(TVScript tvScript) { TV = tvScript; TVSyncConfig syncConfig = Plugin.SyncConfig; _syncInterval = syncConfig.SyncIntervalSec.Value; _driftThresholdSec = syncConfig.DriftThresholdMs.Value / 1000f; _maxSeekThresholdSec = syncConfig.MaxSeekThresholdMs.Value / 1000f; _readyTimeoutSec = syncConfig.ReadyTimeoutSec.Value; LoadVolume(); } private void Update() { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null)) { if (NetworkSync.IsHost && _isPlaying && !_isPaused && Time.time - _lastSyncTime >= _syncInterval) { _lastSyncTime = Time.time; BroadcastSyncState(); } if (_isWaitingForReady && _scheduledStartNetworkTime > 0.0 && GetNetworkTime() >= _scheduledStartNetworkTime) { _isWaitingForReady = false; BeginPlaybackNow(); } } } public void HostPlayVideo(string url, HUDManager hud, string playerName) { if (_isDownloading) { ShowChat(Plugin.SyncConfig.LangData.VideoLoading.Value); return; } _currentUrl = url; NetworkSync.SendPlayVideo(url); ShowChat(Plugin.SyncConfig.LangData.PleaseWait.Value); } public void HostPause() { if (NetworkSync.IsHost && _isPlaying && !_isPaused) { NetworkSync.SendPause(TV.video.time); } } public void HostResume() { if (NetworkSync.IsHost && _isPaused) { double time = TV.video.time; double networkTime = GetNetworkTime() + 0.5; NetworkSync.SendResume(time, networkTime); } } public void HostSeek(double seekTime) { if (NetworkSync.IsHost) { NetworkSync.SendSeek(seekTime); } } public void HostStop() { if (NetworkSync.IsHost) { NetworkSync.SendStop(); } } public void HostRestart() { if (NetworkSync.IsHost) { NetworkSync.SendSeek(0.0); } } public void OnPlayVideoReceived(string url) { _currentUrl = url; _isPlaying = false; _isPaused = false; ((MonoBehaviour)this).StartCoroutine(DownloadAndPrepareCoroutine(url)); } public void OnSyncStateReceived(PlaybackState state) { if (!NetworkSync.IsHost && _isPlaying && _isPaused == state.IsPaused) { ApplyDriftCorrection(state); } } public void OnPauseReceived(double pauseTime) { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null)) { _isPaused = true; TV.video.Pause(); TV.tvSFX.Pause(); TV.video.time = pauseTime; } } public void OnResumeReceived(double resumeTime, double scheduledNetworkTime) { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null)) { TV.video.time = resumeTime; _scheduledStartNetworkTime = scheduledNetworkTime; _isPaused = false; _isWaitingForReady = true; } } public void OnSeekReceived(double seekTime) { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null)) { TV.video.time = seekTime; _hostPlaybackTime = seekTime; } } public void OnStopReceived() { if (!((Object)(object)TV == (Object)null)) { _isPlaying = false; _isPaused = false; _isWaitingForReady = false; if (TV.tvOn) { TV.tvOn = false; SetTVScreenMaterial(on: false); TV.tvSFX.Stop(); TV.video.Stop(); } } } public void OnChatReceived(string message) { ShowChat(message); } public void SendCurrentStateToClient(ulong clientId) { if (NetworkSync.IsHost) { PlaybackState state = new PlaybackState { PlaybackTime = (((Object)(object)TV != (Object)null) ? TV.video.time : 0.0), NetworkTime = GetNetworkTime(), PlaybackSpeed = 1f, IsPaused = _isPaused, IsPlaying = _isPlaying, VideoUrl = _currentUrl }; NetworkSync.SendSyncStateToClient(clientId, state); } } private IEnumerator DownloadAndPrepareCoroutine(string url) { _isDownloading = true; try { if (!Directory.Exists(Plugin.OtherDir)) { Directory.CreateDirectory(Plugin.OtherDir); } } catch (Exception ex) { Plugin.Log.LogError((object)("TVSync: Could not create directory: " + ex.Message)); } if (TV.tvOn) { TV.video.Stop(); TV.tvSFX.Stop(); } string videoPath = Path.GetFullPath(Plugin.VideoPath); try { if (File.Exists(videoPath)) { File.Delete(videoPath); } string path = videoPath + ".part"; if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex2) { Plugin.Log.LogWarning((object)("TVSync: Could not clean old video: " + ex2.Message)); } ShowChat(Plugin.SyncConfig.LangData.PleaseWait.Value); float waitStart = Time.time; while (!Plugin.YtDlpReady && Time.time - waitStart < 60f) { yield return (object)new WaitForSeconds(1f); } if (!Plugin.YtDlpReady) { Plugin.Log.LogError((object)"TVSync: yt-dlp not ready after 60s"); _isDownloading = false; ShowChat(Plugin.SyncConfig.LangData.LinkInvalid.Value); yield break; } string ytDlpFullPath = Path.GetFullPath(Plugin.YtDlpPath); string workDir = Path.GetFullPath(Plugin.OtherDir); string text = Path.Combine(workDir, "cookies.txt"); string text2 = (File.Exists(text) ? ("--cookies \"" + text + "\" ") : ""); string arguments = text2 + "-f \"b[height<=360][ext=mp4]/b[ext=mp4]/b\" --force-ipv4 -N 4 \"" + url + "\" -o test.mp4"; Plugin.Log.LogInfo((object)("TVSync: Starting download: " + ytDlpFullPath + " " + arguments)); Plugin.Log.LogInfo((object)("TVSync: Working directory: " + workDir)); bool downloadComplete = false; bool downloadFailed = false; string errorMsg = ""; Thread downloadThread = new Thread((ThreadStart)delegate { try { Process process = new Process(); process.StartInfo.FileName = ytDlpFullPath; process.StartInfo.UseShellExecute = false; process.StartInfo.Arguments = arguments; process.StartInfo.WorkingDirectory = workDir; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.RedirectStandardOutput = true; process.Start(); string text4 = process.StandardOutput.ReadToEnd(); string text5 = process.StandardError.ReadToEnd(); process.WaitForExit(120000); if (!process.HasExited) { process.Kill(); downloadFailed = true; errorMsg = "yt-dlp timed out after 2 minutes"; } else { if (!string.IsNullOrEmpty(text5)) { Plugin.Log.LogWarning((object)("TVSync: yt-dlp stderr: " + text5)); } if (!string.IsNullOrEmpty(text4)) { Plugin.Log.LogInfo((object)("TVSync: yt-dlp stdout: " + text4)); } Plugin.Log.LogInfo((object)$"TVSync: yt-dlp exit code: {process.ExitCode}"); for (int i = 0; i < 30; i++) { if (File.Exists(videoPath)) { downloadComplete = true; return; } Thread.Sleep(500); } downloadFailed = true; errorMsg = "Video file not found at: " + videoPath; } } catch (Exception ex3) { downloadFailed = true; errorMsg = ex3.ToString(); } }) { IsBackground = true }; downloadThread.Start(); while (downloadThread.IsAlive) { yield return (object)new WaitForSeconds(0.5f); } if (downloadFailed || !downloadComplete) { Plugin.Log.LogError((object)("TVSync: Download failed: " + errorMsg)); _isDownloading = false; ShowChat(Plugin.SyncConfig.LangData.LinkInvalid.Value); yield break; } string text3 = "file:///" + videoPath.Replace('\\', '/'); Plugin.Log.LogInfo((object)("TVSync: Loading video from: " + text3)); TV.video.source = (VideoSource)1; TV.video.url = text3; TV.video.controlledAudioTrackCount = 1; TV.video.audioOutputMode = (VideoAudioOutputMode)1; TV.video.SetTargetAudioSource((ushort)0, TV.tvSFX); TV.video.playbackSpeed = 1f; TV.video.Prepare(); float prepStart = Time.time; while (!TV.video.isPrepared && Time.time - prepStart < 30f) { yield return (object)new WaitForSeconds(0.1f); } _isDownloading = false; if (!TV.video.isPrepared) { Plugin.Log.LogError((object)"TVSync: VideoPlayer failed to prepare"); ShowChat(Plugin.SyncConfig.LangData.LinkInvalid.Value); yield break; } Plugin.Log.LogInfo((object)"TVSync: Video prepared. Reporting ready."); ShowChat(Plugin.SyncConfig.LangData.VideoReady.Value); NetworkSync.SendClientReady(); if (NetworkSync.IsHost) { ((MonoBehaviour)this).StartCoroutine(WaitForReadyAndStart()); } } private IEnumerator WaitForReadyAndStart() { int count = NetworkManager.Singleton.ConnectedClientsIds.Count; _isWaitingForReady = true; ShowChat(Plugin.SyncConfig.LangData.WaitingForPlayers.Value); NetworkSync.BeginWaitForReady(count, delegate { Plugin.Log.LogInfo((object)"TVSync: All clients ready!"); }); float waitStart = Time.time; while (NetworkSync.ReadyCount < NetworkSync.ExpectedCount) { if (Time.time - waitStart > _readyTimeoutSec) { Plugin.Log.LogWarning((object)$"TVSync: Timeout waiting for clients ({NetworkSync.ReadyCount}/{NetworkSync.ExpectedCount})"); ShowChat(Plugin.SyncConfig.LangData.PlayerTimedOut.Value); NetworkSync.ForceAllReady(); break; } yield return (object)new WaitForSeconds(0.25f); } ShowChat(Plugin.SyncConfig.LangData.AllPlayersReady.Value); NetworkSync.SendResume(0.0, _scheduledStartNetworkTime = GetNetworkTime() + 1.0); } private void BeginPlaybackNow() { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null)) { Plugin.Log.LogInfo((object)"TVSync: Beginning synchronized playback!"); TV.tvOn = true; SetTVScreenMaterial(on: true); TV.video.Play(); TV.tvSFX.Play(); TV.tvSFX.PlayOneShot(TV.switchTVOn); WalkieTalkie.TransmitOneShotAudio(TV.tvSFX, TV.switchTVOn, 1f); _isPlaying = true; _isPaused = false; _lastSyncTime = Time.time; } } private void BroadcastSyncState() { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null)) { NetworkSync.SendSyncState(new PlaybackState { PlaybackTime = TV.video.time, NetworkTime = GetNetworkTime(), PlaybackSpeed = TV.video.playbackSpeed, IsPaused = _isPaused, IsPlaying = _isPlaying, VideoUrl = _currentUrl }); } } private void ApplyDriftCorrection(PlaybackState hostState) { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.video == (Object)null) && TV.video.isPlaying) { double num = GetNetworkTime() - hostState.NetworkTime; double num2 = hostState.PlaybackTime + num * (double)hostState.PlaybackSpeed; double time = TV.video.time; double num3 = num2 - time; double num4 = Math.Abs(num3); if (num4 < (double)_driftThresholdSec) { TV.video.playbackSpeed = 1f; } else if (num4 > (double)_maxSeekThresholdSec) { Plugin.Log.LogInfo((object)$"TVSync: Large drift {num3:F3}s — seeking to {num2:F2}"); TV.video.time = num2; TV.video.playbackSpeed = 1f; } else { float playbackSpeed = ((num3 > 0.0) ? 1.03f : 0.97f); TV.video.playbackSpeed = playbackSpeed; } } } public void HandleLateJoin() { if (!NetworkSync.IsHost) { Plugin.Log.LogInfo((object)"TVSync: Requesting current state (late join)..."); NetworkSync.SendRequestState(); } } public void HandleFullStateSync(PlaybackState state) { if (!string.IsNullOrEmpty(state.VideoUrl) && state.IsPlaying) { _currentUrl = state.VideoUrl; ((MonoBehaviour)this).StartCoroutine(LateJoinCoroutine(state)); } } private IEnumerator LateJoinCoroutine(PlaybackState state) { yield return DownloadAndPrepareCoroutine(state.VideoUrl); if (TV.video.isPrepared) { double num = GetNetworkTime() - state.NetworkTime; double val = state.PlaybackTime + num * (double)state.PlaybackSpeed; TV.video.time = Math.Max(0.0, val); if (state.IsPaused) { TV.video.time = state.PlaybackTime; _isPaused = true; } else { BeginPlaybackNow(); } } } public void SetTVScreenMaterial(bool on) { if (!((Object)(object)TV == (Object)null) && !((Object)(object)TV.tvMesh == (Object)null)) { Material[] sharedMaterials = ((Renderer)TV.tvMesh).sharedMaterials; if (sharedMaterials.Length > 1) { sharedMaterials[1] = (on ? TV.tvOnMaterial : TV.tvOffMaterial); ((Renderer)TV.tvMesh).sharedMaterials = sharedMaterials; } if ((Object)(object)TV.tvLight != (Object)null) { ((Behaviour)TV.tvLight).enabled = on; } } } public void HandleTVToggle(bool on) { if (on) { if (PositionMemory) { TV.video.time = SavedPosition; } SetTVScreenMaterial(on: true); TV.video.Play(); TV.tvSFX.Play(); TV.tvSFX.PlayOneShot(TV.switchTVOn); WalkieTalkie.TransmitOneShotAudio(TV.tvSFX, TV.switchTVOn, 1f); } else { if (PositionMemory) { SavedPosition = TV.video.time; } SetTVScreenMaterial(on: false); TV.tvSFX.Stop(); TV.video.Stop(); TV.tvSFX.PlayOneShot(TV.switchTVOff); WalkieTalkie.TransmitOneShotAudio(TV.tvSFX, TV.switchTVOff, 1f); } } private void LoadVolume() { try { if (File.Exists(Plugin.CachePath)) { string text = File.ReadAllText(Plugin.CachePath).Trim(); if (!string.IsNullOrEmpty(text) && float.TryParse(text, out var result)) { if ((Object)(object)TV != (Object)null) { TV.tvSFX.volume = Mathf.Clamp01(result); } } else if ((Object)(object)TV != (Object)null) { TV.tvSFX.volume = 0.5f; } } else if ((Object)(object)TV != (Object)null) { TV.tvSFX.volume = 0.5f; } } catch { if ((Object)(object)TV != (Object)null) { TV.tvSFX.volume = 0.5f; } } } private void SaveVolume(float vol) { try { File.WriteAllText(Plugin.CachePath, vol.ToString("F2")); } catch (Exception ex) { Plugin.Log.LogWarning((object)("TVSync: Could not save volume: " + ex.Message)); } } public void ShowChat(string message) { HUDManager instance = HUDManager.Instance; if (!((Object)(object)instance == (Object)null)) { ChatHelper.ShowMessage(instance, message, "TVSync"); } } private static double GetNetworkTime() { //IL_001e: 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) if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsConnectedClient) { NetworkTime serverTime = NetworkManager.Singleton.ServerTime; return ((NetworkTime)(ref serverTime)).Time; } return Time.timeAsDouble; } } } namespace TVSync.Patches { public static class TVPatches { [HarmonyPatch(typeof(TVScript), "OnEnable")] [HarmonyPrefix] public static bool OnEnable_Prefix(TVScript __instance) { TVController tVController = ((Component)__instance).GetComponent(); if ((Object)(object)tVController == (Object)null) { tVController = ((Component)__instance).gameObject.AddComponent(); } tVController.Init(__instance); __instance.video.clip = null; __instance.tvSFX.clip = null; __instance.video.Stop(); __instance.tvSFX.Stop(); return false; } [HarmonyPatch(typeof(TVScript), "TurnTVOnOff")] [HarmonyPrefix] public static bool TurnTVOnOff_Prefix(TVScript __instance, bool on) { __instance.tvOn = on; TVController instance = TVController.Instance; if ((Object)(object)instance != (Object)null) { instance.HandleTVToggle(on); } return false; } [HarmonyPatch(typeof(TVScript), "TVFinishedClip")] [HarmonyPrefix] public static bool TVFinishedClip_Prefix() { return false; } [HarmonyPatch(typeof(TVScript), "Update")] [HarmonyPrefix] public static bool Update_Prefix() { return false; } } public static class HUDPatches { private static readonly Regex UrlRegex = new Regex("^https?:\\/\\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&\\/=]*)$", RegexOptions.Compiled); [HarmonyPatch(typeof(PlayerControllerB), "SetHoverTipAndCurrentInteractTrigger")] [HarmonyPostfix] public static void SetHoverTipAndCurrentInteractTrigger_Postfix(PlayerControllerB __instance) { InteractTrigger hoveringOverTrigger = __instance.hoveringOverTrigger; if ((Object)(object)hoveringOverTrigger == (Object)null || !hoveringOverTrigger.interactable || !((Object)(object)((Component)hoveringOverTrigger).transform.parent != (Object)null) || (!((Object)((Component)hoveringOverTrigger).transform.parent).name.Contains("Television") && !hoveringOverTrigger.hoverTip.Contains("Switch TV"))) { return; } TVController instance = TVController.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance.TV == (Object)null) { return; } if (!Plugin.YtDlpReady) { hoveringOverTrigger.hoverTip = Plugin.SyncConfig.LangData.LibLoading.Value; return; } double currentTime = instance.CurrentTime; double totalTime = instance.TotalTime; string newValue = FormatTime(currentTime); string newValue2 = FormatTime(totalTime); float volume = instance.Volume; hoveringOverTrigger.hoverTip = Plugin.SyncConfig.LangData.TVTooltip.Value.Replace("@1", $"{Mathf.RoundToInt(volume * 100f)}%").Replace("@2", newValue).Replace("@3", newValue2); if (((ButtonControl)Keyboard.current.pageDownKey).wasPressedThisFrame && volume > 0f) { instance.Volume = Mathf.Max(0f, volume - 0.1f); } if (((ButtonControl)Keyboard.current.pageUpKey).wasPressedThisFrame && volume < 1f) { instance.Volume = Mathf.Min(1f, volume + 0.1f); } } [HarmonyPatch(typeof(HUDManager), "Start")] [HarmonyPostfix] public static void HUDStart_Postfix(HUDManager __instance) { __instance.chatTextField.characterLimit = 200; } [HarmonyPatch(typeof(HUDManager), "AddPlayerChatMessageServerRpc")] [HarmonyPostfix] public static void AddPlayerChatMessageServerRpc_Postfix(HUDManager __instance, string chatMessage, int playerId) { if (chatMessage.Length > 50) { typeof(HUDManager).GetMethod("AddPlayerChatMessageClientRpc", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(__instance, new object[2] { chatMessage, playerId }); } } [HarmonyPatch(typeof(HUDManager), "AddChatMessage")] [HarmonyPrefix] public static bool AddChatMessage_Prefix(HUDManager __instance, string chatMessage, string nameOfUserWhoTyped) { if (__instance.lastChatMessage != chatMessage) { __instance.lastChatMessage = chatMessage; __instance.PingHUDElement(__instance.Chat, 4f, 1f, 0.2f); if (__instance.ChatMessageHistory.Count >= 4) { __instance.ChatMessageHistory.RemoveAt(0); } StringBuilder stringBuilder = new StringBuilder(chatMessage); for (int i = 0; i < Math.Min(4, StartOfRound.Instance.allPlayerScripts.Length); i++) { stringBuilder.Replace($"[playerNum{i}]", StartOfRound.Instance.allPlayerScripts[i].playerUsername); } chatMessage = stringBuilder.ToString(); string item = (string.IsNullOrEmpty(nameOfUserWhoTyped) ? ("" + chatMessage + "") : ("" + nameOfUserWhoTyped + ": '" + chatMessage + "'")); __instance.ChatMessageHistory.Add(item); StringBuilder stringBuilder2 = new StringBuilder(); for (int j = 0; j < __instance.ChatMessageHistory.Count; j++) { stringBuilder2.Append("\n"); stringBuilder2.Append(__instance.ChatMessageHistory[j]); } ((TMP_Text)__instance.chatText).text = stringBuilder2.ToString(); ProcessCommand(__instance, chatMessage, nameOfUserWhoTyped); } return false; } [HarmonyPatch(typeof(HUDManager), "SubmitChat_performed")] [HarmonyPrefix] public static bool SubmitChat_performed_Prefix(HUDManager __instance, ref CallbackContext context) { //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) if (!((CallbackContext)(ref context)).performed) { return false; } __instance.localPlayer = GameNetworkManager.Instance.localPlayerController; if ((Object)(object)__instance.localPlayer == (Object)null) { return false; } if (!__instance.localPlayer.isTypingChat) { return false; } if ((!((NetworkBehaviour)__instance.localPlayer).IsOwner || (((NetworkBehaviour)__instance).IsServer && !__instance.localPlayer.isHostPlayerObject)) && !__instance.localPlayer.isTestingPlayer) { return false; } if (__instance.localPlayer.isPlayerDead) { return false; } if (!string.IsNullOrEmpty(__instance.chatTextField.text) && __instance.chatTextField.text.Length < 200) { __instance.AddTextToChatOnServer(__instance.chatTextField.text, (int)__instance.localPlayer.playerClientId); } for (int i = 0; i < StartOfRound.Instance.allPlayerScripts.Length; i++) { if (StartOfRound.Instance.allPlayerScripts[i].isPlayerControlled && Vector3.Distance(((Component)GameNetworkManager.Instance.localPlayerController).transform.position, ((Component)StartOfRound.Instance.allPlayerScripts[i]).transform.position) > 24.4f && (!GameNetworkManager.Instance.localPlayerController.holdingWalkieTalkie || !StartOfRound.Instance.allPlayerScripts[i].holdingWalkieTalkie)) { __instance.playerCouldRecieveTextChatAnimator.SetTrigger("ping"); break; } } __instance.localPlayer.isTypingChat = false; __instance.chatTextField.text = ""; EventSystem.current.SetSelectedGameObject((GameObject)null); __instance.PingHUDElement(__instance.Chat, 2f, 1f, 0.2f); ((Behaviour)__instance.typingIndicator).enabled = false; return false; } [HarmonyPatch(typeof(HUDManager), "Update")] [HarmonyPrefix] public static void HUDUpdate_Prefix(HUDManager __instance) { } private static void ProcessCommand(HUDManager hud, string chatMessage, string senderName) { if (string.IsNullOrEmpty(chatMessage)) { return; } string[] array = chatMessage.Split(' '); string text = array[0].Replace("/", "").ToLower(); Lang langData = Plugin.SyncConfig.LangData; TVController instance = TVController.Instance; if (text == null) { return; } switch (text.Length) { case 5: switch (text[1]) { case 'h': if (text == "thelp") { ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.HelpText.Value, "TVSync"); } break; case 'p': { if (!(text == "tplay") || !NetworkSync.IsHost) { break; } if (array.Length < 2) { ChatHelper.ShowMessage(hud, langData.InvalidUrl.Value, "TVSync"); break; } if (!UrlRegex.IsMatch(array[1])) { ChatHelper.ShowMessage(hud, langData.InvalidUrl.Value, "TVSync"); break; } string text2 = (array[1].Contains("://") ? array[1].Split(new string[1] { "://" }, StringSplitOptions.None)[1] : array[1]); if (!text2.Contains("youtube.com") && !text2.Contains("youtu.be")) { ChatHelper.ShowMessage(hud, langData.InvalidUrl.Value, "TVSync"); } else if (array[1].Contains("list")) { ChatHelper.ShowMessage(hud, langData.InvalidUrl.Value, "TVSync"); } else if ((Object)(object)instance != (Object)null) { instance.HostPlayVideo(array[1], hud, senderName); } break; } case 't': if (text == "ttime" && NetworkSync.IsHost && array.Length >= 2 && !((Object)(object)instance == (Object)null)) { double num = ParseTimeString(array[1]); if (num >= 0.0) { instance.HostSeek(num); ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.TimeChanged.Value.Replace("@1", FormatTime(num)), "TVSync"); } } break; case 's': if (text == "tstop" && NetworkSync.IsHost && !((Object)(object)instance == (Object)null)) { instance.HostStop(); ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.Stopped.Value, "TVSync"); } break; } break; case 6: switch (text[1]) { case 'r': if (text == "treset" && NetworkSync.IsHost && !((Object)(object)instance == (Object)null)) { instance.HostRestart(); ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.TimeChanged.Value.Replace("@1", "00:00:00"), "TVSync"); } break; case 'p': if (text == "tpause" && NetworkSync.IsHost && !((Object)(object)instance == (Object)null)) { instance.HostPause(); ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.Paused.Value, "TVSync"); } break; } break; case 7: switch (text[1]) { case 'r': if (text == "tresume" && NetworkSync.IsHost && !((Object)(object)instance == (Object)null)) { instance.HostResume(); ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.Resumed.Value, "TVSync"); } break; case 'v': { if (!(text == "tvolume") || array.Length < 2 || !(senderName == GameNetworkManager.Instance.localPlayerController.playerUsername)) { break; } if (int.TryParse(array[1], out var result)) { float volume = Mathf.Clamp01((float)result / 100f); if ((Object)(object)instance != (Object)null) { instance.Volume = volume; ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.VolumeChanged.Value.Replace("@1", senderName ?? "").Replace("@2", array[1] + "%"), "TVSync"); } } else { ChatHelper.ShowMessage(hud, langData.InvalidVolume.Value, "TVSync"); } break; } } break; case 9: if (text == "tposition" && array.Length >= 2 && !((Object)(object)instance == (Object)null)) { if (array[1].ToLower() == "true") { instance.PositionMemory = true; } else if (array[1].ToLower() == "false") { instance.PositionMemory = false; } string newValue = (instance.PositionMemory ? "enabled" : "disabled"); ChatHelper.ResetDedup(); ChatHelper.ShowMessage(hud, langData.MemoryToggle.Value.Replace("@1", newValue), "TVSync"); } break; case 8: break; } } private static double ParseTimeString(string timeStr) { string[] array = timeStr.Split(':'); try { return array.Length switch { 1 => Convert.ToDouble(array[0]), 2 => Convert.ToInt32(array[0]) * 60 + Convert.ToInt32(array[1]), 3 => Convert.ToInt32(array[0]) * 3600 + Convert.ToInt32(array[1]) * 60 + Convert.ToInt32(array[2]), _ => -1.0, }; } catch { return -1.0; } } public static string FormatTime(double seconds) { int num = (int)seconds; int num2 = num / 3600; int num3 = num % 3600 / 60; int num4 = num % 60; return $"{num2:00}:{num3:00}:{num4:00}"; } } public static class NetworkPatches { [HarmonyPatch(typeof(GameNetworkManager), "StartDisconnect")] [HarmonyPostfix] public static void StartDisconnect_Postfix() { NetworkSync.Unregister(); } [HarmonyPatch(typeof(StartOfRound), "Start")] [HarmonyPostfix] public static void StartOfRound_Start_Postfix() { NetworkSync.Register(); if (!NetworkSync.IsHost && (Object)(object)TVController.Instance != (Object)null) { TVController.Instance.HandleLateJoin(); } } [HarmonyPatch(typeof(StartOfRound), "OnPlayerConnectedClientRpc")] [HarmonyPostfix] public static void OnPlayerConnected_Postfix(ulong clientId) { if (NetworkSync.IsHost) { Plugin.Log.LogInfo((object)$"TVSync: Client {clientId} connected. Will send state on request."); } } } } namespace TVSync.Networking { public static class NetworkSync { [CompilerGenerated] private static class <>O { public static HandleNamedMessageDelegate <0>__OnReceivePlayVideo; public static HandleNamedMessageDelegate <1>__OnReceiveSyncState; public static HandleNamedMessageDelegate <2>__OnReceiveClientReady; public static HandleNamedMessageDelegate <3>__OnReceivePause; public static HandleNamedMessageDelegate <4>__OnReceiveResume; public static HandleNamedMessageDelegate <5>__OnReceiveSeek; public static HandleNamedMessageDelegate <6>__OnReceiveStop; public static HandleNamedMessageDelegate <7>__OnReceiveChat; public static HandleNamedMessageDelegate <8>__OnReceiveRequestState; } private const string MSG_PLAY_VIDEO = "TVSync_PlayVideo"; private const string MSG_SYNC_STATE = "TVSync_SyncState"; private const string MSG_CLIENT_READY = "TVSync_ClientReady"; private const string MSG_PAUSE = "TVSync_Pause"; private const string MSG_RESUME = "TVSync_Resume"; private const string MSG_SEEK = "TVSync_Seek"; private const string MSG_STOP = "TVSync_Stop"; private const string MSG_CHAT = "TVSync_Chat"; private const string MSG_REQUEST_STATE = "TVSync_RequestState"; private static HashSet _readyClients = new HashSet(); private static int _expectedReadyCount = 0; private static Action _onAllReady; public static bool IsRegistered { get; private set; } public static bool IsHost { get { if ((Object)(object)NetworkManager.Singleton != (Object)null) { return NetworkManager.Singleton.IsHost; } return false; } } public static bool IsConnected { get { if ((Object)(object)NetworkManager.Singleton != (Object)null) { return NetworkManager.Singleton.IsConnectedClient; } return false; } } public static int ReadyCount => _readyClients.Count; public static int ExpectedCount => _expectedReadyCount; public static void Register() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Expected O, but got Unknown //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00f2: 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) //IL_00fd: Expected O, but got Unknown //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Expected O, but got Unknown //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown if (IsRegistered) { return; } NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null); if (val != null) { object obj = <>O.<0>__OnReceivePlayVideo; if (obj == null) { HandleNamedMessageDelegate val2 = OnReceivePlayVideo; <>O.<0>__OnReceivePlayVideo = val2; obj = (object)val2; } val.RegisterNamedMessageHandler("TVSync_PlayVideo", (HandleNamedMessageDelegate)obj); object obj2 = <>O.<1>__OnReceiveSyncState; if (obj2 == null) { HandleNamedMessageDelegate val3 = OnReceiveSyncState; <>O.<1>__OnReceiveSyncState = val3; obj2 = (object)val3; } val.RegisterNamedMessageHandler("TVSync_SyncState", (HandleNamedMessageDelegate)obj2); object obj3 = <>O.<2>__OnReceiveClientReady; if (obj3 == null) { HandleNamedMessageDelegate val4 = OnReceiveClientReady; <>O.<2>__OnReceiveClientReady = val4; obj3 = (object)val4; } val.RegisterNamedMessageHandler("TVSync_ClientReady", (HandleNamedMessageDelegate)obj3); object obj4 = <>O.<3>__OnReceivePause; if (obj4 == null) { HandleNamedMessageDelegate val5 = OnReceivePause; <>O.<3>__OnReceivePause = val5; obj4 = (object)val5; } val.RegisterNamedMessageHandler("TVSync_Pause", (HandleNamedMessageDelegate)obj4); object obj5 = <>O.<4>__OnReceiveResume; if (obj5 == null) { HandleNamedMessageDelegate val6 = OnReceiveResume; <>O.<4>__OnReceiveResume = val6; obj5 = (object)val6; } val.RegisterNamedMessageHandler("TVSync_Resume", (HandleNamedMessageDelegate)obj5); object obj6 = <>O.<5>__OnReceiveSeek; if (obj6 == null) { HandleNamedMessageDelegate val7 = OnReceiveSeek; <>O.<5>__OnReceiveSeek = val7; obj6 = (object)val7; } val.RegisterNamedMessageHandler("TVSync_Seek", (HandleNamedMessageDelegate)obj6); object obj7 = <>O.<6>__OnReceiveStop; if (obj7 == null) { HandleNamedMessageDelegate val8 = OnReceiveStop; <>O.<6>__OnReceiveStop = val8; obj7 = (object)val8; } val.RegisterNamedMessageHandler("TVSync_Stop", (HandleNamedMessageDelegate)obj7); object obj8 = <>O.<7>__OnReceiveChat; if (obj8 == null) { HandleNamedMessageDelegate val9 = OnReceiveChat; <>O.<7>__OnReceiveChat = val9; obj8 = (object)val9; } val.RegisterNamedMessageHandler("TVSync_Chat", (HandleNamedMessageDelegate)obj8); object obj9 = <>O.<8>__OnReceiveRequestState; if (obj9 == null) { HandleNamedMessageDelegate val10 = OnReceiveRequestState; <>O.<8>__OnReceiveRequestState = val10; obj9 = (object)val10; } val.RegisterNamedMessageHandler("TVSync_RequestState", (HandleNamedMessageDelegate)obj9); IsRegistered = true; Plugin.Log.LogInfo((object)"TVSync: Network message handlers registered."); } } public static void Unregister() { if (IsRegistered) { NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null); if (val != null) { val.UnregisterNamedMessageHandler("TVSync_PlayVideo"); val.UnregisterNamedMessageHandler("TVSync_SyncState"); val.UnregisterNamedMessageHandler("TVSync_ClientReady"); val.UnregisterNamedMessageHandler("TVSync_Pause"); val.UnregisterNamedMessageHandler("TVSync_Resume"); val.UnregisterNamedMessageHandler("TVSync_Seek"); val.UnregisterNamedMessageHandler("TVSync_Stop"); val.UnregisterNamedMessageHandler("TVSync_Chat"); val.UnregisterNamedMessageHandler("TVSync_RequestState"); IsRegistered = false; _readyClients.Clear(); } } } public static void SendPlayVideo(string url) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { FastBufferWriter writer = BeginMessage(); WriteString(writer, url); SendToAllClients("TVSync_PlayVideo", writer); ((FastBufferWriter)(ref writer)).Dispose(); TVController.Instance?.OnPlayVideoReceived(url); } } public static void SendSyncState(PlaybackState state) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { FastBufferWriter writer = BeginMessage(); state.Serialize(writer); SendToAllClients("TVSync_SyncState", writer); ((FastBufferWriter)(ref writer)).Dispose(); } } public static void SendPause(double pauseTime) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) if (IsHost) { FastBufferWriter writer = BeginMessage(); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref pauseTime, default(ForPrimitives)); SendToAllClients("TVSync_Pause", writer); ((FastBufferWriter)(ref writer)).Dispose(); TVController.Instance?.OnPauseReceived(pauseTime); } } public static void SendResume(double resumeTime, double networkTime) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { FastBufferWriter writer = BeginMessage(); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref resumeTime, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref networkTime, default(ForPrimitives)); SendToAllClients("TVSync_Resume", writer); ((FastBufferWriter)(ref writer)).Dispose(); TVController.Instance?.OnResumeReceived(resumeTime, networkTime); } } public static void SendSeek(double seekTime) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: 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) if (IsHost) { FastBufferWriter writer = BeginMessage(); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref seekTime, default(ForPrimitives)); SendToAllClients("TVSync_Seek", writer); ((FastBufferWriter)(ref writer)).Dispose(); TVController.Instance?.OnSeekReceived(seekTime); } } public static void SendStop() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_0027: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { FastBufferWriter writer = BeginMessage(); byte b = 0; ((FastBufferWriter)(ref writer)).WriteValueSafe(ref b, default(ForPrimitives)); SendToAllClients("TVSync_Stop", writer); ((FastBufferWriter)(ref writer)).Dispose(); TVController.Instance?.OnStopReceived(); } } public static void SendChat(string message) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { FastBufferWriter writer = BeginMessage(); WriteString(writer, message); SendToAllClients("TVSync_Chat", writer); ((FastBufferWriter)(ref writer)).Dispose(); } } public static void SendClientReady() { //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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (IsHost) { OnClientReady(NetworkManager.Singleton.LocalClientId); return; } FastBufferWriter writer = BeginMessage(); ulong localClientId = NetworkManager.Singleton.LocalClientId; ((FastBufferWriter)(ref writer)).WriteValueSafe(ref localClientId, default(ForPrimitives)); SendToServer("TVSync_ClientReady", writer); ((FastBufferWriter)(ref writer)).Dispose(); } public static void SendRequestState() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: 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_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!IsHost) { FastBufferWriter writer = BeginMessage(); ulong localClientId = NetworkManager.Singleton.LocalClientId; ((FastBufferWriter)(ref writer)).WriteValueSafe(ref localClientId, default(ForPrimitives)); SendToServer("TVSync_RequestState", writer); ((FastBufferWriter)(ref writer)).Dispose(); } } public static void BeginWaitForReady(int expectedCount, Action onAllReady) { _readyClients.Clear(); _expectedReadyCount = expectedCount; _onAllReady = onAllReady; } private static void OnClientReady(ulong clientId) { _readyClients.Add(clientId); Plugin.Log.LogInfo((object)$"TVSync: Client {clientId} ready ({_readyClients.Count}/{_expectedReadyCount})"); if (_readyClients.Count >= _expectedReadyCount) { _onAllReady?.Invoke(); _onAllReady = null; } } public static void ForceAllReady() { _onAllReady?.Invoke(); _onAllReady = null; } private static void OnReceivePlayVideo(ulong senderClientId, FastBufferReader reader) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) string text = ReadString(reader); Plugin.Log.LogInfo((object)("TVSync: Received PlayVideo: " + text)); TVController.Instance?.OnPlayVideoReceived(text); } private static void OnReceiveSyncState(ulong senderClientId, FastBufferReader reader) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) PlaybackState state = PlaybackState.Deserialize(reader); TVController.Instance?.OnSyncStateReceived(state); } private static void OnReceiveClientReady(ulong senderClientId, FastBufferReader reader) { //IL_0006: 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) ulong clientId = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe(ref clientId, default(ForPrimitives)); if (IsHost) { OnClientReady(clientId); } } private static void OnReceivePause(ulong senderClientId, FastBufferReader reader) { //IL_0006: 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) double pauseTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref pauseTime, default(ForPrimitives)); TVController.Instance?.OnPauseReceived(pauseTime); } private static void OnReceiveResume(ulong senderClientId, FastBufferReader reader) { //IL_0006: 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_0018: 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) double resumeTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref resumeTime, default(ForPrimitives)); double scheduledNetworkTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref scheduledNetworkTime, default(ForPrimitives)); TVController.Instance?.OnResumeReceived(resumeTime, scheduledNetworkTime); } private static void OnReceiveSeek(ulong senderClientId, FastBufferReader reader) { //IL_0006: 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) double seekTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref seekTime, default(ForPrimitives)); TVController.Instance?.OnSeekReceived(seekTime); } private static void OnReceiveStop(ulong senderClientId, FastBufferReader reader) { //IL_0006: 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) byte b = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe(ref b, default(ForPrimitives)); TVController.Instance?.OnStopReceived(); } private static void OnReceiveChat(ulong senderClientId, FastBufferReader reader) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) string message = ReadString(reader); TVController.Instance?.OnChatReceived(message); } private static void OnReceiveRequestState(ulong senderClientId, FastBufferReader reader) { //IL_0006: 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) ulong clientId = default(ulong); ((FastBufferReader)(ref reader)).ReadValueSafe(ref clientId, default(ForPrimitives)); if (IsHost) { TVController.Instance?.SendCurrentStateToClient(clientId); } } private static FastBufferWriter BeginMessage() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return new FastBufferWriter(4096, (Allocator)2, -1); } private static void SendToAllClients(string msgName, FastBufferWriter writer) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null); if (val == null) { return; } foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds) { if (connectedClientsId != NetworkManager.Singleton.LocalClientId) { val.SendNamedMessage(msgName, connectedClientsId, writer, (NetworkDelivery)3); } } } private static void SendToServer(string msgName, FastBufferWriter writer) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null); if (val != null) { val.SendNamedMessage(msgName, 0uL, writer, (NetworkDelivery)3); } } public static void SendSyncStateToClient(ulong clientId, PlaybackState state) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: 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_002b: Unknown result type (might be due to invalid IL or missing references) NetworkManager singleton = NetworkManager.Singleton; CustomMessagingManager val = ((singleton != null) ? singleton.CustomMessagingManager : null); if (val != null) { FastBufferWriter val2 = BeginMessage(); state.Serialize(val2); val.SendNamedMessage("TVSync_SyncState", clientId, val2, (NetworkDelivery)3); ((FastBufferWriter)(ref val2)).Dispose(); } } private static void WriteString(FastBufferWriter writer, string value) { //IL_001f: 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) byte[] bytes = Encoding.UTF8.GetBytes(value ?? ""); int num = bytes.Length; ((FastBufferWriter)(ref writer)).WriteValueSafe(ref num, default(ForPrimitives)); if (bytes.Length != 0) { ((FastBufferWriter)(ref writer)).WriteBytesSafe(bytes, -1, 0); } } private static string ReadString(FastBufferReader reader) { //IL_0006: 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) int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref num, default(ForPrimitives)); if (num <= 0) { return ""; } byte[] bytes = new byte[num]; ((FastBufferReader)(ref reader)).ReadBytesSafe(ref bytes, num, 0); return Encoding.UTF8.GetString(bytes); } } public struct PlaybackState { public double PlaybackTime; public double NetworkTime; public float PlaybackSpeed; public bool IsPaused; public bool IsPlaying; public string VideoUrl; public void Serialize(FastBufferWriter writer) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: 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_0026: 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_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) ((FastBufferWriter)(ref writer)).WriteValueSafe(ref PlaybackTime, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref NetworkTime, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref PlaybackSpeed, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref IsPaused, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe(ref IsPlaying, default(ForPrimitives)); byte[] bytes = Encoding.UTF8.GetBytes(VideoUrl ?? ""); int num = bytes.Length; ((FastBufferWriter)(ref writer)).WriteValueSafe(ref num, default(ForPrimitives)); if (bytes.Length != 0) { ((FastBufferWriter)(ref writer)).WriteBytesSafe(bytes, -1, 0); } } public static PlaybackState Deserialize(FastBufferReader reader) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: 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_005e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) PlaybackState result = default(PlaybackState); ((FastBufferReader)(ref reader)).ReadValueSafe(ref result.PlaybackTime, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref result.NetworkTime, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref result.PlaybackSpeed, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref result.IsPaused, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe(ref result.IsPlaying, default(ForPrimitives)); int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref num, default(ForPrimitives)); if (num > 0) { byte[] bytes = new byte[num]; ((FastBufferReader)(ref reader)).ReadBytesSafe(ref bytes, num, 0); result.VideoUrl = Encoding.UTF8.GetString(bytes); } else { result.VideoUrl = ""; } return result; } } }