Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of TVSync v2.0.0
TVSync.dll
Decompiled 2 days agousing 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) ? ("<color=#7069ff>" + message + "</color>") : ("<color=#00BFFF>" + senderName + "</color>: <color=#FFFFFF>" + message + "</color>")); 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<string> Language { get; private set; } public ConfigEntry<float> DriftThresholdMs { get; private set; } public ConfigEntry<float> SyncIntervalSec { get; private set; } public ConfigEntry<float> ReadyTimeoutSec { get; private set; } public ConfigEntry<float> 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<string>("General", "Language", "en", "en = English, ru = Русский"); DriftThresholdMs = val.Bind<float>("Sync", "DriftThresholdMs", 150f, "Maximum drift in ms before correction is applied"); SyncIntervalSec = val.Bind<float>("Sync", "SyncIntervalSec", 3f, "How often the host broadcasts sync data (seconds)"); ReadyTimeoutSec = val.Bind<float>("Sync", "ReadyTimeoutSec", 30f, "Maximum time to wait for all clients to be ready (seconds)"); MaxSeekThresholdMs = val.Bind<float>("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<string> LibLoading; public ConfigEntry<string> TVTooltip; public ConfigEntry<string> LibReady; public ConfigEntry<string> VideoLoading; public ConfigEntry<string> HelpText; public ConfigEntry<string> InvalidUrl; public ConfigEntry<string> PleaseWait; public ConfigEntry<string> VideoReady; public ConfigEntry<string> VolumeChanged; public ConfigEntry<string> InvalidVolume; public ConfigEntry<string> LinkInvalid; public ConfigEntry<string> TimeChanged; public ConfigEntry<string> MemoryToggle; public ConfigEntry<string> WaitingForPlayers; public ConfigEntry<string> AllPlayersReady; public ConfigEntry<string> SyncStarting; public ConfigEntry<string> PlayerTimedOut; public ConfigEntry<string> HostOnly; public ConfigEntry<string> Paused; public ConfigEntry<string> Resumed; public ConfigEntry<string> 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<string>("General", "Main_1", "Please wait, downloading libraries...", (ConfigDescription)null); TVTooltip = val.Bind<string>("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<string>("General", "Main_3", "Libraries loaded.", (ConfigDescription)null); VideoLoading = val.Bind<string>("General", "Main_4", "Video is loading!", (ConfigDescription)null); HelpText = val.Bind<string>("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<string>("General", "Main_6", "Invalid URL!", (ConfigDescription)null); PleaseWait = val.Bind<string>("General", "Main_7", "Please wait...", (ConfigDescription)null); VideoReady = val.Bind<string>("General", "Main_8", "Video loaded.", (ConfigDescription)null); VolumeChanged = val.Bind<string>("General", "Main_9", "@1 set volume to @2", (ConfigDescription)null); InvalidVolume = val.Bind<string>("General", "Main_10", "Invalid Volume!", (ConfigDescription)null); LinkInvalid = val.Bind<string>("General", "Main_11", "Link is invalid!", (ConfigDescription)null); TimeChanged = val.Bind<string>("General", "Main_12", "Time: @1", (ConfigDescription)null); MemoryToggle = val.Bind<string>("General", "Main_13", "Memory: @1", (ConfigDescription)null); WaitingForPlayers = val.Bind<string>("General", "Main_14", "Waiting for all players to be ready...", (ConfigDescription)null); AllPlayersReady = val.Bind<string>("General", "Main_15", "All players ready! Starting playback.", (ConfigDescription)null); SyncStarting = val.Bind<string>("General", "Main_16", "Synchronized playback starting...", (ConfigDescription)null); PlayerTimedOut = val.Bind<string>("General", "Main_17", "Timed out waiting for players. Starting anyway.", (ConfigDescription)null); HostOnly = val.Bind<string>("General", "Main_18", "Only the host can use this command.", (ConfigDescription)null); Paused = val.Bind<string>("General", "Main_19", "Playback paused.", (ConfigDescription)null); Resumed = val.Bind<string>("General", "Main_20", "Playback resumed.", (ConfigDescription)null); Stopped = val.Bind<string>("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<string>("General", "Main_1", "Пожалуйста, подождите, загружаются библиотеки...", (ConfigDescription)null); TVTooltip = val.Bind<string>("General", "Main_2", "Вкл/Выкл ТВ : [E]\n@2 - @3\n@1 громкость\nГромче [PG UP]\nТише [PG DN]", (ConfigDescription)null); LibReady = val.Bind<string>("General", "Main_3", "Библиотеки загружены.", (ConfigDescription)null); VideoLoading = val.Bind<string>("General", "Main_4", "Видео загружается!", (ConfigDescription)null); HelpText = val.Bind<string>("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<string>("General", "Main_6", "Неверный URL!", (ConfigDescription)null); PleaseWait = val.Bind<string>("General", "Main_7", "Пожалуйста подождите...", (ConfigDescription)null); VideoReady = val.Bind<string>("General", "Main_8", "Видео готово.", (ConfigDescription)null); VolumeChanged = val.Bind<string>("General", "Main_9", "@1 изменил громкость @2", (ConfigDescription)null); InvalidVolume = val.Bind<string>("General", "Main_10", "Неверная громкость!", (ConfigDescription)null); LinkInvalid = val.Bind<string>("General", "Main_11", "Ссылка недействительная!", (ConfigDescription)null); TimeChanged = val.Bind<string>("General", "Main_12", "Позиция: @1", (ConfigDescription)null); MemoryToggle = val.Bind<string>("General", "Main_13", "Запоминание: @1", (ConfigDescription)null); WaitingForPlayers = val.Bind<string>("General", "Main_14", "Ожидание готовности всех игроков...", (ConfigDescription)null); AllPlayersReady = val.Bind<string>("General", "Main_15", "Все игроки готовы! Начинаем воспроизведение.", (ConfigDescription)null); SyncStarting = val.Bind<string>("General", "Main_16", "Синхронное воспроизведение начинается...", (ConfigDescription)null); PlayerTimedOut = val.Bind<string>("General", "Main_17", "Время ожидания игроков истекло. Начинаем.", (ConfigDescription)null); HostOnly = val.Bind<string>("General", "Main_18", "Только хост может использовать эту команду.", (ConfigDescription)null); Paused = val.Bind<string>("General", "Main_19", "Воспроизведение приостановлено.", (ConfigDescription)null); Resumed = val.Bind<string>("General", "Main_20", "Воспроизведение продолжено.", (ConfigDescription)null); Stopped = val.Bind<string>("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<TVController>(); if ((Object)(object)tVController == (Object)null) { tVController = ((Component)__instance).gameObject.AddComponent<TVController>(); } 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) ? ("<color=#7069ff>" + chatMessage + "</color>") : ("<color=#FF0000>" + nameOfUserWhoTyped + "</color>: <color=#FFFF00>'" + chatMessage + "'</color>")); __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<ulong> _readyClients = new HashSet<ulong>(); 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<double>(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<double>(ref resumeTime, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<double>(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<double>(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<byte>(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<ulong>(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<ulong>(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<ulong>(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<double>(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<double>(ref resumeTime, default(ForPrimitives)); double scheduledNetworkTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe<double>(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<double>(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<byte>(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<ulong>(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<int>(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<int>(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<double>(ref PlaybackTime, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<double>(ref NetworkTime, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<float>(ref PlaybackSpeed, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref IsPaused, default(ForPrimitives)); ((FastBufferWriter)(ref writer)).WriteValueSafe<bool>(ref IsPlaying, default(ForPrimitives)); byte[] bytes = Encoding.UTF8.GetBytes(VideoUrl ?? ""); int num = bytes.Length; ((FastBufferWriter)(ref writer)).WriteValueSafe<int>(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<double>(ref result.PlaybackTime, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref result.NetworkTime, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<float>(ref result.PlaybackSpeed, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref result.IsPaused, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref result.IsPlaying, default(ForPrimitives)); int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(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; } } }