using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Logging; using CymruTVSync.NetcodePatcher; using GameNetcodeStuff; using HarmonyLib; using Microsoft.CodeAnalysis; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.Video; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("CymruTVSync")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyDescription("Syncs the TV across all players using TVLoader.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CymruTVSync")] [assembly: AssemblyTitle("CymruTVSync")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] [module: NetcodePatchedAssembly] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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 CymruTVSync { internal class HostLockStatusOverlay : MonoBehaviour { private GUIStyle _labelStyle; private GUIStyle _boxStyle; private void OnGUI() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) if (ShouldShow()) { EnsureStyles(); string text = ((!TVSyncState.LockFeatureEnabled) ? "TV LOCK CTRL: OFF" : (TVSyncState.IsLocked ? "TV LOCK: ON" : "TV LOCK: OFF")); float num = (float)Screen.width - 170f - 16f; Rect val = default(Rect); ((Rect)(ref val))..ctor(num, 16f, 170f, 28f); Color color = GUI.color; GUI.Box(val, string.Empty, _boxStyle); GUI.Label(val, text, _labelStyle); GUI.color = color; } } private static bool ShouldShow() { if ((Object)(object)NetworkManager.Singleton == (Object)null) { return false; } return NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer; } private void EnsureStyles() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if (_boxStyle == null) { _boxStyle = new GUIStyle(GUI.skin.box); _boxStyle.normal.background = Texture2D.whiteTexture; } if (_labelStyle == null) { GUIStyle val = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)4, fontStyle = (FontStyle)1, fontSize = 12 }; val.normal.textColor = Color.white; _labelStyle = val; } GUI.color = (TVSyncState.IsLocked ? new Color(0.72f, 0.14f, 0.14f, 0.78f) : new Color(0.12f, 0.42f, 0.18f, 0.78f)); } } public class TVSyncNetworkHandler : MonoBehaviour { private const string RequestChangeMessageName = "CymruTVSync/RequestChange"; private const string RequestSyncMessageName = "CymruTVSync/RequestSync"; private const string ToggleLockMessageName = "CymruTVSync/ToggleLock"; private const string ToggleLockFeatureMessageName = "CymruTVSync/ToggleLockFeature"; private const string BroadcastStateMessageName = "CymruTVSync/BroadcastState"; private const string BroadcastLockStateMessageName = "CymruTVSync/BroadcastLockState"; private const string BroadcastLockFeatureMessageName = "CymruTVSync/BroadcastLockFeature"; private static bool _handlersRegistered; private static NetworkManager _registeredNetworkManager; private static bool _pendingSyncRequest; public static TVSyncNetworkHandler Instance { get; private set; } public static event Action OnTVStateReceived; public static event Action OnLockStateReceived; public static event Action OnLockFeatureStateReceived; public static void EnsureInstance() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown if (!((Object)(object)Instance != (Object)null)) { GameObject val = new GameObject("TVSyncNetworkHandlerLocal"); Object.DontDestroyOnLoad((Object)(object)val); Instance = val.AddComponent(); } } public static void EnsureMessagingReady(string source) { EnsureInstance(); Instance.TryEnsureMessagingReady(source); } private void Awake() { if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } Instance = this; Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject); } private static string LocalRoleTag() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { return "NO-NET"; } if (singleton.IsHost) { return "HOST"; } if (singleton.IsServer) { return "SERVER"; } if (singleton.IsClient) { return "CLIENT"; } return "OFFLINE"; } private static string LocalEndpointTag() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { return "role=NO-NET local=n/a"; } return $"role={LocalRoleTag()} local={singleton.LocalClientId}"; } private bool TryEnsureMessagingReady(string source) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Expected O, but got Unknown //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Expected O, but got Unknown NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync][NGO] Messaging not ready during " + source + " because NetworkManager.Singleton is null.")); return false; } if (_handlersRegistered && (Object)(object)_registeredNetworkManager == (Object)(object)singleton) { return true; } if (singleton.CustomMessagingManager == null) { TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync][NGO] Messaging not ready during " + source + " because CustomMessagingManager is null (" + LocalEndpointTag() + ").")); return false; } singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/RequestChange", new HandleNamedMessageDelegate(OnRequestChangeMessage)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/RequestSync", new HandleNamedMessageDelegate(OnRequestSyncMessage)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/ToggleLock", new HandleNamedMessageDelegate(OnToggleLockMessage)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/ToggleLockFeature", new HandleNamedMessageDelegate(OnToggleLockFeatureMessage)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/BroadcastState", new HandleNamedMessageDelegate(OnBroadcastStateMessage)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/BroadcastLockState", new HandleNamedMessageDelegate(OnBroadcastLockStateMessage)); singleton.CustomMessagingManager.RegisterNamedMessageHandler("CymruTVSync/BroadcastLockFeature", new HandleNamedMessageDelegate(OnBroadcastLockFeatureMessage)); _registeredNetworkManager = singleton; _handlersRegistered = true; if (_pendingSyncRequest && singleton.IsClient && !singleton.IsServer) { _pendingSyncRequest = false; TryRequestFullSyncFromServer("PendingSyncRequest"); } return true; } public bool TrySendChangeToServer(string source, int clipIndex, double seekTime, bool tvOn) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) if (!TryEnsureMessagingReady(source)) { return false; } string currentMediaId = BestestTV.GetCurrentMediaId(); NetworkManager singleton = NetworkManager.Singleton; if (singleton.IsServer) { if (TVSyncState.LockFeatureEnabled && TVSyncState.IsLocked && string.Equals(source, "TVScript.TurnTVOnOff", StringComparison.Ordinal) && !TVSyncPatches.ConsumeHostControlIntent()) { TVSyncPlugin.Log.LogDebug((object)"[CymruTVSync] Ignoring mirrored TurnTVOnOff while locked because it was not host-initiated."); return false; } HandleRequestChange(singleton.LocalClientId, clipIndex, seekTime, tvOn, currentMediaId); return true; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(4096, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(ref clipIndex, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref seekTime, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref tvOn, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(currentMediaId ?? string.Empty, false); singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/RequestChange", 0uL, val, (NetworkDelivery)3); return true; } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } public bool TryRequestFullSyncFromServer(string source) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) if (!TryEnsureMessagingReady(source)) { _pendingSyncRequest = true; return false; } NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { _pendingSyncRequest = true; return false; } if (singleton.IsServer) { SendCurrentStateToClient(singleton.LocalClientId); return true; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1); try { singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/RequestSync", 0uL, val, (NetworkDelivery)3); return true; } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } public bool TryToggleLockFromHost(string source) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!TryEnsureMessagingReady(source)) { return false; } NetworkManager singleton = NetworkManager.Singleton; if (singleton.IsServer) { HandleToggleLock(singleton.LocalClientId); return true; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1); try { singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/ToggleLock", 0uL, val, (NetworkDelivery)3); return true; } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } public bool TryToggleLockFeatureFromHost(string source) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (!TryEnsureMessagingReady(source)) { return false; } NetworkManager singleton = NetworkManager.Singleton; if (singleton.IsServer) { HandleToggleLockFeature(singleton.LocalClientId); return true; } FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(1, (Allocator)2, -1); try { singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/ToggleLockFeature", 0uL, val, (NetworkDelivery)3); return true; } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } private void OnRequestChangeMessage(ulong senderClientId, FastBufferReader reader) { //IL_0007: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) int clipIndex = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref clipIndex, default(ForPrimitives)); double seekTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref seekTime, default(ForPrimitives)); bool tvOn = default(bool); ((FastBufferReader)(ref reader)).ReadValueSafe(ref tvOn, default(ForPrimitives)); string mediaId = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref mediaId, false); HandleRequestChange(senderClientId, clipIndex, seekTime, tvOn, mediaId); } private void HandleRequestChange(ulong senderClientId, int clipIndex, double seekTime, bool tvOn, string mediaId) { if (mediaId == null) { mediaId = string.Empty; } bool flag = clipIndex == TVSyncState.CurrentClip; bool flag2 = tvOn == TVSyncState.IsTvOn; bool flag3 = Math.Abs(seekTime - TVSyncState.SeekTime) < 0.01; bool flag4 = string.Equals(mediaId, TVSyncState.CurrentMediaId, StringComparison.OrdinalIgnoreCase); if (flag && flag2 && flag3 && flag4) { TVSyncPlugin.Log.LogDebug((object)"[CymruTVSync] Ignoring duplicate TV state request."); return; } if (TVSyncState.LockFeatureEnabled && TVSyncState.IsLocked && senderClientId != NetworkManager.Singleton.LocalClientId) { TVSyncPlugin.Log.LogDebug((object)$"[CymruTVSync] Client {senderClientId} tried to change TV but it is locked. Ignoring."); return; } TVSyncState.CurrentClip = clipIndex; TVSyncState.SeekTime = seekTime; TVSyncState.IsTvOn = tvOn; TVSyncState.CurrentMediaId = mediaId; BroadcastStateToClients(clipIndex, seekTime, tvOn, mediaId); } private void OnRequestSyncMessage(ulong senderClientId, FastBufferReader reader) { SendCurrentStateToClient(senderClientId); } private void SendCurrentStateToClient(ulong targetClientId) { SendStateMessage(targetClientId, TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId); SendLockStateMessage(targetClientId, TVSyncState.IsLocked); SendLockFeatureStateMessage(targetClientId, TVSyncState.LockFeatureEnabled); } private void OnToggleLockMessage(ulong senderClientId, FastBufferReader reader) { HandleToggleLock(senderClientId); } private void HandleToggleLock(ulong senderClientId) { if (senderClientId != NetworkManager.Singleton.LocalClientId) { TVSyncPlugin.Log.LogWarning((object)$"[CymruTVSync][NGO-RX S<-C] Non-host client {senderClientId} tried to toggle lock. Ignoring."); return; } if (!TVSyncState.LockFeatureEnabled) { TVSyncPlugin.Log.LogDebug((object)"[CymruTVSync] Host tried to toggle TV lock while lock feature is disabled."); return; } TVSyncState.IsLocked = !TVSyncState.IsLocked; TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO-RX S<-C] Host toggled TV lock: {TVSyncState.IsLocked}. Broadcasting."); BroadcastLockStateToClients(TVSyncState.IsLocked); BroadcastStateToClients(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId); } private void OnToggleLockFeatureMessage(ulong senderClientId, FastBufferReader reader) { HandleToggleLockFeature(senderClientId); } private void HandleToggleLockFeature(ulong senderClientId) { if (senderClientId != NetworkManager.Singleton.LocalClientId) { TVSyncPlugin.Log.LogWarning((object)$"[CymruTVSync][NGO-RX S<-C] Non-host client {senderClientId} tried to toggle lock feature. Ignoring."); return; } TVSyncState.LockFeatureEnabled = !TVSyncState.LockFeatureEnabled; if (!TVSyncState.LockFeatureEnabled) { TVSyncState.IsLocked = false; } TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO-RX S<-C] Host toggled lock feature: {TVSyncState.LockFeatureEnabled}. Current lock={TVSyncState.IsLocked}. Broadcasting."); BroadcastLockFeatureStateToClients(TVSyncState.LockFeatureEnabled); BroadcastStateToClients(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId); if (!TVSyncState.LockFeatureEnabled) { BroadcastLockStateToClients(isLocked: false); } } private void BroadcastStateToClients(int clipIndex, double seekTime, bool tvOn, string mediaId) { if (mediaId == null) { mediaId = string.Empty; } ReceiveBroadcastState(clipIndex, seekTime, tvOn, mediaId, "LOCAL-HOST"); foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds) { if (connectedClientsId != NetworkManager.Singleton.LocalClientId) { SendStateMessage(connectedClientsId, clipIndex, seekTime, tvOn, mediaId); } } } private void SendStateMessage(ulong targetClientId, int clipIndex, double seekTime, bool tvOn, string mediaId) { //IL_0015: 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_0028: 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_003b: 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_006c: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(4096, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(ref clipIndex, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref seekTime, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(ref tvOn, default(ForPrimitives)); ((FastBufferWriter)(ref val)).WriteValueSafe(mediaId ?? string.Empty, false); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/BroadcastState", targetClientId, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } private void OnBroadcastStateMessage(ulong senderClientId, FastBufferReader reader) { //IL_0007: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) int clipIndex = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe(ref clipIndex, default(ForPrimitives)); double seekTime = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe(ref seekTime, default(ForPrimitives)); bool tvOn = default(bool); ((FastBufferReader)(ref reader)).ReadValueSafe(ref tvOn, default(ForPrimitives)); string mediaId = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref mediaId, false); ReceiveBroadcastState(clipIndex, seekTime, tvOn, mediaId, senderClientId.ToString()); } private void ReceiveBroadcastState(int clipIndex, double seekTime, bool tvOn, string mediaId, string target) { if (mediaId == null) { mediaId = string.Empty; } TVSyncState.CurrentClip = clipIndex; TVSyncState.SeekTime = seekTime; TVSyncState.IsTvOn = tvOn; TVSyncState.CurrentMediaId = mediaId; TVSyncNetworkHandler.OnTVStateReceived?.Invoke(clipIndex, seekTime, tvOn, mediaId); } private void BroadcastLockStateToClients(bool isLocked) { ReceiveBroadcastLockState(isLocked, "LOCAL-HOST"); foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds) { if (connectedClientsId != NetworkManager.Singleton.LocalClientId) { SendLockStateMessage(connectedClientsId, isLocked); } } } private void SendLockStateMessage(ulong targetClientId, bool isLocked) { //IL_0012: 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_002f: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(ref isLocked, default(ForPrimitives)); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/BroadcastLockState", targetClientId, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } private void OnBroadcastLockStateMessage(ulong senderClientId, FastBufferReader reader) { //IL_0007: 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) bool isLocked = default(bool); ((FastBufferReader)(ref reader)).ReadValueSafe(ref isLocked, default(ForPrimitives)); ReceiveBroadcastLockState(isLocked, senderClientId.ToString()); } private void ReceiveBroadcastLockState(bool isLocked, string target) { TVSyncState.IsLocked = isLocked; TVSyncNetworkHandler.OnLockStateReceived?.Invoke(isLocked); if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost && (Object)(object)HUDManager.Instance != (Object)null) { string text = (isLocked ? "Host has locked the TV." : "Host has unlocked the TV."); HUDManager.Instance.DisplayTip("CymruTV Sync", text, false, false, "TVLockTip"); } } private void BroadcastLockFeatureStateToClients(bool enabled) { ReceiveBroadcastLockFeatureState(enabled, "LOCAL-HOST"); foreach (ulong connectedClientsId in NetworkManager.Singleton.ConnectedClientsIds) { if (connectedClientsId != NetworkManager.Singleton.LocalClientId) { SendLockFeatureStateMessage(connectedClientsId, enabled); } } } private void SendLockFeatureStateMessage(ulong targetClientId, bool enabled) { //IL_0012: 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_002f: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter val = default(FastBufferWriter); ((FastBufferWriter)(ref val))..ctor(64, (Allocator)2, -1); try { ((FastBufferWriter)(ref val)).WriteValueSafe(ref enabled, default(ForPrimitives)); NetworkManager.Singleton.CustomMessagingManager.SendNamedMessage("CymruTVSync/BroadcastLockFeature", targetClientId, val, (NetworkDelivery)3); } finally { ((IDisposable)(FastBufferWriter)(ref val)).Dispose(); } } private void OnBroadcastLockFeatureMessage(ulong senderClientId, FastBufferReader reader) { //IL_0007: 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) bool enabled = default(bool); ((FastBufferReader)(ref reader)).ReadValueSafe(ref enabled, default(ForPrimitives)); ReceiveBroadcastLockFeatureState(enabled, senderClientId.ToString()); } private void ReceiveBroadcastLockFeatureState(bool enabled, string target) { TVSyncState.LockFeatureEnabled = enabled; TVSyncNetworkHandler.OnLockFeatureStateReceived?.Invoke(enabled); if ((Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsHost && (Object)(object)HUDManager.Instance != (Object)null) { string text = (enabled ? "Host enabled TV lock controls (L toggles lock)." : "Host disabled TV lock controls."); HUDManager.Instance.DisplayTip("CymruTV Sync", text, false, false, "TVLockFeatureTip"); } } } public static class TVSyncState { public static int CurrentClip = 0; public static double SeekTime = 0.0; public static bool IsTvOn = false; public static string CurrentMediaId = string.Empty; public static bool LockFeatureEnabled = true; public static bool IsLocked = false; } [HarmonyPatch] public class TVSyncNetworkObjectManager { private static bool _ngoCallbacksRegistered; private static string LocalRole() { if ((Object)(object)NetworkManager.Singleton == (Object)null) { return "NO-NET"; } if (NetworkManager.Singleton.IsHost) { return "HOST"; } if (NetworkManager.Singleton.IsServer) { return "SERVER"; } if (NetworkManager.Singleton.IsClient) { return "CLIENT"; } return "OFFLINE"; } private static void EnsureNgoCallbacks() { if (!_ngoCallbacksRegistered && !((Object)(object)NetworkManager.Singleton == (Object)null)) { NetworkManager.Singleton.OnClientConnectedCallback += delegate(ulong clientId) { TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO] OnClientConnected callback: client={clientId}, local={NetworkManager.Singleton.LocalClientId}, role={LocalRole()}."); }; NetworkManager.Singleton.OnClientDisconnectCallback += delegate(ulong clientId) { TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync][NGO] OnClientDisconnected callback: client={clientId}, local={NetworkManager.Singleton.LocalClientId}, role={LocalRole()}."); }; _ngoCallbacksRegistered = true; TVSyncPlugin.Log.LogInfo((object)"[CymruTVSync][NGO] Registered connection callbacks."); } } [HarmonyPostfix] [HarmonyPatch(typeof(GameNetworkManager), "Start")] public static void RegisterNetworkPrefab() { TVSyncNetworkHandler.EnsureInstance(); if ((Object)(object)NetworkManager.Singleton == (Object)null) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Could not initialize NGO messaging because NetworkManager.Singleton is null."); return; } EnsureNgoCallbacks(); TVSyncNetworkHandler.EnsureMessagingReady("GameNetworkManager.Start"); TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync] NGO message transport ready (role={LocalRole()}, local={NetworkManager.Singleton.LocalClientId})."); } [HarmonyPostfix] [HarmonyPatch(typeof(StartOfRound), "Awake")] public static void SpawnNetworkHandler() { TVSyncNetworkHandler.EnsureInstance(); if ((Object)(object)NetworkManager.Singleton == (Object)null) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Cannot refresh NGO messaging because NetworkManager.Singleton is null."); return; } TVSyncNetworkHandler.EnsureMessagingReady("StartOfRound.Awake"); TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync] NGO message transport refreshed for round start (role={LocalRole()}, local={NetworkManager.Singleton.LocalClientId})."); } } internal static class TVLockGuard { private static InteractTrigger _cachedTvTrigger; private static string _originalHoverTip; private static string _originalDisabledHoverTip; private static bool _warnedMissingTrigger; public static void ApplyAuthoritativeState() { TVSyncInputPatch.ApplyState(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId); } public static bool ShouldBlockLocalInput() { if (!TVSyncState.LockFeatureEnabled) { return false; } if (!TVSyncState.IsLocked) { return false; } if ((Object)(object)NetworkManager.Singleton == (Object)null) { return false; } return !NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer; } public static void NotifyBlocked(string reason) { } private static string ScenePath(Transform t) { if ((Object)(object)t == (Object)null) { return "(null)"; } StringBuilder stringBuilder = new StringBuilder(); Transform val = t; while ((Object)(object)val != (Object)null) { if (stringBuilder.Length > 0) { stringBuilder.Insert(0, "/"); } stringBuilder.Insert(0, ((Object)val).name); val = val.parent; } return stringBuilder.ToString(); } private static InteractTrigger FindTVInteractTrigger() { if ((Object)(object)_cachedTvTrigger != (Object)null) { return _cachedTvTrigger; } InteractTrigger[] array = Object.FindObjectsOfType(true); string[] array2 = new string[5] { "Switch TV", "Turn on TV", "Turn off TV", "Toggle TV", "TV" }; string[] array3 = array2; foreach (string value in array3) { InteractTrigger[] array4 = array; foreach (InteractTrigger val in array4) { if (val.hoverTip != null && val.hoverTip.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { _cachedTvTrigger = val; return _cachedTvTrigger; } } } TVScript[] array5 = Object.FindObjectsOfType(true); TVScript[] array6 = array5; foreach (TVScript val2 in array6) { InteractTrigger componentInChildren = ((Component)val2).GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { _cachedTvTrigger = componentInChildren; return _cachedTvTrigger; } } InteractTrigger[] array7 = array; foreach (InteractTrigger val3 in array7) { Transform val4 = ((Component)val3).transform; while ((Object)(object)val4 != (Object)null) { if (((Object)val4).name.IndexOf("Television", StringComparison.OrdinalIgnoreCase) >= 0 || ((Object)val4).name.IndexOf("TVSet", StringComparison.OrdinalIgnoreCase) >= 0) { _cachedTvTrigger = val3; return _cachedTvTrigger; } val4 = val4.parent; } } return null; } public static void ApplyTriggerLock() { if ((Object)(object)NetworkManager.Singleton == (Object)null) { return; } bool flag = TVSyncState.LockFeatureEnabled && TVSyncState.IsLocked; bool flag2 = NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer; InteractTrigger val = FindTVInteractTrigger(); if ((Object)(object)val == (Object)null) { if (!_warnedMissingTrigger) { _warnedMissingTrigger = true; TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Could not find TV InteractTrigger to apply lock state."); } return; } _warnedMissingTrigger = false; if (string.IsNullOrEmpty(_originalHoverTip)) { _originalHoverTip = val.hoverTip; } if (string.IsNullOrEmpty(_originalDisabledHoverTip)) { _originalDisabledHoverTip = val.disabledHoverTip; } string text = (string.IsNullOrWhiteSpace(_originalHoverTip) ? "Switch TV" : _originalHoverTip); if (flag2) { val.interactable = true; val.hoverTip = (flag ? (text + "\nTV is locked for clients") : (text + "\nTV is unlocked")); val.disabledHoverTip = _originalDisabledHoverTip; } else if (flag) { val.interactable = false; val.disabledHoverTip = "TV locked"; val.hoverTip = text; } else { val.interactable = true; val.hoverTip = text; val.disabledHoverTip = _originalDisabledHoverTip; } } } internal static class BestestTV { private static Type _patchType; private static Type _videoManagerType; private static Type _configType; public static FieldInfo TVIndexField; public static FieldInfo VideoSourceField; public static FieldInfo AudioSourceField; public static FieldInfo TvIsOnField; public static FieldInfo VideosField; public static FieldInfo EnableChannelsField; public static FieldInfo EnableSeekingField; public static FieldInfo SkipForwardKeyField; public static FieldInfo SkipReverseKeyField; public static FieldInfo SeekForwardKeyField; public static FieldInfo SeekReverseKeyField; public static bool Ready { get; private set; } public static int TVIndex { get { return (TVIndexField != null) ? ((int)TVIndexField.GetValue(null)) : 0; } set { TVIndexField?.SetValue(null, value); } } public static VideoPlayer VideoSource { get { object? obj = VideoSourceField?.GetValue(null); return (VideoPlayer)((obj is VideoPlayer) ? obj : null); } } public static AudioSource AudioSource { get { object? obj = AudioSourceField?.GetValue(null); return (AudioSource)((obj is AudioSource) ? obj : null); } } public static bool TvIsOn { get { return TvIsOnField != null && (bool)TvIsOnField.GetValue(null); } set { TvIsOnField?.SetValue(null, value); } } public static List Videos => VideosField?.GetValue(null) as List; public static bool EnableChannels => EnableChannelsField == null || GetConfigValue(EnableChannelsField); public static bool EnableSeeking => EnableSeekingField == null || GetConfigValue(EnableSeekingField); public static void Init() { try { Assembly assembly = FindBestestTVAssembly(); if (assembly == null) { TVSyncPlugin.Log.LogError((object)("[CymruTVSync] Could not find BestestTVMod assembly. Loaded assemblies: " + string.Join(", ", GetLoadedAssemblyNames()))); return; } TVSyncPlugin.Log.LogInfo((object)("[CymruTVSync] Found BestestTVMod assembly: " + assembly.GetName().Name)); Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (_patchType == null && type.GetField("TVIndex", BindingFlags.Static | BindingFlags.Public) != null) { _patchType = type; } if (_videoManagerType == null && type.GetField("Videos", BindingFlags.Static | BindingFlags.Public) != null) { _videoManagerType = type; } if (_configType == null && type.GetField("enableChannels", BindingFlags.Static | BindingFlags.Public) != null) { _configType = type; } } if (_patchType == null) { TVSyncPlugin.Log.LogError((object)"[CymruTVSync] Could not find TVScriptPatches type in BestestTVMod!"); return; } TVSyncPlugin.Log.LogInfo((object)("[CymruTVSync] Found patch type: " + _patchType.FullName)); BindingFlags bindingAttr = BindingFlags.Static | BindingFlags.Public; TVIndexField = _patchType.GetField("TVIndex", bindingAttr); VideoSourceField = _patchType.GetField("videoSource", bindingAttr); AudioSourceField = _patchType.GetField("audioSource", bindingAttr); TvIsOnField = _patchType.GetField("tvIsCurrentlyOn", bindingAttr); if (_videoManagerType != null) { VideosField = _videoManagerType.GetField("Videos", bindingAttr); } if (_configType != null) { EnableChannelsField = _configType.GetField("enableChannels", bindingAttr); EnableSeekingField = _configType.GetField("enableSeeking", bindingAttr); SkipForwardKeyField = _configType.GetField("skipForwardKeyBind", bindingAttr); SkipReverseKeyField = _configType.GetField("skipReverseKeyBind", bindingAttr); SeekForwardKeyField = _configType.GetField("seekForwardKeyBind", bindingAttr); SeekReverseKeyField = _configType.GetField("seekReverseKeyBind", bindingAttr); } Ready = TVIndexField != null && VideoSourceField != null; TVSyncPlugin.Log.LogInfo((object)($"[CymruTVSync] BestestTV reflection ready={Ready}. " + $"TVIndex={TVIndexField != null}, videoSource={VideoSourceField != null}, " + $"Videos={VideosField != null}, Config={_configType != null}")); } catch (Exception arg) { TVSyncPlugin.Log.LogError((object)$"[CymruTVSync] Reflection init failed: {arg}"); } } private static Assembly FindBestestTVAssembly() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { if (IsBestestTVAssemblyName(assembly.GetName().Name)) { return assembly; } } try { string pluginPath = Paths.PluginPath; if (!Directory.Exists(pluginPath)) { return null; } string[] files = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (IsBestestTVAssemblyName(fileNameWithoutExtension)) { TVSyncPlugin.Log.LogInfo((object)("[CymruTVSync] Loading BestestTV assembly from disk: " + text)); return Assembly.LoadFrom(text); } } } catch (Exception ex) { TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync] Failed to load BestestTV assembly from disk: " + ex.Message)); } return null; } private static bool IsBestestTVAssemblyName(string assemblyName) { if (string.IsNullOrWhiteSpace(assemblyName)) { return false; } return assemblyName.IndexOf("BestestTelevisionMod", StringComparison.OrdinalIgnoreCase) >= 0 || assemblyName.IndexOf("BestestTV", StringComparison.OrdinalIgnoreCase) >= 0 || assemblyName.IndexOf("DeathWrench", StringComparison.OrdinalIgnoreCase) >= 0; } private static string[] GetLoadedAssemblyNames() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); string[] array = new string[assemblies.Length]; for (int i = 0; i < assemblies.Length; i++) { array[i] = assemblies[i].GetName().Name; } return array; } private static T GetConfigValue(FieldInfo fi) { try { object value = fi.GetValue(null); if (value == null) { return default(T); } PropertyInfo property = value.GetType().GetProperty("Value"); if (property == null) { return default(T); } return (T)property.GetValue(value); } catch { return default(T); } } public static Key GetKeyBind(FieldInfo fi, Key fallback) { //IL_004b: 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_004f: 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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) try { object obj = fi?.GetValue(null); if (obj == null) { return fallback; } PropertyInfo property = obj.GetType().GetProperty("Value"); return (property == null) ? fallback : ConvertToInputSystemKey(property.GetValue(obj), fallback); } catch { return fallback; } } private static Key ConvertToInputSystemKey(object value, Key fallback) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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) //IL_0028: 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) //IL_0040: 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_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (value == null) { return fallback; } if (value is Key result) { return result; } string value2 = value.ToString(); if (string.IsNullOrWhiteSpace(value2)) { return fallback; } Key result2; return Enum.TryParse(value2, ignoreCase: true, out result2) ? result2 : fallback; } public static bool WasPressedThisFrame(Key key) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Keyboard current = Keyboard.current; if (current == null) { return false; } try { return ((ButtonControl)current[key]).wasPressedThisFrame; } catch { return false; } } public static string NormalizeMediaId(string media) { if (string.IsNullOrWhiteSpace(media)) { return string.Empty; } string text = media.Trim(); if (Uri.TryCreate(text, UriKind.Absolute, out Uri result)) { text = ((!result.IsFile) ? result.AbsoluteUri : result.LocalPath); } else if (text.StartsWith("file://", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(7); } string fileName = Path.GetFileName(text); if (!string.IsNullOrWhiteSpace(fileName)) { return fileName.Trim().ToLowerInvariant(); } text = text.Replace('\\', '/').TrimStart('/'); return text.ToLowerInvariant(); } public static string GetCurrentMediaId() { return ((Object)(object)VideoSource == (Object)null) ? string.Empty : NormalizeMediaId(VideoSource.url); } public static int FindVideoIndexByMediaId(string mediaId) { string text = NormalizeMediaId(mediaId); if (string.IsNullOrEmpty(text)) { return -1; } List videos = Videos; if (videos == null) { return -1; } for (int i = 0; i < videos.Count; i++) { if (NormalizeMediaId(videos[i]) == text) { return i; } } return -1; } } [HarmonyPatch] public class TVSyncBestestHooks { [CompilerGenerated] private sealed class d__9 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private MethodBase <>2__current; private int <>l__initialThreadId; private Type 5__1; private string[] 5__2; private string[] <>s__3; private int <>s__4; private string 5__5; private MethodInfo 5__6; MethodBase IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__9(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { 5__1 = null; 5__2 = null; <>s__3 = null; 5__5 = null; 5__6 = null; <>1__state = -2; } private bool MoveNext() { int num = <>1__state; if (num != 0) { if (num != 1) { return false; } <>1__state = -1; 5__6 = null; 5__5 = null; goto IL_012e; } <>1__state = -1; 5__1 = AccessTools.TypeByName("BestestTVModPlugin.TVScriptPatches"); if (5__1 == null) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Could not find BestestTVModPlugin.TVScriptPatches for direct input hooks."); return false; } 5__2 = new string[5] { "TVIndexUp", "TVIndexDown", "GetTVInput", "Update", "TurnTVOnOff" }; <>s__3 = 5__2; <>s__4 = 0; goto IL_013c; IL_012e: <>s__4++; goto IL_013c; IL_013c: if (<>s__4 < <>s__3.Length) { 5__5 = <>s__3[<>s__4]; 5__6 = AccessTools.DeclaredMethod(5__1, 5__5, (Type[])null, (Type[])null); if (5__6 == null) { TVSyncPlugin.Log.LogWarning((object)("[CymruTVSync] Could not hook BestestTV method " + 5__5 + ".")); goto IL_012e; } <>2__current = 5__6; <>1__state = 1; return true; } <>s__3 = null; return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__9(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } private static int _preInputClip; private static double _preInputTime; private static bool _preInputOn; private static bool _updateSnapshotReady; private static int _lastUpdateClip; private static double _lastUpdateTime; private static bool _lastUpdateOn; private static bool _warnedBestestNotReady; private static bool _warnedHandlerMissing; [IteratorStateMachine(typeof(d__9))] public static IEnumerable TargetMethods() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__9(-2); } [HarmonyPrefix] [HarmonyPriority(200)] public static bool Prefix(MethodBase __originalMethod) { if (__originalMethod == null) { return true; } string name = __originalMethod.Name; if (name == "GetTVInput") { _preInputClip = BestestTV.TVIndex; _preInputTime = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0); _preInputOn = BestestTV.TvIsOn; } switch (name) { default: if (!(name == "TurnTVOnOff")) { break; } goto case "TVIndexUp"; case "TVIndexUp": case "TVIndexDown": case "GetTVInput": if (TVLockGuard.ShouldBlockLocalInput()) { if (name == "TurnTVOnOff" && TVSyncPatches.SuppressOutboundSync) { return true; } if (name != "GetTVInput") { TVLockGuard.NotifyBlocked(name); } return false; } break; } return true; } [HarmonyPostfix] [HarmonyPriority(200)] public static void Postfix(MethodBase __originalMethod) { //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) if (__originalMethod == null || TVSyncPatches.SuppressOutboundSync) { return; } if (!BestestTV.Ready) { BestestTV.Init(); if (!BestestTV.Ready) { if (!_warnedBestestNotReady) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Bestest hooks skipped because BestestTV reflection is not ready on this client."); _warnedBestestNotReady = true; } return; } } _warnedBestestNotReady = false; if ((Object)(object)TVSyncNetworkHandler.Instance == (Object)null) { if (!_warnedHandlerMissing) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] Bestest hooks skipped because TVSyncNetworkHandler.Instance is null on this client."); _warnedHandlerMissing = true; } return; } _warnedHandlerMissing = false; int tVIndex = BestestTV.TVIndex; double num = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0); bool tvIsOn = BestestTV.TvIsOn; if (__originalMethod.Name == "TVIndexUp" || __originalMethod.Name == "TVIndexDown") { TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:" + __originalMethod.Name, tVIndex, 0.0, tvIsOn); } else if (__originalMethod.Name == "Update") { if (_updateSnapshotReady) { bool flag = tVIndex == _lastUpdateClip; bool flag2 = tvIsOn == _lastUpdateOn; double num2 = Math.Abs(num - _lastUpdateTime); if (flag && flag2 && tvIsOn && num2 >= 1.0) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:UpdateSeekJump", tVIndex, num, tvIsOn); } } _lastUpdateClip = tVIndex; _lastUpdateTime = num; _lastUpdateOn = tvIsOn; _updateSnapshotReady = true; } else { if (__originalMethod.Name != "GetTVInput") { return; } Key keyBind = BestestTV.GetKeyBind(BestestTV.SeekForwardKeyField, (Key)63); Key keyBind2 = BestestTV.GetKeyBind(BestestTV.SeekReverseKeyField, (Key)64); if ((BestestTV.WasPressedThisFrame(keyBind) || BestestTV.WasPressedThisFrame(keyBind2)) && BestestTV.EnableSeeking) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:GetTVInputSeekKey", tVIndex, num, tvIsOn); return; } bool flag3 = tVIndex != _preInputClip; bool flag4 = tvIsOn != _preInputOn; bool flag5 = Math.Abs(num - _preInputTime) >= 0.75; if (flag3 || flag4 || flag5) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("Bestest:GetTVInputDelta", tVIndex, num, tvIsOn); } } } } [HarmonyPatch] public class TVSyncPatches { private static float _ignoreOutboundUntilTime; private static bool _skipNextTurnTvOnOffPostfix; private static float _hostControlIntentUntilTime; private static int _lastObservedClip = -1; private static double _lastObservedSeek = 0.0; private static bool _lastObservedOn = false; private static bool _hasObservedState; private static string _lastObservedMediaId = string.Empty; private static float _nextWatchSendTime; private static bool _watcherWarnedBestestNotReady; private static bool _watcherWarnedHandlerMissing; internal static bool SuppressOutboundSync => Time.unscaledTime < _ignoreOutboundUntilTime; internal static void MarkHostControlIntent(float durationSeconds = 0.5f) { if (!((Object)(object)NetworkManager.Singleton == (Object)null) && (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsServer)) { _hostControlIntentUntilTime = Mathf.Max(_hostControlIntentUntilTime, Time.unscaledTime + durationSeconds); } } internal static bool ConsumeHostControlIntent() { bool flag = Time.unscaledTime <= _hostControlIntentUntilTime; if (flag) { _hostControlIntentUntilTime = 0f; } return flag; } internal static void BeginSuppressedRemoteApply() { _ignoreOutboundUntilTime = Mathf.Max(_ignoreOutboundUntilTime, Time.unscaledTime + 1f); } internal static void EndSuppressedRemoteApply() { _ignoreOutboundUntilTime = Mathf.Max(_ignoreOutboundUntilTime, Time.unscaledTime + 0.5f); } internal static void UpdateObservedStateSnapshot(int clipIndex, double seekTime, bool tvOn, string mediaId) { _lastObservedClip = clipIndex; _lastObservedSeek = seekTime; _lastObservedOn = tvOn; _lastObservedMediaId = mediaId ?? string.Empty; _hasObservedState = true; _nextWatchSendTime = Mathf.Max(_nextWatchSendTime, Time.unscaledTime + 0.75f); } [HarmonyPostfix] [HarmonyPatch(typeof(TVScript), "Update")] [HarmonyPriority(200)] public static void TVUpdate_Postfix() { if (SuppressOutboundSync) { return; } if (!BestestTV.Ready) { BestestTV.Init(); if (!BestestTV.Ready) { if (!_watcherWarnedBestestNotReady) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] TV watcher skipped because BestestTV reflection is not ready on this client."); _watcherWarnedBestestNotReady = true; } return; } } _watcherWarnedBestestNotReady = false; if ((Object)(object)TVSyncNetworkHandler.Instance == (Object)null) { if (!_watcherWarnedHandlerMissing) { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] TV watcher skipped because TVSyncNetworkHandler.Instance is null on this client."); _watcherWarnedHandlerMissing = true; } return; } _watcherWarnedHandlerMissing = false; if ((Object)(object)NetworkManager.Singleton == (Object)null || !NetworkManager.Singleton.IsClient) { return; } int tVIndex = BestestTV.TVIndex; double num = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0); bool tvIsOn = BestestTV.TvIsOn; string currentMediaId = BestestTV.GetCurrentMediaId(); if (!_hasObservedState) { _lastObservedClip = tVIndex; _lastObservedSeek = num; _lastObservedOn = tvIsOn; _lastObservedMediaId = currentMediaId; _hasObservedState = true; return; } bool flag = tVIndex != _lastObservedClip; bool flag2 = tvIsOn != _lastObservedOn; bool flag3 = Math.Abs(num - _lastObservedSeek) >= 1.0; bool flag4 = !string.Equals(currentMediaId, _lastObservedMediaId, StringComparison.OrdinalIgnoreCase); if (flag || flag2 || flag3 || flag4) { if (TVLockGuard.ShouldBlockLocalInput()) { _lastObservedClip = TVSyncState.CurrentClip; _lastObservedSeek = TVSyncState.SeekTime; _lastObservedOn = TVSyncState.IsTvOn; _lastObservedMediaId = TVSyncState.CurrentMediaId; return; } if (Time.unscaledTime >= _nextWatchSendTime) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("TVScript.UpdateWatcher", tVIndex, num, tvIsOn); _nextWatchSendTime = Time.unscaledTime + 0.12f; } } _lastObservedClip = tVIndex; _lastObservedSeek = num; _lastObservedOn = tvIsOn; _lastObservedMediaId = currentMediaId; } [HarmonyPrefix] [HarmonyPatch(typeof(TVScript), "TurnTVOnOff")] [HarmonyPriority(600)] public static bool TurnTVOnOff_Prefix(bool on) { MarkHostControlIntent(0.6f); if (!TVLockGuard.ShouldBlockLocalInput()) { return true; } _skipNextTurnTvOnOffPostfix = true; return true; } [HarmonyPostfix] [HarmonyPatch(typeof(TVScript), "TurnTVOnOff")] [HarmonyPriority(200)] public static void TurnTVOnOff_Postfix(bool on) { if (_skipNextTurnTvOnOffPostfix) { _skipNextTurnTvOnOffPostfix = false; } else if (!SuppressOutboundSync && BestestTV.Ready && !((Object)(object)TVSyncNetworkHandler.Instance == (Object)null)) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("TVScript.TurnTVOnOff", BestestTV.TVIndex, ((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0, on); } } [HarmonyPostfix] [HarmonyPatch(typeof(TVScript), "TVFinishedClip")] [HarmonyPriority(200)] public static void TVFinishedClip_Postfix() { if (!SuppressOutboundSync && BestestTV.Ready && !((Object)(object)TVSyncNetworkHandler.Instance == (Object)null) && BestestTV.TvIsOn) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("TVScript.TVFinishedClip", BestestTV.TVIndex, 0.0, tvOn: true); } } } [HarmonyPatch(typeof(PlayerControllerB), "Update")] public class TVSyncInputPatch { [CompilerGenerated] private static class <>O { public static EventHandler <0>__OnVideoPrepared; } private static VideoPlayer _preparedVideoSource; private static AudioSource _preparedAudioSource; private static double _preparedSeekTime; private static bool _preparedTvOn; private static int _preparedClipIndex; private static string _preparedMediaId; [HarmonyPostfix] [HarmonyPriority(200)] public static void Update_Postfix(PlayerControllerB __instance) { //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) if (TVSyncPatches.SuppressOutboundSync || !BestestTV.Ready || !((NetworkBehaviour)__instance).IsOwner || !__instance.isPlayerControlled || __instance.inTerminalMenu || __instance.isTypingChat || __instance.isPlayerDead || (Object)(object)TVSyncNetworkHandler.Instance == (Object)null) { return; } InteractTrigger hoveringOverTrigger = __instance.hoveringOverTrigger; if ((Object)(object)hoveringOverTrigger == (Object)null) { return; } Transform parent = ((Component)hoveringOverTrigger).transform.parent; if ((Object)(object)parent == (Object)null || !((Object)((Component)parent).gameObject).name.Contains("Television")) { return; } Key keyBind = BestestTV.GetKeyBind(BestestTV.SkipForwardKeyField, (Key)62); Key keyBind2 = BestestTV.GetKeyBind(BestestTV.SkipReverseKeyField, (Key)61); Key keyBind3 = BestestTV.GetKeyBind(BestestTV.SeekForwardKeyField, (Key)63); Key keyBind4 = BestestTV.GetKeyBind(BestestTV.SeekReverseKeyField, (Key)64); bool flag = BestestTV.WasPressedThisFrame(keyBind); bool flag2 = BestestTV.WasPressedThisFrame(keyBind2); bool flag3 = BestestTV.WasPressedThisFrame(keyBind3); bool flag4 = BestestTV.WasPressedThisFrame(keyBind4); bool flag5 = (flag || flag2) && BestestTV.EnableChannels && BestestTV.TvIsOn; bool flag6 = (flag3 || flag4) && BestestTV.EnableSeeking; if (!flag5 && !flag6) { return; } if (TVLockGuard.ShouldBlockLocalInput()) { TVLockGuard.NotifyBlocked("PlayerControllerB.Update input"); return; } double seekTime = (((Object)(object)BestestTV.VideoSource != (Object)null) ? BestestTV.VideoSource.time : 0.0); if (flag5) { TVSyncNetworkHandler.Instance.TrySendChangeToServer("PlayerController.Update:ChannelSkip", BestestTV.TVIndex, 0.0, BestestTV.TvIsOn); } else { TVSyncNetworkHandler.Instance.TrySendChangeToServer("PlayerController.Update:Seek", BestestTV.TVIndex, seekTime, BestestTV.TvIsOn); } } private static void OnVideoPrepared(VideoPlayer source) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown try { if ((Object)(object)_preparedVideoSource != (Object)null) { VideoPlayer preparedVideoSource = _preparedVideoSource; object obj = <>O.<0>__OnVideoPrepared; if (obj == null) { EventHandler val = OnVideoPrepared; <>O.<0>__OnVideoPrepared = val; obj = (object)val; } preparedVideoSource.prepareCompleted -= (EventHandler)obj; } source.time = _preparedSeekTime; if (_preparedTvOn) { if (!source.isPlaying) { source.Play(); } if ((Object)(object)_preparedAudioSource != (Object)null && !_preparedAudioSource.isPlaying) { _preparedAudioSource.Play(); } BestestTV.TvIsOn = true; } else { if (source.isPlaying) { source.Stop(); } if ((Object)(object)_preparedAudioSource != (Object)null && _preparedAudioSource.isPlaying) { _preparedAudioSource.Stop(); } BestestTV.TvIsOn = false; } TVSyncPatches.UpdateObservedStateSnapshot(_preparedClipIndex, _preparedSeekTime, _preparedTvOn, _preparedMediaId); } finally { _preparedVideoSource = null; _preparedAudioSource = null; _preparedMediaId = string.Empty; TVSyncPatches.EndSuppressedRemoteApply(); } } private static void QueuePreparedPlayback(VideoPlayer source, AudioSource audio, int clipIndex, double seekTime, bool tvOn, string mediaId) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown if ((Object)(object)_preparedVideoSource != (Object)null) { VideoPlayer preparedVideoSource = _preparedVideoSource; object obj = <>O.<0>__OnVideoPrepared; if (obj == null) { EventHandler val = OnVideoPrepared; <>O.<0>__OnVideoPrepared = val; obj = (object)val; } preparedVideoSource.prepareCompleted -= (EventHandler)obj; TVSyncPatches.EndSuppressedRemoteApply(); } _preparedVideoSource = source; _preparedAudioSource = audio; _preparedSeekTime = seekTime; _preparedTvOn = tvOn; _preparedClipIndex = clipIndex; _preparedMediaId = mediaId ?? string.Empty; object obj2 = <>O.<0>__OnVideoPrepared; if (obj2 == null) { EventHandler val2 = OnVideoPrepared; <>O.<0>__OnVideoPrepared = val2; obj2 = (object)val2; } source.prepareCompleted -= (EventHandler)obj2; object obj3 = <>O.<0>__OnVideoPrepared; if (obj3 == null) { EventHandler val3 = OnVideoPrepared; <>O.<0>__OnVideoPrepared = val3; obj3 = (object)val3; } source.prepareCompleted += (EventHandler)obj3; source.Prepare(); } private static void ApplyVisualPowerForClients(bool tvOn) { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null || !singleton.IsClient || singleton.IsServer) { return; } TVScript[] array = Object.FindObjectsOfType(true); TVScript[] array2 = array; foreach (TVScript val in array2) { if (!((Object)(object)val == (Object)null)) { try { val.TurnTVOnOff(tvOn); } catch (Exception ex) { TVSyncPlugin.Log.LogDebug((object)("[CymruTVSync] Visual power fallback failed on TVScript: " + ex.Message)); } } } } public static void ApplyState(int clipIndex, double seekTime, bool tvOn, string mediaId = "") { if (!BestestTV.Ready || (Object)(object)BestestTV.VideoSource == (Object)null) { return; } TVSyncPatches.BeginSuppressedRemoteApply(); try { bool tvIsOn = BestestTV.TvIsOn; VideoPlayer videoSource = BestestTV.VideoSource; AudioSource audioSource = BestestTV.AudioSource; List videos = BestestTV.Videos; string text = BestestTV.NormalizeMediaId(mediaId); string b = BestestTV.NormalizeMediaId(videoSource.url); int num = clipIndex; if (!string.IsNullOrEmpty(text)) { int num2 = BestestTV.FindVideoIndexByMediaId(text); if (num2 >= 0) { num = num2; } } bool flag = videos != null && num >= 0 && num < videos.Count; bool flag2 = !string.IsNullOrEmpty(text) && !string.Equals(text, b, StringComparison.OrdinalIgnoreCase); bool flag3 = false; if (flag && (BestestTV.TVIndex != num || flag2)) { BestestTV.TVIndex = num; videoSource.Stop(); if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying) { audioSource.Stop(); } videoSource.url = "file://" + videos[num]; videoSource.time = 0.0; flag3 = true; } else if (!flag && flag2) { TVSyncPlugin.Log.LogWarning((object)$"[CymruTVSync] Received media id '{text}' that does not exist in local list. Using incoming clip index {clipIndex} as fallback."); } if (!flag3 && tvOn && !videoSource.isPrepared) { if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying) { audioSource.Stop(); } flag3 = true; } if (flag3) { QueuePreparedPlayback(videoSource, audioSource, num, seekTime, tvOn, mediaId); if (tvIsOn != tvOn) { ApplyVisualPowerForClients(tvOn); } return; } videoSource.time = seekTime; if (tvOn) { if (!BestestTV.TvIsOn) { BestestTV.TvIsOn = true; } if (!videoSource.isPlaying) { videoSource.Play(); } if ((Object)(object)audioSource != (Object)null && !audioSource.isPlaying) { audioSource.Play(); } } else { if (videoSource.isPlaying) { videoSource.Stop(); } if ((Object)(object)audioSource != (Object)null && audioSource.isPlaying) { audioSource.Stop(); } BestestTV.TvIsOn = false; } if (tvIsOn != tvOn) { ApplyVisualPowerForClients(tvOn); } TVSyncPatches.UpdateObservedStateSnapshot(num, seekTime, tvOn, mediaId); } finally { if ((Object)(object)_preparedVideoSource == (Object)null) { TVSyncPatches.EndSuppressedRemoteApply(); } } } } [HarmonyPatch(typeof(TVScript))] public class TVSyncReceiver { private static bool _subscribed; [HarmonyPostfix] [HarmonyPatch("OnEnable")] public static void OnEnable_Postfix(TVScript __instance) { BestestTV.Init(); if (!_subscribed) { TVSyncNetworkHandler.OnTVStateReceived -= OnStateReceived; TVSyncNetworkHandler.OnTVStateReceived += OnStateReceived; TVSyncNetworkHandler.OnLockStateReceived -= OnLockReceived; TVSyncNetworkHandler.OnLockStateReceived += OnLockReceived; TVSyncNetworkHandler.OnLockFeatureStateReceived -= OnLockFeatureReceived; TVSyncNetworkHandler.OnLockFeatureStateReceived += OnLockFeatureReceived; _subscribed = true; } TVLockGuard.ApplyTriggerLock(); if (!NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer) { if ((Object)(object)TVSyncNetworkHandler.Instance != (Object)null) { TVSyncNetworkHandler.Instance.TryRequestFullSyncFromServer("TVScript.OnEnable"); } else { TVSyncPlugin.Log.LogWarning((object)"[CymruTVSync] TV enabled before network handler existed, so no sync request could be sent yet."); } } } [HarmonyPostfix] [HarmonyPatch("OnDisable")] public static void OnDisable_Postfix() { TVSyncNetworkHandler.OnTVStateReceived -= OnStateReceived; TVSyncNetworkHandler.OnLockStateReceived -= OnLockReceived; TVSyncNetworkHandler.OnLockFeatureStateReceived -= OnLockFeatureReceived; _subscribed = false; } private static void OnStateReceived(int clipIndex, double seekTime, bool tvOn, string mediaId) { TVSyncInputPatch.ApplyState(clipIndex, seekTime, tvOn, mediaId); } private static void OnLockReceived(bool isLocked) { TVLockGuard.ApplyTriggerLock(); } private static void OnLockFeatureReceived(bool enabled) { TVLockGuard.ApplyTriggerLock(); if (!enabled) { TVSyncInputPatch.ApplyState(TVSyncState.CurrentClip, TVSyncState.SeekTime, TVSyncState.IsTvOn, TVSyncState.CurrentMediaId); } } } [HarmonyPatch(typeof(InteractTrigger), "Interact")] public class TVSyncInteractTriggerPatch { [HarmonyPrefix] [HarmonyPriority(600)] public static bool Prefix(InteractTrigger __instance) { if (!TVLockGuard.ShouldBlockLocalInput()) { return true; } if (__instance.hoverTip == null || !__instance.hoverTip.Contains("Switch TV")) { return true; } TVLockGuard.NotifyBlocked("InteractTrigger.Interact"); return false; } } [HarmonyPatch(typeof(PlayerControllerB), "Update")] public class TVSyncLockKeyPatch { [HarmonyPostfix] public static void Update_Postfix(PlayerControllerB __instance) { if (!((NetworkBehaviour)__instance).IsOwner || !__instance.isPlayerControlled || __instance.inTerminalMenu || __instance.isTypingChat || __instance.isPlayerDead || (!NetworkManager.Singleton.IsHost && !NetworkManager.Singleton.IsServer) || (Object)(object)TVSyncNetworkHandler.Instance == (Object)null) { return; } if (BestestTV.WasPressedThisFrame((Key)29)) { TVSyncNetworkHandler.Instance.TryToggleLockFeatureFromHost("HostKey:O"); } else { if (!BestestTV.WasPressedThisFrame((Key)26)) { return; } if (!TVSyncState.LockFeatureEnabled) { if ((Object)(object)HUDManager.Instance != (Object)null) { HUDManager.Instance.DisplayTip("CymruTV Sync", "Lock controls are disabled. Press O to enable.", false, false, "TVLockDisabledTip"); } } else { TVSyncNetworkHandler.Instance.TryToggleLockFromHost("HostKey:L"); } } } } [HarmonyPatch(typeof(ShipBuildModeManager))] public static class TVSyncStorageBlockPatch { private static MethodInfo _returnUnlockableFromStorageClientRpcMethod; private static bool IsTelevision(PlaceableShipObject shipObject) { if ((Object)(object)shipObject == (Object)null) { return false; } if ((Object)(object)((Component)shipObject).GetComponentInChildren(true) != (Object)null) { return true; } StartOfRound instance = StartOfRound.Instance; if (instance?.unlockablesList?.unlockables == null) { return false; } int unlockableID = shipObject.unlockableID; if (unlockableID < 0 || unlockableID >= instance.unlockablesList.unlockables.Count) { return false; } UnlockableItem val = instance.unlockablesList.unlockables[unlockableID]; if (val == null) { return false; } string text = val.unlockableName ?? string.Empty; return text.IndexOf("television", StringComparison.OrdinalIgnoreCase) >= 0 || text.Equals("TV", StringComparison.OrdinalIgnoreCase); } private static void TryReturnUnlockableToClient(int unlockableId, int playerWhoStored) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0073: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { return; } if ((object)_returnUnlockableFromStorageClientRpcMethod == null) { _returnUnlockableFromStorageClientRpcMethod = AccessTools.Method(typeof(StartOfRound), "ReturnUnlockableFromStorageClientRpc", (Type[])null, (Type[])null); } if (!(_returnUnlockableFromStorageClientRpcMethod == null)) { ClientRpcParams val = default(ClientRpcParams); val.Send = new ClientRpcSendParams { TargetClientIds = new ulong[1] { (ulong)playerWhoStored } }; ClientRpcParams val2 = val; ParameterInfo[] parameters = _returnUnlockableFromStorageClientRpcMethod.GetParameters(); if (parameters.Length == 1) { _returnUnlockableFromStorageClientRpcMethod.Invoke(instance, new object[1] { unlockableId }); } else if (parameters.Length >= 2) { _returnUnlockableFromStorageClientRpcMethod.Invoke(instance, new object[2] { unlockableId, val2 }); } } } [HarmonyPrefix] [HarmonyPatch("StoreObjectLocalClient")] public static bool PreventStoreHost(ShipBuildModeManager __instance) { if ((Object)(object)__instance == (Object)null || !((NetworkBehaviour)__instance).IsServer) { return true; } if (__instance.timeSincePlacingObject <= 0.25f || !__instance.InBuildMode || !Object.op_Implicit((Object)(object)__instance.placingObject)) { return true; } PlaceableShipObject placingObject = __instance.placingObject; if (!IsTelevision(placingObject)) { return true; } TVSyncPlugin.Log.LogInfo((object)"[CymruTVSync] Blocked host from storing the TV."); __instance.CancelBuildMode(false); AudioSource component = ((Component)placingObject).GetComponent(); if ((Object)(object)component != (Object)null && (Object)(object)placingObject.placeObjectSFX != (Object)null) { component.PlayOneShot(placingObject.placeObjectSFX); } return false; } [HarmonyPrefix] [HarmonyPatch("StoreObjectServerRpc")] public static bool PreventStoreServer(ShipBuildModeManager __instance, NetworkObjectReference objectRef, int playerWhoStored) { //IL_001f: 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) if ((Object)(object)__instance == (Object)null || !((NetworkBehaviour)__instance).IsServer) { return true; } NetworkObjectReference val = objectRef; NetworkObject val2 = default(NetworkObject); if (!((NetworkObjectReference)(ref val)).TryGet(ref val2, (NetworkManager)null) || (Object)(object)val2 == (Object)null) { return true; } PlaceableShipObject componentInChildren = ((Component)val2).GetComponentInChildren(); if (!IsTelevision(componentInChildren)) { return true; } TVSyncPlugin.Log.LogInfo((object)$"[CymruTVSync] Blocked client {playerWhoStored} from storing the TV."); if ((Object)(object)componentInChildren != (Object)null) { TryReturnUnlockableToClient(componentInChildren.unlockableID, playerWhoStored); } return false; } } [BepInPlugin("CymruTV.Sync", "CymruTV Sync", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class TVSyncPlugin : BaseUnityPlugin { internal static ManualLogSource Log; private readonly Harmony _harmony = new Harmony("CymruTV.Sync"); private void Awake() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; if ((Object)(object)Object.FindObjectOfType() == (Object)null) { GameObject val = new GameObject("CymruTVSyncHostLockOverlay"); Object.DontDestroyOnLoad((Object)(object)val); val.AddComponent(); } TVSyncNetworkHandler.EnsureInstance(); NetcodePatcher(); _harmony.PatchAll(typeof(TVSyncNetworkObjectManager)); _harmony.PatchAll(typeof(TVSyncPatches)); _harmony.PatchAll(typeof(TVSyncInputPatch)); _harmony.PatchAll(typeof(TVSyncBestestHooks)); _harmony.PatchAll(typeof(TVSyncReceiver)); _harmony.PatchAll(typeof(TVSyncLockKeyPatch)); _harmony.PatchAll(typeof(TVSyncStorageBlockPatch)); Log.LogInfo((object)"[CymruTVSync] Plugin loaded. Host keys: L = lock/unlock TV, O = enable/disable lock controls."); } private static void NetcodePatcher() { Type[] types = Assembly.GetExecutingAssembly().GetTypes(); Type[] array = types; foreach (Type type in array) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic); MethodInfo[] array2 = methods; foreach (MethodInfo methodInfo in array2) { object[] customAttributes = methodInfo.GetCustomAttributes(typeof(RuntimeInitializeOnLoadMethodAttribute), inherit: false); if (customAttributes.Length != 0) { methodInfo.Invoke(null, null); } } } } } public static class PluginInfo { public const string PLUGIN_GUID = "CymruTVSync"; public const string PLUGIN_NAME = "CymruTVSync"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace CymruTVSync.Patches { [HarmonyPatch(typeof(TVScript))] public class ExampleTVPatch { [HarmonyPatch("SwitchTVLocalClient")] [HarmonyPrefix] private static void SwitchTVPrefix(TVScript __instance) { StartOfRound.Instance.shipRoomLights.SetShipLightsBoolean(__instance.tvOn); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } } namespace __GEN { internal class NetworkVariableSerializationHelper { [RuntimeInitializeOnLoadMethod] internal static void InitializeSerialization() { } } } namespace CymruTVSync.NetcodePatcher { [AttributeUsage(AttributeTargets.Module)] internal class NetcodePatchedAssemblyAttribute : Attribute { } }