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 LethalTV v1.0.0
LethalTV.dll
Decompiled 4 hours agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using GameNetcodeStuff; using HarmonyLib; using LethalCompanyInputUtils.Api; using Microsoft.CodeAnalysis; using Unity.Collections; using Unity.Netcode; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.Video; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LethalTV")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LethalTV")] [assembly: AssemblyTitle("LethalTV")] [assembly: AssemblyVersion("1.0.0.0")] [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 LethalTV { [BepInPlugin("brant.LethalTV", "LethalTV", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string Guid = "brant.LethalTV"; public const string Name = "LethalTV"; public const string Version = "1.0.0"; internal static ManualLogSource Log; internal static PluginConfig Cfg; internal static TVRemoteInputs Inputs; private void Awake() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Cfg = new PluginConfig(((BaseUnityPlugin)this).Config); Inputs = new TVRemoteInputs(); new Harmony("brant.LethalTV").PatchAll(typeof(TVScriptPatches)); GameObject val = new GameObject("LethalTV") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); val.AddComponent<TVSyncManager>(); Log.LogInfo((object)("LethalTV 1.0.0 loaded. Host video folder: " + VideoLibrary.Folder)); } } internal sealed class PluginConfig { public readonly ConfigEntry<string> VideoFolder; public readonly ConfigEntry<bool> Shuffle; public readonly ConfigEntry<bool> AllowClientControl; public readonly ConfigEntry<int> MaxUploadKBps; public readonly ConfigEntry<int> TransferWindowKB; public readonly ConfigEntry<float> Volume; public readonly ConfigEntry<bool> Muted; public readonly ConfigEntry<float> VolumeStep; public readonly ConfigEntry<float> RemoteRange; public readonly ConfigEntry<bool> ShowHud; public readonly ConfigEntry<bool> DownloadAllVideos; public readonly ConfigEntry<int> MaxCacheMB; public readonly ConfigEntry<int> MaxVideoMB; public PluginConfig(ConfigFile cfg) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Expected O, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown VideoFolder = cfg.Bind<string>("Host", "VideoFolder", "", "Folder the host's videos are loaded from. Leave empty to use <your Videos folder>\\LethalTV. Best compatibility: H.264 video + AAC audio in .mp4. Also tried: .webm .mov .m4v .wmv .avi .mpg .mpeg .ogv"); Shuffle = cfg.Bind<bool>("Host", "Shuffle", false, "Shuffle the playlist order each time you host. Otherwise videos play in file name order."); AllowClientControl = cfg.Bind<bool>("Host", "AllowClientControl", true, "Let other players change videos with the TV remote. Anyone can still turn the TV on/off, like vanilla."); MaxUploadKBps = cfg.Bind<int>("Host", "MaxUploadKBps", 8192, "Upper limit on how fast the host sends videos to players who don't have them yet (KB/s, shared by everyone). 0 = no limit."); TransferWindowKB = cfg.Bind<int>("Host", "TransferWindowKB", 256, "How much unacknowledged video data may be in flight per player over Steam. Higher can be faster on high-ping connections; too high can cause resends."); Volume = cfg.Bind<float>("Player", "Volume", 1f, new ConfigDescription("Your personal TV volume (only affects what you hear).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>())); Muted = cfg.Bind<bool>("Player", "Muted", false, "Your personal TV mute."); VolumeStep = cfg.Bind<float>("Player", "VolumeStep", 0.1f, new ConfigDescription("How much one volume button press changes the volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 0.5f), Array.Empty<object>())); RemoteRange = cfg.Bind<float>("Player", "RemoteRange", 20f, "How close to the TV (in meters) you need to be to use the remote when you're not inside the ship."); ShowHud = cfg.Bind<bool>("Player", "ShowHud", true, "Show the on-screen TV banner when you use the remote or the video changes."); DownloadAllVideos = cfg.Bind<bool>("Player", "DownloadAllVideos", true, "Download every one of the host's videos in the background. If false, only the current and next video are downloaded."); MaxCacheMB = cfg.Bind<int>("Player", "MaxCacheMB", 8192, "Downloaded videos are kept so you don't redownload them next time. Oldest unused ones are deleted past this size."); MaxVideoMB = cfg.Bind<int>("Player", "MaxVideoMB", 4096, "Refuse to download any single video larger than this."); } } internal sealed class TVController : MonoBehaviour { private static readonly FieldRef<TVScript, bool> WasTvOnLastFrame = AccessTools.FieldRefAccess<TVScript, bool>("wasTvOnLastFrame"); private static readonly FieldRef<TVScript, int> VanillaClip = AccessTools.FieldRefAccess<TVScript, int>("currentClip"); private float baseVolume; private VideoAudioOutputMode origAudioMode; private VideoAspectRatio origAspect; private bool origLooping; private bool customMode; private bool screenOn; private string loadedId; private int loadedSeq = -1; private bool preparing; private float nextDriftCheck; private string failedId; private InteractTrigger trigger; private string baseHoverTip; private float nextTipUpdate; public static TVController Current { get; private set; } public TVScript Script { get; private set; } public static void Attach(TVScript tv) { TVController tVController = ((Component)tv).GetComponent<TVController>(); if ((Object)(object)tVController == (Object)null) { tVController = ((Component)tv).gameObject.AddComponent<TVController>(); } tVController.Init(tv); } private void Init(TVScript tv) { //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_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0077: Expected O, but got Unknown Current = this; if (!((Object)(object)Script != (Object)null)) { Script = tv; baseVolume = tv.tvSFX.volume; origAudioMode = tv.video.audioOutputMode; origAspect = tv.video.aspectRatio; origLooping = tv.video.isLooping; tv.video.errorReceived += new ErrorEventHandler(OnVideoError); FindTrigger(); } } private void OnEnable() { if ((Object)(object)Script != (Object)null) { Current = this; } } private void OnDisable() { if ((Object)(object)Current == (Object)(object)this) { Current = null; } } private void OnDestroy() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown if ((Object)(object)Script != (Object)null && (Object)(object)Script.video != (Object)null) { Script.video.errorReceived -= new ErrorEventHandler(OnVideoError); } } private void Update() { if (!((Object)(object)Script == (Object)null)) { PluginConfig cfg = Plugin.Cfg; Script.tvSFX.volume = baseVolume * (cfg.Muted.Value ? 0f : cfg.Volume.Value); TVSyncManager instance = TVSyncManager.Instance; if ((Object)(object)instance != (Object)null && instance.CustomActive) { CustomUpdate(instance); } else if (customMode) { ExitCustomMode(); } UpdateHoverTip(instance); } } private void CustomUpdate(TVSyncManager mgr) { if (!customMode) { EnterCustomMode(); } PlayerControllerB val = (((Object)(object)GameNetworkManager.Instance != (Object)null) ? GameNetworkManager.Instance.localPlayerController : null); VideoEntry currentEntry = mgr.CurrentEntry; bool flag = currentEntry != null && currentEntry.Path != null && currentEntry.Id != failedId; bool num = Script.tvOn && flag && (Object)(object)val != (Object)null && !val.isInsideFactory; VideoPlayer video = Script.video; if (!num) { if (loadedId != null || screenOn) { video.Stop(); loadedId = null; preparing = false; SetScreen(on: false); } } else if (loadedId != currentEntry.Id) { loadedId = currentEntry.Id; preparing = true; SetScreen(on: false); video.Stop(); video.url = VideoLibrary.ToUrl(currentEntry.Path); video.Prepare(); } else if (preparing) { if (video.isPrepared) { preparing = false; SyncTo(mgr, currentEntry, force: true); SetScreen(on: true); } } else if (loadedSeq != mgr.Seq) { SyncTo(mgr, currentEntry, force: true); } else if (Time.unscaledTime >= nextDriftCheck) { SyncTo(mgr, currentEntry, force: false); } } private void SyncTo(TVSyncManager mgr, VideoEntry entry, bool force) { nextDriftCheck = Time.unscaledTime + 2f; loadedSeq = mgr.Seq; VideoPlayer video = Script.video; double elapsed = mgr.Elapsed; double num = ((video.length > 0.0) ? video.length : entry.Duration); if (!(elapsed >= num - 0.25)) { if (force || Math.Abs(video.time - elapsed) > 1.0) { video.time = elapsed; } if (!video.isPlaying) { video.Play(); } } } private void EnterCustomMode() { customMode = true; VideoPlayer video = Script.video; Script.tvSFX.Stop(); video.Stop(); video.source = (VideoSource)1; video.isLooping = false; video.playOnAwake = false; video.waitForFirstFrame = true; video.skipOnDrop = true; video.aspectRatio = (VideoAspectRatio)3; video.audioOutputMode = (VideoAudioOutputMode)1; video.controlledAudioTrackCount = 1; video.EnableAudioTrack((ushort)0, true); video.SetTargetAudioSource((ushort)0, Script.tvSFX); WasTvOnLastFrame.Invoke(Script) = false; loadedId = null; preparing = false; failedId = null; SetScreen(on: false); } private void ExitCustomMode() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) customMode = false; VideoPlayer video = Script.video; video.Stop(); video.source = (VideoSource)0; video.audioOutputMode = origAudioMode; video.aspectRatio = origAspect; video.isLooping = origLooping; loadedId = null; preparing = false; SetScreen(on: false); WasTvOnLastFrame.Invoke(Script) = false; int num = VanillaClip.Invoke(Script); if (Script.tvOn && num >= 0 && num < Script.tvAudioClips.Length) { Script.tvSFX.clip = Script.tvAudioClips[num]; Script.tvSFX.Play(); } } private void SetScreen(bool on) { screenOn = on; Material[] sharedMaterials = ((Renderer)Script.tvMesh).sharedMaterials; if (sharedMaterials.Length > 1) { sharedMaterials[1] = (on ? Script.tvOnMaterial : Script.tvOffMaterial); ((Renderer)Script.tvMesh).sharedMaterials = sharedMaterials; } if ((Object)(object)Script.tvLight != (Object)null) { ((Behaviour)Script.tvLight).enabled = on; } } private void OnVideoError(VideoPlayer source, string message) { if (customMode) { Plugin.Log.LogError((object)("TV couldn't play video: " + message)); failedId = loadedId; } } private void FindTrigger() { NetworkObject componentInParent = ((Component)Script).GetComponentInParent<NetworkObject>(); InteractTrigger[] componentsInChildren = ((Component)(((Object)(object)componentInParent != (Object)null) ? ((Component)componentInParent).transform : ((Component)Script).transform.root)).GetComponentsInChildren<InteractTrigger>(true); foreach (InteractTrigger val in componentsInChildren) { for (int j = 0; j < ((UnityEventBase)val.onInteract).GetPersistentEventCount(); j++) { if (((UnityEventBase)val.onInteract).GetPersistentTarget(j) == (Object)(object)Script) { trigger = val; baseHoverTip = val.hoverTip; return; } } } } private void UpdateHoverTip(TVSyncManager mgr) { if (!((Object)(object)trigger == (Object)null) && !(Time.unscaledTime < nextTipUpdate)) { nextTipUpdate = Time.unscaledTime + 0.25f; string text = (((Object)(object)mgr != (Object)null) ? mgr.TvStatusLine() : null); string text2 = ((text == null) ? baseHoverTip : (baseHoverTip + "\n" + text)); if (trigger.hoverTip != text2) { trigger.hoverTip = text2; } } } } public class TVRemoteInputs : LcInputActions { [InputAction("<Keyboard>/backslash", Name = "TV Remote: Power")] public InputAction Power { get; set; } [InputAction("<Keyboard>/rightBracket", Name = "TV Remote: Next Video")] public InputAction Next { get; set; } [InputAction("<Keyboard>/leftBracket", Name = "TV Remote: Previous Video")] public InputAction Previous { get; set; } [InputAction("<Keyboard>/equals", Name = "TV Remote: Volume Up")] public InputAction VolumeUp { get; set; } [InputAction("<Keyboard>/minus", Name = "TV Remote: Volume Down")] public InputAction VolumeDown { get; set; } [InputAction("<Keyboard>/quote", Name = "TV Remote: Mute")] public InputAction Mute { get; set; } } [HarmonyPatch(typeof(TVScript))] internal static class TVScriptPatches { private static bool CustomActive { get { if ((Object)(object)TVSyncManager.Instance != (Object)null) { return TVSyncManager.Instance.CustomActive; } return false; } } [HarmonyPatch("OnEnable")] [HarmonyPostfix] private static void OnEnablePostfix(TVScript __instance) { TVController.Attach(__instance); } [HarmonyPatch("Update")] [HarmonyPrefix] private static bool UpdatePrefix() { return !CustomActive; } [HarmonyPatch("TVFinishedClip")] [HarmonyPrefix] private static bool TVFinishedClipPrefix() { return !CustomActive; } [HarmonyPatch("TurnTVOnOff")] [HarmonyPrefix] private static bool TurnTVOnOffPrefix(TVScript __instance, bool on) { if (!CustomActive) { return true; } __instance.tvOn = on; AudioClip val = (on ? __instance.switchTVOn : __instance.switchTVOff); __instance.tvSFX.PlayOneShot(val); WalkieTalkie.TransmitOneShotAudio(__instance.tvSFX, val, 1f); return false; } } internal enum RemoteCommand : byte { Next = 1, Previous } internal sealed class TVSyncManager : MonoBehaviour { private delegate void Writer(ref FastBufferWriter w); private sealed class Upload { public VideoEntry Entry; public FileStream Stream; public long Sent; public long Acked; public float LastProgress; } private sealed class Download { public VideoEntry Entry; public FileStream Stream; public long Received; public float LastData; public float LastRequest; } private const string MsgHello = "LethalTV.Hello"; private const string MsgManifest = "LethalTV.Manifest"; private const string MsgState = "LethalTV.State"; private const string MsgCommand = "LethalTV.Command"; private const string MsgRequestFile = "LethalTV.RequestFile"; private const string MsgChunk = "LethalTV.Chunk"; private const string MsgAck = "LethalTV.Ack"; private const NetworkDelivery Delivery = (NetworkDelivery)4; private const int MaxMessageSize = 1048576; private const int MaxPlaylist = 2000; private NetworkManager net; private CustomMessagingManager messaging; private bool isServer; private bool manifestReceived; private bool clientControlAllowed = true; public readonly List<VideoEntry> Playlist = new List<VideoEntry>(); private readonly Dictionary<ulong, Upload> uploads = new Dictionary<ulong, Upload>(); private readonly Dictionary<ulong, float> lastCommandTime = new Dictionary<ulong, float>(); private int chunkSize = 32768; private int windowBytes = 262144; private byte[] sendBuffer = new byte[32768]; private float uploadAllowance; private Download download; private Task<string> verifyTask; private VideoEntry verifyEntry; private string verifyPath; private readonly Dictionary<string, int> verifyFailures = new Dictionary<string, int>(); private byte[] recvBuffer = new byte[65536]; private float hudUntil; private string hudNote; private GUIStyle hudStyle; private GUIStyle hudShadowStyle; public static TVSyncManager Instance { get; private set; } public int CurrentIndex { get; private set; } public double ClipStart { get; private set; } public int Seq { get; private set; } public bool InSession => messaging != null; public bool CustomActive { get { if (InSession && Playlist.Count > 0) { if (!isServer) { return manifestReceived; } return true; } return false; } } public VideoEntry CurrentEntry { get { if (!CustomActive || CurrentIndex < 0 || CurrentIndex >= Playlist.Count) { return null; } return Playlist[CurrentIndex]; } } public double Now { get { //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)net != (Object)null)) { return 0.0; } NetworkTime serverTime = net.ServerTime; return ((NetworkTime)(ref serverTime)).Time; } } public double Elapsed => Math.Max(0.0, Now - ClipStart); private static PlayerControllerB LocalPlayer { get { if (!((Object)(object)GameNetworkManager.Instance != (Object)null)) { return null; } return GameNetworkManager.Instance.localPlayerController; } } private void Awake() { Instance = this; VideoLibrary.Changed += OnLibraryChanged; ((MonoBehaviour)this).StartCoroutine(VideoLibrary.Scan()); } private void OnDestroy() { VideoLibrary.Changed -= OnLibraryChanged; EndSession(); } private void Update() { TrackSession(); if (InSession) { try { if (isServer) { AdvanceTimeline(); PumpUploads(); } else { PumpDownloads(); } } catch (Exception ex) { Plugin.Log.LogError((object)ex); } } HandleRemote(); } private void TrackSession() { NetworkManager singleton = NetworkManager.Singleton; if (!((Object)(object)singleton != (Object)null) || !singleton.IsListening || singleton.ShutdownInProgress || (!singleton.IsServer && !singleton.IsConnectedClient) || singleton.CustomMessagingManager == null) { if (InSession) { EndSession(); } } else if (messaging != singleton.CustomMessagingManager) { if (InSession) { EndSession(); } BeginSession(singleton); } } private void BeginSession(NetworkManager nm) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Expected O, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown net = nm; isServer = nm.IsServer; messaging = nm.CustomMessagingManager; messaging.RegisterNamedMessageHandler("LethalTV.Hello", new HandleNamedMessageDelegate(OnHello)); messaging.RegisterNamedMessageHandler("LethalTV.Manifest", new HandleNamedMessageDelegate(OnManifest)); messaging.RegisterNamedMessageHandler("LethalTV.State", new HandleNamedMessageDelegate(OnState)); messaging.RegisterNamedMessageHandler("LethalTV.Command", new HandleNamedMessageDelegate(OnCommand)); messaging.RegisterNamedMessageHandler("LethalTV.RequestFile", new HandleNamedMessageDelegate(OnRequestFile)); messaging.RegisterNamedMessageHandler("LethalTV.Chunk", new HandleNamedMessageDelegate(OnChunk)); messaging.RegisterNamedMessageHandler("LethalTV.Ack", new HandleNamedMessageDelegate(OnAck)); ConfigureTransfer(nm); TuneSteamNetworking(nm); Playlist.Clear(); manifestReceived = false; CurrentIndex = 0; ClipStart = Now; Seq = 0; if (isServer) { nm.OnClientDisconnectCallback += OnClientDisconnected; clientControlAllowed = Plugin.Cfg.AllowClientControl.Value; RebuildPlaylist(broadcast: false); ((MonoBehaviour)this).StartCoroutine(VideoLibrary.Scan()); Plugin.Log.LogInfo((object)$"Hosting with {Playlist.Count} custom video(s)."); } else { SendToServer("LethalTV.Hello", 8, delegate { }); } } private void EndSession() { if (messaging != null) { try { string[] array = new string[7] { "LethalTV.Hello", "LethalTV.Manifest", "LethalTV.State", "LethalTV.Command", "LethalTV.RequestFile", "LethalTV.Chunk", "LethalTV.Ack" }; foreach (string text in array) { messaging.UnregisterNamedMessageHandler(text); } } catch (Exception) { } } if ((Object)(object)net != (Object)null && isServer) { try { net.OnClientDisconnectCallback -= OnClientDisconnected; } catch (Exception) { } } foreach (Upload value in uploads.Values) { value.Stream.Dispose(); } uploads.Clear(); lastCommandTime.Clear(); CloseDownload(sendStop: false); verifyTask = null; verifyEntry = null; Playlist.Clear(); manifestReceived = false; messaging = null; net = null; } private void OnClientDisconnected(ulong clientId) { CloseUpload(clientId); lastCommandTime.Remove(clientId); } private void ConfigureTransfer(NetworkManager nm) { NetworkTransport val = nm.NetworkConfig?.NetworkTransport; if ((Object)(object)val != (Object)null && ((object)val).GetType().Name == "UnityTransport") { int num = 6144; try { PropertyInfo property = ((object)val).GetType().GetProperty("MaxPayloadSize"); if (property != null) { num = (int)property.GetValue(val); } } catch (Exception) { } chunkSize = Mathf.Clamp(num - 512, 1024, 16384); windowBytes = chunkSize * 4; } else { chunkSize = 32768; windowBytes = Math.Max(chunkSize, Plugin.Cfg.TransferWindowKB.Value * 1024); } sendBuffer = new byte[chunkSize]; } private void TuneSteamNetworking(NetworkManager nm) { try { if (((object)nm.NetworkConfig?.NetworkTransport)?.GetType().Name != "FacepunchTransport") { return; } Type type = Type.GetType("Steamworks.SteamNetworkingUtils, Facepunch.Steamworks.Win64"); Type configEnum = type?.Assembly.GetType("Steamworks.NetConfig"); MethodInfo get = type?.GetMethod("GetConfigInt", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo set = type?.GetMethod("SetConfigInt", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (!(configEnum == null) && !(get == null) && !(set == null)) { int value = Plugin.Cfg.MaxUploadKBps.Value; int num = ((value > 0) ? (Math.Max(value, 1024) * 1024) : 0); int num2 = Get("SendRateMax"); if (num2 != 0 && (num == 0 || num2 < num)) { Set("SendRateMax", num); } int num3 = Math.Max(windowBytes * 4, 2097152); if (Get("SendBufferSize") < num3) { Set("SendBufferSize", num3); } Plugin.Log.LogInfo((object)string.Format("Steam networking: SendRateMax={0} SendBufferSize={1}", Get("SendRateMax"), Get("SendBufferSize"))); } int Get(string key) { return (int)get.Invoke(null, new object[1] { Enum.Parse(configEnum, key) }); } void Set(string key, int num4) { set.Invoke(null, new object[2] { Enum.Parse(configEnum, key), num4 }); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Couldn't tune Steam networking (video transfers may be slower): " + ex.Message)); } } private void Send(string name, ulong clientId, int initialSize, Writer write) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) FastBufferWriter w = default(FastBufferWriter); ((FastBufferWriter)(ref w))..ctor(initialSize, (Allocator)2, 1048576); try { write(ref w); messaging.SendNamedMessage(name, clientId, w, (NetworkDelivery)4); } finally { ((FastBufferWriter)(ref w)).Dispose(); } } private void SendToServer(string name, int initialSize, Writer write) { Send(name, 0uL, initialSize, write); } private void Broadcast(string name, int initialSize, Writer write) { //IL_0049: Unknown result type (might be due to invalid IL or missing references) List<ulong> list = net.ConnectedClientsIds.Where((ulong id) => id != net.LocalClientId).ToList(); if (list.Count == 0) { return; } FastBufferWriter w = default(FastBufferWriter); ((FastBufferWriter)(ref w))..ctor(initialSize, (Allocator)2, 1048576); try { write(ref w); messaging.SendNamedMessage(name, (IReadOnlyList<ulong>)list, w, (NetworkDelivery)4); } finally { ((FastBufferWriter)(ref w)).Dispose(); } } private void OnLibraryChanged() { if (InSession && isServer) { RebuildPlaylist(broadcast: true); } } private void RebuildPlaylist(bool broadcast) { string currentId = ((CurrentIndex >= 0 && CurrentIndex < Playlist.Count) ? Playlist[CurrentIndex].Id : null); List<VideoEntry> list = VideoLibrary.Ready.ToList(); if (Plugin.Cfg.Shuffle.Value) { Random random = new Random(); for (int num = list.Count - 1; num > 0; num--) { int num2 = random.Next(num + 1); List<VideoEntry> list2 = list; int index = num; int index2 = num2; VideoEntry value = list[num2]; VideoEntry value2 = list[num]; list2[index] = value; list[index2] = value2; } } Playlist.Clear(); Playlist.AddRange(list); int num3 = ((currentId != null) ? Playlist.FindIndex((VideoEntry e) => e.Id == currentId) : (-1)); if (num3 >= 0) { CurrentIndex = num3; } else { CurrentIndex = 0; ClipStart = Now; } Seq++; if (broadcast) { Broadcast("LethalTV.Manifest", 1024, WriteManifest); Broadcast("LethalTV.State", 64, WriteState); Plugin.Log.LogInfo((object)$"Playlist updated: {Playlist.Count} video(s)."); } } private void AdvanceTimeline() { if (Playlist.Count == 0) { return; } if (CurrentIndex >= Playlist.Count) { CurrentIndex = 0; } bool flag = false; for (int i = 0; i <= Playlist.Count; i++) { if (!(Now - ClipStart >= Playlist[CurrentIndex].Duration)) { break; } ClipStart += Playlist[CurrentIndex].Duration; CurrentIndex = (CurrentIndex + 1) % Playlist.Count; flag = true; } if (Now - ClipStart >= Playlist[CurrentIndex].Duration) { ClipStart = Now; } if (flag) { Seq++; Broadcast("LethalTV.State", 64, WriteState); NotifyChannelChanged(); } } private void ApplyCommand(RemoteCommand cmd) { if (Playlist.Count != 0) { int count = Playlist.Count; switch (cmd) { default: return; case RemoteCommand.Next: CurrentIndex = (CurrentIndex + 1) % count; break; case RemoteCommand.Previous: CurrentIndex = (CurrentIndex - 1 + count) % count; break; } ClipStart = Now; Seq++; Broadcast("LethalTV.State", 64, WriteState); NotifyChannelChanged(); } } private void WriteManifest(ref FastBufferWriter w) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: 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) //IL_0093: Unknown result type (might be due to invalid IL or missing references) ((FastBufferWriter)(ref w)).WriteValueSafe<bool>(ref clientControlAllowed, default(ForPrimitives)); int count = Playlist.Count; ((FastBufferWriter)(ref w)).WriteValueSafe<int>(ref count, default(ForPrimitives)); foreach (VideoEntry item in Playlist) { ((FastBufferWriter)(ref w)).WriteValueSafe(item.Id, true); ((FastBufferWriter)(ref w)).WriteValueSafe(item.Name, false); ((FastBufferWriter)(ref w)).WriteValueSafe(item.Ext, true); ((FastBufferWriter)(ref w)).WriteValueSafe<long>(ref item.Size, default(ForPrimitives)); ((FastBufferWriter)(ref w)).WriteValueSafe<double>(ref item.Duration, default(ForPrimitives)); } } private void WriteState(ref FastBufferWriter w) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_0042: Unknown result type (might be due to invalid IL or missing references) int currentIndex = CurrentIndex; ((FastBufferWriter)(ref w)).WriteValueSafe<int>(ref currentIndex, default(ForPrimitives)); double clipStart = ClipStart; ((FastBufferWriter)(ref w)).WriteValueSafe<double>(ref clipStart, default(ForPrimitives)); currentIndex = Seq; ((FastBufferWriter)(ref w)).WriteValueSafe<int>(ref currentIndex, default(ForPrimitives)); } private void OnHello(ulong sender, FastBufferReader reader) { if (isServer) { Send("LethalTV.Manifest", sender, 1024, WriteManifest); Send("LethalTV.State", sender, 64, WriteState); } } private void OnCommand(ulong sender, FastBufferReader reader) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (isServer) { byte cmd = default(byte); ((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref cmd, default(ForPrimitives)); if (clientControlAllowed && (!lastCommandTime.TryGetValue(sender, out var value) || !(Time.unscaledTime - value < 0.2f))) { lastCommandTime[sender] = Time.unscaledTime; ApplyCommand((RemoteCommand)cmd); } } } private void OnRequestFile(ulong sender, FastBufferReader reader) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) if (!isServer) { return; } string id = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref id, true); long val = default(long); ((FastBufferReader)(ref reader)).ReadValueSafe<long>(ref val, default(ForPrimitives)); CloseUpload(sender); if (string.IsNullOrEmpty(id)) { return; } VideoEntry videoEntry = Playlist.Find((VideoEntry e) => e.Id == id); if (videoEntry == null || videoEntry.Path == null) { return; } try { FileStream stream = new FileStream(videoEntry.Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536); val = Math.Max(0L, Math.Min(val, videoEntry.Size)); uploads[sender] = new Upload { Entry = videoEntry, Stream = stream, Sent = val, Acked = val, LastProgress = Time.unscaledTime }; Plugin.Log.LogInfo((object)$"Sending '{videoEntry.Name}' to client {sender} from {val / 1024} KB"); } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't open '" + videoEntry.Path + "' to send: " + ex.Message)); } } private void OnAck(ulong sender, FastBufferReader reader) { //IL_0019: 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) if (!isServer) { return; } string text = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, true); long num = default(long); ((FastBufferReader)(ref reader)).ReadValueSafe<long>(ref num, default(ForPrimitives)); if (uploads.TryGetValue(sender, out var value) && !(value.Entry.Id != text)) { if (num > value.Acked && num <= value.Sent) { value.Acked = num; value.LastProgress = Time.unscaledTime; } if (value.Acked >= value.Entry.Size) { Plugin.Log.LogInfo((object)$"Client {sender} finished receiving '{value.Entry.Name}'"); CloseUpload(sender); } } } private void CloseUpload(ulong clientId) { if (uploads.TryGetValue(clientId, out var value)) { value.Stream.Dispose(); uploads.Remove(clientId); } } private void PumpUploads() { if (uploads.Count == 0) { uploadAllowance = 0f; return; } int value = Plugin.Cfg.MaxUploadKBps.Value; if (value > 0) { float num = (float)value * 1024f; uploadAllowance = Mathf.Min(uploadAllowance + num * Time.unscaledDeltaTime, num * 0.25f + (float)chunkSize); } else { uploadAllowance = float.MaxValue; } foreach (KeyValuePair<ulong, Upload> item in uploads.ToList()) { Upload value2 = item.Value; if (value2.Sent > value2.Acked && Time.unscaledTime - value2.LastProgress > 10f) { Plugin.Log.LogWarning((object)$"Transfer of '{value2.Entry.Name}' to client {item.Key} stalled, resending from {value2.Acked / 1024} KB"); value2.Sent = value2.Acked; value2.LastProgress = Time.unscaledTime; } while (value2.Sent < value2.Entry.Size && value2.Sent - value2.Acked < windowBytes && uploadAllowance > 0f) { int count = (int)Math.Min(chunkSize, value2.Entry.Size - value2.Sent); int read; try { value2.Stream.Position = value2.Sent; read = ReadFully(value2.Stream, sendBuffer, count); } catch (Exception ex) { Plugin.Log.LogError((object)("Error reading '" + value2.Entry.Path + "': " + ex.Message)); read = 0; } if (read <= 0) { CloseUpload(item.Key); break; } string id = value2.Entry.Id; long offset = value2.Sent; Send("LethalTV.Chunk", item.Key, read + 256, delegate(ref FastBufferWriter w) { //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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) ((FastBufferWriter)(ref w)).WriteValueSafe(id, true); ((FastBufferWriter)(ref w)).WriteValueSafe<long>(ref offset, default(ForPrimitives)); ((FastBufferWriter)(ref w)).WriteValueSafe<int>(ref read, default(ForPrimitives)); ((FastBufferWriter)(ref w)).WriteBytesSafe(sendBuffer, read, 0); }); value2.Sent += read; uploadAllowance -= read; } } } private static int ReadFully(Stream stream, byte[] buffer, int count) { int i; int num; for (i = 0; i < count; i += num) { num = stream.Read(buffer, i, count - i); if (num <= 0) { break; } } return i; } private void OnManifest(ulong sender, FastBufferReader reader) { //IL_0010: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) if (isServer) { return; } try { bool flag = default(bool); ((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref flag, default(ForPrimitives)); int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); if (num < 0 || num > 2000) { throw new InvalidDataException($"bad playlist size {num}"); } long num2 = (long)Plugin.Cfg.MaxVideoMB.Value * 1024L * 1024; List<VideoEntry> list = new List<VideoEntry>(num); string id = default(string); string text = default(string); string ext = default(string); long num3 = default(long); double num4 = default(double); for (int i = 0; i < num; i++) { ((FastBufferReader)(ref reader)).ReadValueSafe(ref id, true); ((FastBufferReader)(ref reader)).ReadValueSafe(ref text, false); ((FastBufferReader)(ref reader)).ReadValueSafe(ref ext, true); ((FastBufferReader)(ref reader)).ReadValueSafe<long>(ref num3, default(ForPrimitives)); ((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref num4, default(ForPrimitives)); VideoEntry videoEntry = new VideoEntry { Id = id, Name = (text ?? "video"), Ext = ext, Size = num3, Duration = num4 }; if (!VideoCache.IsValidId(id) || !VideoLibrary.IsSupportedExtension(ext) || num3 <= 0 || !(num4 > 0.0)) { videoEntry.Invalid = "Invalid video"; } else if (num3 > num2) { videoEntry.Invalid = "Video too large (see MaxVideoMB)"; } else { videoEntry.Path = VideoCache.FindComplete(videoEntry); } list.Add(videoEntry); } clientControlAllowed = flag; Playlist.Clear(); Playlist.AddRange(list); manifestReceived = true; if (download != null) { VideoEntry videoEntry2 = Playlist.Find((VideoEntry e) => e.Id == download.Entry.Id && e.Invalid == null); if (videoEntry2 == null) { CloseDownload(sendStop: true); } else { download.Entry = videoEntry2; } } int num5 = Playlist.Count((VideoEntry e) => e.Path != null); Plugin.Log.LogInfo((object)$"Host playlist: {Playlist.Count} video(s), {num5} already cached."); } catch (Exception ex) { Plugin.Log.LogError((object)("Bad playlist from host: " + ex.Message)); } } private void OnState(ulong sender, FastBufferReader reader) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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) if (!isServer) { int num = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives)); double clipStart = default(double); ((FastBufferReader)(ref reader)).ReadValueSafe<double>(ref clipStart, default(ForPrimitives)); int num2 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num2, default(ForPrimitives)); bool num3 = num != CurrentIndex || num2 != Seq; CurrentIndex = num; ClipStart = clipStart; Seq = num2; if (num3 && CustomActive) { NotifyChannelChanged(); } } } private void OnChunk(ulong sender, FastBufferReader reader) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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) if (isServer) { return; } string id = default(string); ((FastBufferReader)(ref reader)).ReadValueSafe(ref id, true); long num = default(long); ((FastBufferReader)(ref reader)).ReadValueSafe<long>(ref num, default(ForPrimitives)); int num2 = default(int); ((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num2, default(ForPrimitives)); if (num2 <= 0 || num2 > 1048576) { return; } if (recvBuffer.Length < num2) { recvBuffer = new byte[num2]; } ((FastBufferReader)(ref reader)).ReadBytesSafe(ref recvBuffer, num2, 0); Download download = this.download; if (download == null || download.Entry.Id != id || num < download.Received) { return; } if (num > download.Received) { if (Time.unscaledTime - download.LastRequest > 2f) { RequestFrom(download); } return; } if (num + num2 > download.Entry.Size) { Plugin.Log.LogError((object)("Host sent too much data for '" + download.Entry.Name + "', discarding download.")); FailDownload(download); return; } download.Stream.Write(recvBuffer, 0, num2); download.Received += num2; download.LastData = Time.unscaledTime; long received = download.Received; SendToServer("LethalTV.Ack", 96, delegate(ref FastBufferWriter w) { //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) ((FastBufferWriter)(ref w)).WriteValueSafe(id, true); ((FastBufferWriter)(ref w)).WriteValueSafe<long>(ref received, default(ForPrimitives)); }); if (download.Received >= download.Entry.Size) { CompleteDownload(); } } private void PumpDownloads() { if (!manifestReceived) { return; } if (verifyTask != null) { if (!verifyTask.IsCompleted) { return; } FinishVerify(); } VideoEntry videoEntry = PickDownload(); if (this.download != null && this.download.Entry != videoEntry) { CloseDownload(videoEntry == null); } if (this.download == null && videoEntry != null) { StartDownload(videoEntry); } Download download = this.download; if (download != null && Time.unscaledTime - download.LastData > 15f && Time.unscaledTime - download.LastRequest > 15f) { Plugin.Log.LogWarning((object)("Download of '" + download.Entry.Name + "' stalled, asking host to resume.")); RequestFrom(download); } } private VideoEntry PickDownload() { int count = Playlist.Count; if (count == 0) { return null; } int num = ((CurrentIndex >= 0 && CurrentIndex < count) ? CurrentIndex : 0); int num2 = (Plugin.Cfg.DownloadAllVideos.Value ? count : Math.Min(2, count)); for (int i = 0; i < num2; i++) { VideoEntry videoEntry = Playlist[(num + i) % count]; if (videoEntry.Path == null && videoEntry.Invalid == null && (!verifyFailures.TryGetValue(videoEntry.Id, out var value) || value < 2)) { return videoEntry; } } return null; } private void StartDownload(VideoEntry e) { try { Directory.CreateDirectory(VideoCache.Dir); FileStream fileStream = new FileStream(VideoCache.PartPath(e), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); if (fileStream.Length > e.Size) { fileStream.SetLength(0L); } fileStream.Position = fileStream.Length; download = new Download { Entry = e, Stream = fileStream, Received = fileStream.Length, LastData = Time.unscaledTime }; } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't write to video cache: " + ex.Message)); verifyFailures[e.Id] = 99; return; } if (download.Received >= e.Size) { CompleteDownload(); return; } Plugin.Log.LogInfo((object)((download.Received > 0) ? $"Resuming download of '{e.Name}' at {download.Received * 100 / e.Size}%" : $"Downloading '{e.Name}' ({(double)e.Size / 1048576.0:0.0} MB) from host")); RequestFrom(download); } private void RequestFrom(Download d) { d.LastRequest = Time.unscaledTime; d.LastData = Time.unscaledTime; string id = d.Entry.Id; long offset = d.Received; SendToServer("LethalTV.RequestFile", 96, delegate(ref FastBufferWriter w) { //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) ((FastBufferWriter)(ref w)).WriteValueSafe(id, true); ((FastBufferWriter)(ref w)).WriteValueSafe<long>(ref offset, default(ForPrimitives)); }); } private void CloseDownload(bool sendStop) { if (download == null) { return; } try { download.Stream.Dispose(); } catch (Exception) { } download = null; if (sendStop && InSession) { SendToServer("LethalTV.RequestFile", 96, delegate(ref FastBufferWriter w) { //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) ((FastBufferWriter)(ref w)).WriteValueSafe("", true); long num = 0L; ((FastBufferWriter)(ref w)).WriteValueSafe<long>(ref num, default(ForPrimitives)); }); } } private void FailDownload(Download d) { CloseDownload(sendStop: true); try { File.Delete(VideoCache.PartPath(d.Entry)); } catch (Exception) { } verifyFailures.TryGetValue(d.Entry.Id, out var value); verifyFailures[d.Entry.Id] = value + 1; } private void CompleteDownload() { Download download = this.download; this.download = null; download.Stream.Flush(); download.Stream.Dispose(); verifyEntry = download.Entry; verifyPath = VideoCache.PartPath(download.Entry); string path = verifyPath; verifyTask = Task.Run(() => VideoLibrary.HashFile(path)); } private void FinishVerify() { VideoEntry videoEntry = verifyEntry; string text = ((verifyTask.Status == TaskStatus.RanToCompletion) ? verifyTask.Result : null); verifyTask = null; verifyEntry = null; try { if (text == videoEntry.Id) { string text2 = VideoCache.CompletePath(videoEntry); if (File.Exists(text2)) { File.Delete(text2); } File.Move(verifyPath, text2); foreach (VideoEntry item in Playlist) { if (item.Id == videoEntry.Id) { item.Path = text2; } } Plugin.Log.LogInfo((object)("Downloaded '" + videoEntry.Name + "'")); VideoCache.Prune(Playlist.Select((VideoEntry p) => p.Id), (long)Plugin.Cfg.MaxCacheMB.Value * 1024L * 1024); } else { File.Delete(verifyPath); verifyFailures.TryGetValue(videoEntry.Id, out var value); verifyFailures[videoEntry.Id] = value + 1; Plugin.Log.LogWarning((object)("Download of '" + videoEntry.Name + "' was corrupted, retrying.")); } } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't finish download of '" + videoEntry.Name + "': " + ex.Message)); verifyFailures[videoEntry.Id] = 99; } } private static bool InRemoteRange(PlayerControllerB player, TVController tv) { //IL_0032: 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) if ((Object)(object)player == (Object)null || (Object)(object)tv == (Object)null || !((Behaviour)tv).isActiveAndEnabled) { return false; } if (!player.isPlayerDead && !player.isInHangarShipRoom) { return Vector3.Distance(((Component)player).transform.position, ((Component)tv).transform.position) <= Plugin.Cfg.RemoteRange.Value; } return true; } private void HandleRemote() { TVRemoteInputs inputs = Plugin.Inputs; PlayerControllerB localPlayer = LocalPlayer; if (inputs == null || (Object)(object)localPlayer == (Object)null || localPlayer.isTypingChat || localPlayer.inTerminalMenu || ((Object)(object)localPlayer.quickMenuManager != (Object)null && localPlayer.quickMenuManager.isMenuOpen)) { return; } bool flag = inputs.Power.WasPressedThisFrame(); bool flag2 = inputs.Next.WasPressedThisFrame(); bool flag3 = inputs.Previous.WasPressedThisFrame(); bool flag4 = inputs.VolumeUp.WasPressedThisFrame(); bool flag5 = inputs.VolumeDown.WasPressedThisFrame(); bool flag6 = inputs.Mute.WasPressedThisFrame(); if (!(flag || flag2 || flag3 || flag4 || flag5 || flag6)) { return; } TVController current = TVController.Current; if (!InRemoteRange(localPlayer, current)) { return; } PluginConfig cfg = Plugin.Cfg; if (flag4 || flag5) { float num = cfg.Volume.Value + (flag4 ? cfg.VolumeStep.Value : (0f - cfg.VolumeStep.Value)); cfg.Volume.Value = Mathf.Clamp01(Mathf.Round(num * 100f) / 100f); cfg.Muted.Value = false; ShowHud(); } if (flag6) { cfg.Muted.Value = !cfg.Muted.Value; ShowHud(); } if (flag && !localPlayer.isPlayerDead) { current.Script.SwitchTVLocalClient(); ShowHud(current.Script.tvOn ? "TV off" : "TV on"); } if ((flag2 || flag3) && !localPlayer.isPlayerDead) { if (!CustomActive) { ShowHud("The host has no custom videos"); } else if (!isServer && !clientControlAllowed) { ShowHud("The host has disabled changing videos"); } else { SendCommand(flag2 ? RemoteCommand.Next : RemoteCommand.Previous); } } } private void SendCommand(RemoteCommand cmd) { if (isServer) { ApplyCommand(cmd); return; } SendToServer("LethalTV.Command", 8, delegate(ref FastBufferWriter w) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) byte b = (byte)cmd; ((FastBufferWriter)(ref w)).WriteValueSafe<byte>(ref b, default(ForPrimitives)); }); } private void NotifyChannelChanged() { TVController current = TVController.Current; if ((Object)(object)current != (Object)null && current.Script.tvOn && InRemoteRange(LocalPlayer, current)) { ShowHud(); } } private void ShowHud(string note = null) { hudUntil = Time.unscaledTime + 3f; hudNote = note; } public string TvStatusLine() { VideoEntry currentEntry = CurrentEntry; if (currentEntry == null) { return null; } if (currentEntry.Invalid != null) { return currentEntry.Invalid; } if (currentEntry.Path != null) { return "Playing: " + Shorten(currentEntry.Name, 32); } if (download != null && download.Entry.Id == currentEntry.Id) { return $"Downloading video... {download.Received * 100 / Math.Max(1L, currentEntry.Size)}%"; } if (verifyEntry != null && verifyEntry.Id == currentEntry.Id) { return "Finishing download..."; } return "Waiting for video..."; } private static string Shorten(string s, int max) { if (s.Length > max) { return s.Substring(0, max - 3) + "..."; } return s; } private static string Bind(InputAction action) { try { string bindingDisplayString = InputActionRebindingExtensions.GetBindingDisplayString(action, (DisplayStringOptions)0, (string)null); return string.IsNullOrEmpty(bindingDisplayString) ? "?" : bindingDisplayString; } catch (Exception) { return "?"; } } private void OnGUI() { //IL_0042: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) if (Plugin.Cfg.ShowHud.Value && !(Time.unscaledTime > hudUntil) && !((Object)(object)LocalPlayer == (Object)null)) { if (hudStyle == null) { hudStyle = new GUIStyle(GUI.skin.label) { alignment = (TextAnchor)1, fontStyle = (FontStyle)1, wordWrap = false }; hudStyle.normal.textColor = new Color(1f, 0.55f, 0.15f); hudShadowStyle = new GUIStyle(hudStyle); hudShadowStyle.normal.textColor = new Color(0f, 0f, 0f, 0.85f); } int num = Mathf.Max(12, Mathf.RoundToInt((float)Screen.height / 1080f * 22f)); hudStyle.fontSize = num; hudShadowStyle.fontSize = num; PluginConfig cfg = Plugin.Cfg; List<string> list = new List<string>(); if (hudNote != null) { list.Add(hudNote); } VideoEntry currentEntry = CurrentEntry; if (currentEntry != null) { list.Add($"CH {CurrentIndex + 1}/{Playlist.Count} {Shorten(currentEntry.Name, 40)}"); } string text = TvStatusLine(); if (text != null && currentEntry != null && currentEntry.Path == null) { list.Add(text); } int num2 = Mathf.RoundToInt(cfg.Volume.Value * 100f); int num3 = Mathf.RoundToInt(cfg.Volume.Value * 10f); list.Add(cfg.Muted.Value ? "VOLUME MUTED" : $"VOLUME [{new string('|', num3)}{new string('.', 10 - num3)}] {num2}%"); TVRemoteInputs inputs = Plugin.Inputs; list.Add(Bind(inputs.Power) + " power " + Bind(inputs.Previous) + " / " + Bind(inputs.Next) + " video " + Bind(inputs.VolumeDown) + " / " + Bind(inputs.VolumeUp) + " volume " + Bind(inputs.Mute) + " mute"); string text2 = string.Join("\n", list); Rect val = default(Rect); ((Rect)(ref val))..ctor(0f, (float)Screen.height * 0.7f, (float)Screen.width, (float)num * 1.6f * (float)list.Count); GUI.Label(new Rect(((Rect)(ref val)).x + 2f, ((Rect)(ref val)).y + 2f, ((Rect)(ref val)).width, ((Rect)(ref val)).height), text2, hudShadowStyle); GUI.Label(val, text2, hudStyle); } } } internal sealed class VideoEntry { public string Id; public string Name; public string Ext; public long Size; public double Duration; public string Path; public string Invalid; } internal static class VideoLibrary { public static readonly string[] Extensions = new string[12] { ".mp4", ".m4v", ".mov", ".webm", ".wmv", ".avi", ".mpg", ".mpeg", ".ogv", ".asf", ".dv", ".vp8" }; private static readonly Dictionary<string, VideoEntry> known = new Dictionary<string, VideoEntry>(StringComparer.OrdinalIgnoreCase); public static List<VideoEntry> Ready { get; private set; } = new List<VideoEntry>(); public static bool Scanning { get; private set; } public static string Folder { get { string text = Environment.ExpandEnvironmentVariables(Plugin.Cfg.VideoFolder.Value ?? "").Trim().Trim('"'); if (!string.IsNullOrEmpty(text)) { return text; } string folderPath = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos); if (!string.IsNullOrEmpty(folderPath)) { return Path.Combine(folderPath, "LethalTV"); } return Path.Combine(Paths.BepInExRootPath, "LethalTV", "Videos"); } } public static event Action Changed; public static bool IsSupportedExtension(string ext) { if (ext != null) { return Extensions.Contains(ext.ToLowerInvariant()); } return false; } public static IEnumerator Scan() { if (Scanning) { yield break; } Scanning = true; try { string folder = Folder; string[] array; try { Directory.CreateDirectory(folder); array = (from f in Directory.GetFiles(folder) where IsSupportedExtension(Path.GetExtension(f)) select f).OrderBy<string, string>((string f) => f, StringComparer.OrdinalIgnoreCase).ToArray(); } catch (Exception ex) { Plugin.Log.LogError((object)("Couldn't read video folder '" + folder + "': " + ex.Message)); array = Array.Empty<string>(); } List<VideoEntry> result = new List<VideoEntry>(); HashSet<string> seen = new HashSet<string>(); string[] array2 = array; foreach (string fileName in array2) { FileInfo info = new FileInfo(fileName); string key = $"{info.FullName}|{info.Length}|{info.LastWriteTimeUtc.Ticks}"; if (!known.TryGetValue(key, out var value)) { Task<string> hashTask = Task.Run(() => HashFile(info.FullName)); while (!hashTask.IsCompleted) { yield return null; } if (hashTask.IsFaulted) { Plugin.Log.LogWarning((object)("Couldn't read '" + info.Name + "': " + hashTask.Exception?.GetBaseException().Message)); continue; } double duration = 0.0; yield return ProbeDuration(info.FullName, delegate(double d) { duration = d; }); value = new VideoEntry { Id = hashTask.Result, Name = Path.GetFileNameWithoutExtension(info.Name), Ext = info.Extension.ToLowerInvariant(), Size = info.Length, Duration = duration, Path = info.FullName }; known[key] = value; if (info.FullName.IndexOfAny(new char[2] { '#', '%' }) >= 0) { Plugin.Log.LogWarning((object)("'" + info.Name + "': '#' or '%' in a file or folder name can stop videos from playing. Rename it if it shows up blank.")); } if (duration > 0.0) { Plugin.Log.LogInfo((object)$"Loaded '{info.Name}' ({duration:0.0}s, {(double)info.Length / 1048576.0:0.0} MB)"); } else { Plugin.Log.LogWarning((object)("Skipping '" + info.Name + "': the game can't play it. Re-encode it as H.264/AAC .mp4.")); } } if (value.Duration > 0.0 && seen.Add(value.Id)) { result.Add(value); } } bool num2 = !result.Select((VideoEntry e) => e.Id).SequenceEqual(Ready.Select((VideoEntry e) => e.Id)); Ready = result; Plugin.Log.LogInfo((object)$"{result.Count} video(s) ready in '{folder}'"); if (num2) { VideoLibrary.Changed?.Invoke(); } } finally { Scanning = false; } } private static IEnumerator ProbeDuration(string path, Action<double> done) { GameObject go = new GameObject("LethalTV Probe") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)(object)go); VideoPlayer player = go.AddComponent<VideoPlayer>(); player.playOnAwake = false; player.renderMode = (VideoRenderMode)4; player.audioOutputMode = (VideoAudioOutputMode)0; player.source = (VideoSource)1; player.url = ToUrl(path); bool failed = false; player.errorReceived += (ErrorEventHandler)delegate(VideoPlayer _, string message) { failed = true; Plugin.Log.LogWarning((object)("Video error for '" + Path.GetFileName(path) + "': " + message)); }; player.Prepare(); float waited = 0f; while (!player.isPrepared && !failed && waited < 20f) { waited += Time.unscaledDeltaTime; yield return null; } double num = 0.0; if (player.isPrepared) { num = player.length; if (num <= 0.0 && player.frameRate > 0f) { num = (float)player.frameCount / player.frameRate; } } Object.Destroy((Object)(object)go); done(num); } public static string ToUrl(string path) { return "file://" + path.Replace('\\', '/'); } public static string HashFile(string path) { using SHA256 sHA = SHA256.Create(); using FileStream inputStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 1048576); byte[] array = sHA.ComputeHash(inputStream); StringBuilder stringBuilder = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2")); } return stringBuilder.ToString(); } } internal static class VideoCache { public static string Dir => Path.Combine(Paths.BepInExRootPath, "LethalTV", "Cache"); public static string CompletePath(VideoEntry e) { return Path.Combine(Dir, e.Id + e.Ext); } public static string PartPath(VideoEntry e) { return Path.Combine(Dir, e.Id + ".part"); } public static bool IsValidId(string id) { if (id == null || id.Length != 64) { return false; } foreach (char c in id) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { return false; } } return true; } public static string FindComplete(VideoEntry e) { try { FileInfo fileInfo = new FileInfo(CompletePath(e)); if (!fileInfo.Exists || fileInfo.Length != e.Size) { return null; } fileInfo.LastWriteTimeUtc = DateTime.UtcNow; return fileInfo.FullName; } catch (Exception) { return null; } } public static void Prune(IEnumerable<string> keepIds, long maxBytes) { try { DirectoryInfo directoryInfo = new DirectoryInfo(Dir); if (!directoryInfo.Exists) { return; } HashSet<string> hashSet = new HashSet<string>(keepIds); List<FileInfo> list = (from f in directoryInfo.GetFiles() orderby f.LastWriteTimeUtc select f).ToList(); long num = list.Sum((FileInfo f) => f.Length); foreach (FileInfo item in list) { if (num > maxBytes) { if (!hashSet.Contains(Path.GetFileNameWithoutExtension(item.Name))) { long length = item.Length; item.Delete(); num -= length; Plugin.Log.LogInfo((object)("Pruned cached video " + item.Name)); } continue; } break; } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Cache prune failed: " + ex.Message)); } } } }