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 ValheimCinema v0.1.5
BepInEx/plugins/ValheimCinema/ValheimCinema.dll
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using UnityEngine; using UnityEngine.Rendering; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace ValheimCinema; public sealed class CinemaAudioOutput : MonoBehaviour { private VlcBackend backend; private AudioSource source; private AudioListener listener; private readonly CinemaPcmMix mix = new CinemaPcmMix(); private volatile int sampleRate = 48000; public void Initialize(VlcBackend value) { backend = value; source = ((Component)this).GetComponent<AudioSource>(); RefreshMix(); } public float SetVolume(float distance, float volume, bool muted, float near, float far) { float num = (muted ? 0f : (volume * CinemaPlayback.DistanceGain(distance, near, far))); if (Object.op_Implicit((Object)(object)source)) { source.volume = num; source.mute = muted; } return num; } private void LateUpdate() { RefreshMix(); } private void RefreshMix() { //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) sampleRate = AudioSettings.outputSampleRate; if (!Object.op_Implicit((Object)(object)source)) { mix.Set(0f, 0f, 0f); return; } if (!Object.op_Implicit((Object)(object)listener) || !((Behaviour)listener).isActiveAndEnabled) { listener = null; AudioListener[] array = Object.FindObjectsOfType<AudioListener>(); foreach (AudioListener val in array) { if (((Behaviour)val).isActiveAndEnabled) { listener = val; break; } } } float volume = ((source.mute || (AudioListener.pause && !source.ignoreListenerPause)) ? 0f : (source.volume * MasterVolume())); float direction = 0f; if (source.spatialBlend > 0f) { if (!Object.op_Implicit((Object)(object)listener)) { volume = 0f; } else { Vector3 val2 = ((Component)this).transform.position - ((Component)listener).transform.position; direction = ((((Vector3)(ref val2)).sqrMagnitude < 0.0001f) ? 0f : Vector3.Dot(((Vector3)(ref val2)).normalized, ((Component)listener).transform.right)); } } mix.Set(volume, direction, source.spatialBlend); } private static float MasterVolume() { return AudioListener.volume; } private void OnAudioFilterRead(float[] data, int channels) { VlcBackend vlcBackend = backend; if (vlcBackend == null) { Array.Clear(data, 0, data.Length); return; } vlcBackend.RenderAudio(data, channels, sampleRate); mix.Process(data, channels, sampleRate); } private void OnDestroy() { backend = null; } } public sealed class CinemaPanel : MonoBehaviour { private const float Width = 840f; private const float Height = 684f; public static CinemaScreen ActiveScreen; public static int ClosedFrame = -1; private static CinemaPanel instance; private string url = ""; private string seekText = "0:00"; private GUIStyle titleStyle; private GUIStyle smallStyle; private GUIStyle labelStyle; private GUIStyle buttonStyle; private GUIStyle fieldStyle; private GUIStyle badgeStyle; private Texture2D background; private Texture2D border; private Texture2D button; private Texture2D buttonHover; private Texture2D field; private Texture2D accent; private Font font; private bool focusUrl; private static readonly Color Gold = new Color(0.91f, 0.72f, 0.4f); public static bool IsOpen => Object.op_Implicit((Object)(object)ActiveScreen); private void Awake() { instance = this; } public static void Open(CinemaScreen screen) { if (Object.op_Implicit((Object)(object)instance) && Object.op_Implicit((Object)(object)screen)) { ActiveScreen = screen; instance.url = screen.State.Url; instance.seekText = FormatTime(screen.State.ExpectedPosition(CinemaScreen.NetworkTime)); instance.focusUrl = true; Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; ZCursor.LockState = (CursorLockMode)0; ZCursor.Show(); } } public static void Close() { if (!Object.op_Implicit((Object)(object)ActiveScreen)) { ActiveScreen = null; return; } ActiveScreen = null; ClosedFrame = Time.frameCount; if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && Object.op_Implicit((Object)(object)GameCamera.instance)) { GameCamera.instance.UpdateMouseCapture(); } } private void Update() { if (Object.op_Implicit((Object)(object)ActiveScreen)) { if (!ActiveScreen.CanControlLocal() || ZInput.GetKeyDown((KeyCode)27, true)) { Close(); return; } Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } private static Texture2D Solid(Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown //IL_000d: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, color); val.Apply(); return val; } private void Styles() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: 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_00fc: Expected O, but got Unknown //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Expected O, but got Unknown //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Expected O, but got Unknown //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Expected O, but got Unknown //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Expected O, but got Unknown //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Expected O, but got Unknown //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Expected O, but got Unknown //IL_02b9: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Expected O, but got Unknown //IL_02c5: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Expected O, but got Unknown //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0322: Unknown result type (might be due to invalid IL or missing references) if (titleStyle == null) { font = Font.CreateDynamicFontFromOSFont(new string[3] { "Segoe UI", "Arial", "DejaVu Sans" }, 17); background = Solid(new Color(0.055f, 0.07f, 0.085f, 0.99f)); border = Solid(new Color(0.22f, 0.26f, 0.29f)); button = Solid(new Color(0.14f, 0.18f, 0.2f)); buttonHover = Solid(new Color(0.22f, 0.28f, 0.3f)); field = Solid(new Color(0.09f, 0.115f, 0.135f)); accent = Solid(Gold); GUIStyle val = new GUIStyle(GUI.skin.label); val.font = font; val.fontSize = 17; val.richText = false; val.wordWrap = true; labelStyle = val; labelStyle.normal.textColor = new Color(0.92f, 0.93f, 0.94f); GUIStyle val2 = new GUIStyle(labelStyle); val2.fontSize = 14; smallStyle = val2; smallStyle.normal.textColor = new Color(0.6f, 0.67f, 0.7f); GUIStyle val3 = new GUIStyle(labelStyle); val3.fontSize = 27; val3.fontStyle = (FontStyle)1; titleStyle = val3; GUIStyle val4 = new GUIStyle(smallStyle); val4.alignment = (TextAnchor)5; badgeStyle = val4; badgeStyle.normal.textColor = Gold; GUIStyle val5 = new GUIStyle(GUI.skin.button); val5.font = font; val5.fontSize = 16; val5.padding = new RectOffset(12, 12, 6, 6); val5.border = new RectOffset(); val5.margin = new RectOffset(); buttonStyle = val5; buttonStyle.normal.background = button; buttonStyle.hover.background = buttonHover; buttonStyle.active.background = buttonHover; buttonStyle.normal.textColor = Color.white; GUIStyle val6 = new GUIStyle(GUI.skin.textField); val6.font = font; val6.fontSize = 16; val6.padding = new RectOffset(10, 10, 9, 8); val6.border = new RectOffset(); fieldStyle = val6; fieldStyle.normal.background = field; fieldStyle.focused.background = field; fieldStyle.normal.textColor = Color.white; fieldStyle.focused.textColor = Color.white; } } private bool Button(float x, float y, float w, string text) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) return GUI.Button(new Rect(x, y, w, 36f), text, buttonStyle); } private void OnGUI() { //IL_004d: 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_0087: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Unknown result type (might be due to invalid IL or missing references) //IL_0582: Unknown result type (might be due to invalid IL or missing references) //IL_064a: Unknown result type (might be due to invalid IL or missing references) //IL_066e: Unknown result type (might be due to invalid IL or missing references) //IL_0697: Unknown result type (might be due to invalid IL or missing references) //IL_06e4: Unknown result type (might be due to invalid IL or missing references) //IL_07c9: Unknown result type (might be due to invalid IL or missing references) //IL_07f6: Unknown result type (might be due to invalid IL or missing references) //IL_07fb: Unknown result type (might be due to invalid IL or missing references) //IL_080e: Unknown result type (might be due to invalid IL or missing references) //IL_08b6: Unknown result type (might be due to invalid IL or missing references) //IL_08df: Unknown result type (might be due to invalid IL or missing references) //IL_08f4: Unknown result type (might be due to invalid IL or missing references) //IL_0859: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)ActiveScreen)) { return; } Styles(); CinemaScreen activeScreen = ActiveScreen; float num = Mathf.Min(1.3f, Mathf.Min(((float)Screen.width - 24f) / 840f, ((float)Screen.height - 24f) / 684f)); Matrix4x4 matrix = GUI.matrix; GUI.depth = -50; GUI.matrix = Matrix4x4.TRS(new Vector3(((float)Screen.width - 840f * num) * 0.5f, ((float)Screen.height - 684f * num) * 0.5f, 0f), Quaternion.identity, Vector3.one * num); GUI.DrawTexture(new Rect(0f, 0f, 840f, 684f), (Texture)(object)background); GUI.DrawTexture(new Rect(0f, 0f, 840f, 3f), (Texture)(object)accent); GUI.Label(new Rect(24f, 16f, 570f, 40f), "VALHEIM CINEMA", titleStyle); GUI.Label(new Rect(535f, 21f, 228f, 28f), "ОБЩИЙ ПРОСМОТР", badgeStyle); if (Button(776f, 18f, 40f, "×")) { Close(); GUI.matrix = matrix; return; } GUI.Label(new Rect(24f, 57f, 792f, 25f), activeScreen.GetHoverName(), smallStyle); Rect val = default(Rect); ((Rect)(ref val))..ctor(24f, 92f, 400f, 225f); GUI.DrawTexture(val, (Texture)(object)Texture2D.blackTexture); if (Object.op_Implicit((Object)(object)activeScreen.Playback) && Object.op_Implicit((Object)(object)activeScreen.Playback.Picture)) { GUI.DrawTexture(val, activeScreen.Playback.Picture, (ScaleMode)2); } GUI.Label(new Rect(445f, 92f, 371f, 75f), activeScreen.Title, labelStyle); GUI.Label(new Rect(445f, 175f, 371f, 82f), activeScreen.Status, smallStyle); string text = (activeScreen.IsLive ? "LIVE · ПРЯМОЙ ЭФИР" : (activeScreen.State.Playing ? "ВОСПРОИЗВЕДЕНИЕ" : "ПАУЗА")); GUI.Label(new Rect(445f, 262f, 371f, 24f), string.IsNullOrEmpty(activeScreen.State.Url) ? "ЭКРАН ГОТОВ К ПРОСМОТРУ" : text, badgeStyle); double num2 = (Object.op_Implicit((Object)(object)activeScreen.Playback) ? activeScreen.Playback.Position : activeScreen.State.ExpectedPosition(CinemaScreen.NetworkTime)); double num3 = (Object.op_Implicit((Object)(object)activeScreen.Playback) ? activeScreen.Playback.Duration : 0.0); GUI.Label(new Rect(445f, 292f, 371f, 25f), activeScreen.IsLive ? "Пауза общая; продолжение — с прямого эфира" : (FormatTime(num2) + ((num3 > 0.0) ? (" / " + FormatTime(num3)) : "")), smallStyle); GUI.Label(new Rect(24f, 331f, 792f, 25f), "YOUTUBE · RUTUBE · VK ВИДЕО · ПРЯМАЯ ССЫЛКА", smallStyle); GUI.SetNextControlName("cinema_url"); url = GUI.TextField(new Rect(24f, 360f, 544f, 38f), url, 2048, fieldStyle); if (focusUrl) { GUI.FocusControl("cinema_url"); focusUrl = false; } if (Button(578f, 361f, 107f, "Вставить")) { url = GUIUtility.systemCopyBuffer ?? ""; } if (Button(695f, 361f, 121f, "Смотреть")) { GUI.FocusControl(""); activeScreen.LoadFromInput(url); } GUI.enabled = !string.IsNullOrEmpty(activeScreen.State.Url); if (Button(24f, 413f, 164f, activeScreen.State.Playing ? "Пауза для всех" : "Пуск для всех")) { activeScreen.Request((!activeScreen.State.Playing) ? CinemaCommand.Play : CinemaCommand.Pause, "", 0.0, activeScreen.IsLive); } GUI.enabled = GUI.enabled && !activeScreen.IsLive; if (Button(198f, 413f, 66f, "−10 с")) { activeScreen.Request(CinemaCommand.Seek, "", Math.Max(0.0, num2 - 10.0), live: false); } if (Button(274f, 413f, 66f, "+10 с")) { activeScreen.Request(CinemaCommand.Seek, "", (num3 > 0.0) ? Math.Min(num3 - 0.1, num2 + 10.0) : (num2 + 10.0), live: false); } seekText = GUI.TextField(new Rect(350f, 413f, 92f, 36f), seekText, 16, fieldStyle); if (Button(452f, 413f, 145f, "Перемотать")) { if (TryTime(seekText, out var seconds)) { activeScreen.Request(CinemaCommand.Seek, "", seconds, live: false); } else { activeScreen.Status = "Введите время: 1:23, 01:02:03 или число секунд"; } } GUI.enabled = !string.IsNullOrEmpty(activeScreen.State.Url); if (Button(607f, 413f, 209f, "Выключить для всех")) { activeScreen.Request(CinemaCommand.Stop, "", 0.0, live: false); } GUI.enabled = true; GUI.DrawTexture(new Rect(24f, 465f, 792f, 1f), (Texture)(object)border); GUI.Label(new Rect(24f, 477f, 792f, 24f), "ЛИЧНЫЕ НАСТРОЙКИ", smallStyle); GUI.Label(new Rect(24f, 511f, 132f, 25f), "Громкость " + Mathf.RoundToInt(CinemaPlugin.Volume.Value * 100f) + "%", labelStyle); float num4 = GUI.HorizontalSlider(new Rect(164f, 520f, 226f, 20f), CinemaPlugin.Volume.Value, 0f, 1f); if (Math.Abs(num4 - CinemaPlugin.Volume.Value) > 0.005f) { CinemaPlugin.Volume.Value = num4; } if (Button(410f, 506f, 183f, CinemaPlugin.Muted.Value ? "Включить мой звук" : "Без звука")) { CinemaPlugin.Muted.Value = !CinemaPlugin.Muted.Value; } if (Button(603f, 506f, 213f, CinemaPlugin.PlaybackEnabled.Value ? "Отключить у меня" : "Включить у меня")) { CinemaPlugin.PlaybackEnabled.Value = !CinemaPlugin.PlaybackEnabled.Value; } GUI.Label(new Rect(24f, 557f, 121f, 28f), "Качество", labelStyle); int[] array = new int[4] { 360, 480, 720, 1080 }; for (int i = 0; i < array.Length; i++) { Color backgroundColor = GUI.backgroundColor; if (CinemaPlugin.Quality.Value == array[i]) { GUI.backgroundColor = Gold; } if (Button(145 + i * 94, 550f, 84f, array[i] + "p")) { CinemaPlugin.Quality.Value = array[i]; } GUI.backgroundColor = backgroundColor; } if (Button(603f, 550f, 213f, "Переподключить") && Object.op_Implicit((Object)(object)activeScreen.Playback)) { activeScreen.Playback.Retry(); } GUI.Label(new Rect(24f, 599f, 792f, 42f), "Звук плавно затихает при удалении. Выбирается ближайшее доступное качество; у прямой ссылки поток задаётся источником.", smallStyle); GUI.Label(new Rect(24f, 650f, 720f, 24f), "E — открыть у экрана · Esc — закрыть и вернуться в игру", smallStyle); GUI.matrix = matrix; } public static string FormatTime(double seconds) { if (double.IsNaN(seconds) || double.IsInfinity(seconds)) { return "0:00"; } long num = (long)Math.Max(0.0, Math.Min(31536000.0, seconds)); if (num < 3600) { return num / 60 + ":" + (num % 60).ToString("00"); } return num / 3600 + ":" + (num / 60 % 60).ToString("00") + ":" + (num % 60).ToString("00"); } private static bool TryTime(string input, out double seconds) { seconds = 0.0; string[] array = input.Trim().Split(':'); if (array.Length > 3) { return false; } for (int i = 0; i < array.Length; i++) { if (!int.TryParse(array[i], out var result) || result < 0 || (i > 0 && result > 59)) { return false; } seconds = seconds * 60.0 + (double)result; } return seconds <= 31536000.0; } private void OnDestroy() { Close(); instance = null; Texture2D[] array = (Texture2D[])(object)new Texture2D[6] { background, border, button, buttonHover, field, accent }; foreach (Texture2D val in array) { if (Object.op_Implicit((Object)(object)val)) { Object.Destroy((Object)(object)val); } } if (Object.op_Implicit((Object)(object)font)) { Object.Destroy((Object)(object)font); } } } public sealed class CinemaPcmMix { private sealed class Levels { public readonly float Gain; public readonly float Pan; public readonly float Spatial; public Levels(float gain, float pan, float spatial) { Gain = gain; Pan = pan; Spatial = spatial; } } private volatile Levels target = new Levels(0f, 0f, 0f); private float gain; private float pan; private float spatial; public void Set(float volume, float direction, float blend) { volume = Clamp(volume, 0f, 1f); direction = Clamp(direction, -1f, 1f); blend = Clamp(blend, 0f, 1f); Levels levels = target; if (levels.Gain != volume || levels.Pan != direction || levels.Spatial != blend) { target = new Levels(volume, direction, blend); } } public void Process(float[] data, int channels, int sampleRate) { if (channels < 1 || sampleRate < 1) { Array.Clear(data, 0, data.Length); return; } Levels levels = target; int num = data.Length / channels; int num2 = Math.Max(1, Math.Min(num, sampleRate / 100)); float num3 = (levels.Gain - gain) / (float)num2; float num4 = (levels.Pan - pan) / (float)num2; float num5 = (levels.Spatial - spatial) / (float)num2; for (int i = 0; i < num; i++) { if (i < num2) { gain += num3; pan += num4; spatial += num5; } else { gain = levels.Gain; pan = levels.Pan; spatial = levels.Spatial; } int num6 = i * channels; if (channels == 1) { data[num6] *= gain; continue; } float num7 = data[num6]; float num8 = data[num6 + 1]; float num9 = (num7 + num8) * 0.5f; float num10 = (float)Math.Sqrt(Math.Max(0.0, (double)(1f - pan) * 0.5)); float num11 = (float)Math.Sqrt(Math.Max(0.0, (double)(1f + pan) * 0.5)); data[num6] = ((1f - spatial) * num7 + spatial * num9 * num10) * gain; data[num6 + 1] = ((1f - spatial) * num8 + spatial * num9 * num11) * gain; for (int j = 2; j < channels; j++) { data[num6 + j] *= gain; } } gain = levels.Gain; pan = levels.Pan; spatial = levels.Spatial; } private static float Clamp(float value, float min, float max) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Math.Max(min, Math.Min(max, value)); } return 0f; } } public sealed class CinemaPlayback : MonoBehaviour { private CinemaScreen screen; private Renderer surface; private Material material; private Texture idleTexture; private GameObject speakerObject; private AudioSource speaker; private CinemaAudioOutput audioOutput; private VlcBackend backend; private CancellationTokenSource cancellation; private string currentUrl = ""; private int quality; private int generation; private int failures; private long sourceRevision = -1L; private long lastRevision = -1L; private bool preparing; private bool prepared; private bool live; private bool wasPlaying; private bool livePaused; private float preparationDeadline; private float retryAt; private float nextSync; private float lastSeek; private float lastAdvanceAt; private double lastFrameTime; public VlcBackend StreamBackend => backend; public AudioSource Speaker => speaker; public Texture Picture { get { if (backend == null || !backend.Ready) { return idleTexture; } return (Texture)(object)backend.Texture; } } public bool IsLive => live; public bool IsPrepared => prepared; public bool IsPlaying { get { if (prepared && backend != null) { return backend.IsPlaying; } return false; } } public float EffectiveVolume { get; private set; } public double Position { get { if (backend == null || !backend.Ready) { return screen.State.ExpectedPosition(CinemaScreen.NetworkTime); } return backend.Time; } } public double Duration { get { if (live || backend == null || !backend.Ready) { return 0.0; } return backend.Length; } } public void Initialize(CinemaScreen owner, Renderer renderer) { screen = owner; surface = renderer; material = surface.material; idleTexture = material.mainTexture; } public static float DistanceGain(float distance, float near, float far) { if (float.IsNaN(distance) || float.IsInfinity(distance)) { return 0f; } far = Mathf.Max(near + 0.1f, far); float num = Mathf.Clamp01((distance - near) / (far - near)); return 1f - num * num * (3f - 2f * num); } public void Tick(bool active) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)screen) || !Object.op_Implicit((Object)(object)surface)) { return; } if (!active) { if (Object.op_Implicit((Object)(object)speakerObject) || cancellation != null || backend != null) { Release(); } currentUrl = ""; return; } if (currentUrl != screen.State.Url || quality != CinemaPlugin.Quality.Value || sourceRevision != screen.State.LoadRevision) { Release(); currentUrl = screen.State.Url; quality = CinemaPlugin.Quality.Value; sourceRevision = screen.State.LoadRevision; failures = 0; retryAt = 0f; Begin(); } else if (!preparing && !prepared && failures > 0 && failures <= 2 && Time.unscaledTime >= retryAt) { Begin(); } if (preparing && Time.unscaledTime > preparationDeadline) { Fail("Видео не ответило вовремя. Проверьте соединение или другую ссылку."); return; } if (Object.op_Implicit((Object)(object)speaker)) { float distance = (Object.op_Implicit((Object)(object)Player.m_localPlayer) ? screen.DistanceTo(((Character)Player.m_localPlayer).m_eye.position) : float.PositiveInfinity); EffectiveVolume = (Object.op_Implicit((Object)(object)audioOutput) ? audioOutput.SetVolume(distance, CinemaPlugin.Volume.Value, CinemaPlugin.Muted.Value, CinemaPlugin.FullVolumeDistance.Value, CinemaPlugin.SilentDistance.Value) : 0f); } if (backend == null) { return; } backend.Tick(); if (!string.IsNullOrEmpty(backend.Error)) { Fail(CinemaScreen.FriendlyError(backend.Error)); return; } if (!prepared && backend.Ready) { prepared = true; preparing = false; material.mainTexture = (Texture)(object)backend.Texture; if (material.HasProperty("_EmissionMap")) { material.SetTexture("_EmissionMap", (Texture)(object)backend.Texture); } screen.Status = (live ? "Прямой эфир · задержка зависит от источника" : "Готово"); lastRevision = -1L; wasPlaying = false; livePaused = false; lastAdvanceAt = Time.unscaledTime; lastFrameTime = backend.Time; lastSeek = -100f; } if (!prepared) { return; } if (backend.Ended) { if (live && screen.State.Playing) { Fail("Эфир завершился или соединение прервано."); return; } if (!live && Duration > 0.0 && screen.State.ExpectedPosition(CinemaScreen.NetworkTime) < Duration - 0.25) { Begin(); return; } } bool flag = lastRevision != screen.State.Revision; if (flag || Time.unscaledTime >= nextSync) { nextSync = Time.unscaledTime + 1f; Synchronize(flag); lastRevision = screen.State.Revision; } if (prepared && backend != null && screen.State.Playing && backend.IsPlaying) { if (Math.Abs(backend.Time - lastFrameTime) > 0.02) { lastFrameTime = backend.Time; lastAdvanceAt = Time.unscaledTime; } else if (Time.unscaledTime - lastAdvanceAt > 25f && (live || Duration <= 0.0 || backend.Time < Duration - 1.0)) { Fail("Видеопоток остановился. Пробуем подключиться снова…"); } } } private void Synchronize(bool changed) { bool playing = screen.State.Playing; if (playing && !wasPlaying) { lastAdvanceAt = Time.unscaledTime; lastFrameTime = backend.Time; } if (live && playing && livePaused) { livePaused = false; Begin(); return; } double num = screen.State.ExpectedPosition(CinemaScreen.NetworkTime); double duration = Duration; bool flag = !live && duration > 0.0 && num >= duration; if (!live && (changed || Math.Abs(backend.Time - num) > 1.5) && Time.unscaledTime - lastSeek > 2f) { double seconds = ((duration > 0.0) ? Math.Min(num, Math.Max(0.0, duration - 0.05)) : num); if (playing && !flag) { backend.Seek(seconds); } else { backend.SeekPaused(seconds); } lastSeek = Time.unscaledTime; } if (playing && !flag) { backend.Play(); if (Object.op_Implicit((Object)(object)speaker) && !speaker.isPlaying) { speaker.UnPause(); if (!speaker.isPlaying) { speaker.Play(); } } } else { if (backend.IsPlaying || changed) { backend.Pause(); } if (Object.op_Implicit((Object)(object)speaker)) { speaker.Pause(); } if (live) { livePaused = true; } if (flag) { screen.Status = "Видео закончилось · перемотайте к началу для повтора"; } } wasPlaying = playing; } private void Begin() { ReleaseDecoder(); if (cancellation != null) { cancellation.Cancel(); cancellation.Dispose(); } cancellation = new CancellationTokenSource(); int expected = ++generation; preparing = true; prepared = false; preparationDeadline = Time.unscaledTime + 100f; screen.Status = ((failures > 0) ? "Повторное подключение к видео…" : "Загрузка видео…"); ((MonoBehaviour)this).StartCoroutine(Resolve(expected, currentUrl, quality, cancellation.Token)); } private IEnumerator Resolve(int expected, string url, int height, CancellationToken token) { ResolvedMedia media = null; if (screen.CachedMedia != null && screen.CachedUrl == url && screen.CachedQuality == height && failures == 0) { media = screen.CachedMedia; screen.CachedMedia = null; } if (media == null) { Task<ResolvedMedia> task = MediaResolver.ResolveAsync(url, height, token); while (!task.IsCompleted) { yield return null; } if (expected != generation || token.IsCancellationRequested) { if (task.IsFaulted) { _ = task.Exception; } yield break; } if (task.IsCanceled) { yield break; } if (task.IsFaulted) { Fail(CinemaScreen.FriendlyError(task.Exception.GetBaseException().Message)); yield break; } media = task.Result; } if (expected == generation && !token.IsCancellationRequested) { try { Prepare(media, height); } catch (Exception ex) { Fail(CinemaScreen.FriendlyError(ex.Message)); } } } private void Prepare(ResolvedMedia media, int height) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: 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_0086: Unknown result type (might be due to invalid IL or missing references) live = media.IsLive || screen.State.IsLive; screen.Title = (string.IsNullOrEmpty(media.Title) ? "Valheim Cinema" : media.Title); speakerObject = new GameObject("CinemaLocalSpeaker"); speakerObject.transform.SetParent(((Component)this).transform, false); Transform transform = speakerObject.transform; Bounds bounds = surface.bounds; transform.position = ((Bounds)(ref bounds)).center; speaker = speakerObject.AddComponent<AudioSource>(); speaker.playOnAwake = false; speaker.loop = true; speaker.volume = 0f; speaker.spatialBlend = 1f; speaker.dopplerLevel = 0f; speaker.rolloffMode = (AudioRolloffMode)2; speaker.SetCustomCurve((AudioSourceCurveType)0, AnimationCurve.Linear(0f, 1f, 1f, 1f)); speaker.minDistance = 1f; speaker.maxDistance = 1000f; speaker.spread = 35f; speaker.bypassReverbZones = true; speaker.ignoreListenerPause = false; backend = new VlcBackend(); backend.Load(media, height); if (!string.IsNullOrEmpty(backend.Error)) { Fail(backend.Error); return; } speaker.clip = backend.Clip; audioOutput = speakerObject.AddComponent<CinemaAudioOutput>(); audioOutput.Initialize(backend); preparationDeadline = Time.unscaledTime + 65f; } private void Fail(string reason) { CinemaPlugin.Log.LogWarning((object)("Media playback: " + reason)); failures++; preparing = (prepared = false); generation++; if (cancellation != null) { cancellation.Cancel(); } ReleaseDecoder(); retryAt = Time.unscaledTime + 4f * (float)failures; screen.Status = reason + ((failures <= 2) ? " Повторяем…" : " Нажмите «Переподключить»."); } public void Retry() { failures = 0; if (!string.IsNullOrEmpty(currentUrl)) { Begin(); } } private void Release() { generation++; preparing = (prepared = false); failures = 0; if (cancellation != null) { cancellation.Cancel(); cancellation.Dispose(); cancellation = null; } ReleaseDecoder(); } private void ReleaseDecoder() { if (Object.op_Implicit((Object)(object)material)) { material.mainTexture = idleTexture; if (material.HasProperty("_EmissionMap")) { material.SetTexture("_EmissionMap", idleTexture); } } if (Object.op_Implicit((Object)(object)speaker)) { speaker.Stop(); speaker.clip = null; } if (backend != null) { backend.Dispose(); backend = null; } if (Object.op_Implicit((Object)(object)speakerObject)) { Object.Destroy((Object)(object)speakerObject); } speaker = null; audioOutput = null; speakerObject = null; EffectiveVolume = 0f; } private void OnDestroy() { Release(); if (Object.op_Implicit((Object)(object)material)) { Object.Destroy((Object)(object)material); } } } [BepInPlugin("local.valheim.cinema", "Valheim Cinema", "0.1.5")] public sealed class CinemaPlugin : BaseUnityPlugin { [HarmonyPatch(typeof(ZNetScene), "Awake")] private static class ScenePatch { private static void Postfix(ZNetScene __instance) { ScreenPrefabs.Register(__instance); } } [HarmonyPatch(typeof(ObjectDB), "Awake")] private static class DatabasePatch { private static void Postfix(ObjectDB __instance) { ScreenPrefabs.AddToHammer(__instance); } } [HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")] private static class CopyDatabasePatch { private static void Postfix(ObjectDB __instance) { ScreenPrefabs.AddToHammer(__instance); } } [HarmonyPatch(typeof(ZInput), "GetButtonDown", new Type[] { typeof(string) })] private static class ModalButtonPatch { private static bool Prefix(ref bool __result) { if (!CinemaPanel.IsOpen) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(ZInput), "GetKeyDown", new Type[] { typeof(KeyCode), typeof(bool) })] private static class ModalKeyPatch { private static bool Prefix(KeyCode __0, ref bool __result) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 if (!CinemaPanel.IsOpen || (int)__0 == 27) { return true; } __result = false; return false; } } [HarmonyPatch(typeof(Player), "TakeInput")] private static class PlayerInputPatch { private static void Postfix(Player __instance, ref bool __result) { if (CinemaPanel.IsOpen && (Object)(object)__instance == (Object)(object)Player.m_localPlayer) { __result = false; } } } [HarmonyPatch(typeof(PlayerController), "TakeInput")] private static class ControllerInputPatch { private static void Postfix(ref bool __result) { if (CinemaPanel.IsOpen) { __result = false; } } } [HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")] private static class CursorPatch { private static bool Prefix() { if (!CinemaPanel.IsOpen) { return true; } ZCursor.LockState = (CursorLockMode)0; ZCursor.Show(); Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; return false; } } [HarmonyPatch(typeof(GameCamera), "UpdateCamera")] private static class CameraPatch { private static bool Prefix() { return !CinemaPanel.IsOpen; } } [HarmonyPatch(typeof(Menu), "Update")] private static class EscapePatch { private static bool Prefix() { if (!CinemaPanel.IsOpen) { return Time.frameCount != CinemaPanel.ClosedFrame; } return false; } } public const string Id = "local.valheim.cinema"; public const string Version = "0.1.5"; public static CinemaPlugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<float> Volume; internal static ConfigEntry<float> FullVolumeDistance; internal static ConfigEntry<float> SilentDistance; internal static ConfigEntry<float> ViewingDistance; internal static ConfigEntry<int> Quality; internal static ConfigEntry<int> MaxActiveScreens; internal static ConfigEntry<bool> Muted; internal static ConfigEntry<bool> PlaybackEnabled; private Harmony harmony; private void Awake() { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Expected O, but got Unknown //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Volume = ((BaseUnityPlugin)this).Config.Bind<float>("Local playback", "Volume", 0.7f, new ConfigDescription("Your cinema volume; also follows game master volume.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), new object[0])); Muted = ((BaseUnityPlugin)this).Config.Bind<bool>("Local playback", "Muted", false, "Mute cinema on this computer only."); PlaybackEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Local playback", "Enabled", true, "Allow nearby screens to load video on this computer."); Quality = ((BaseUnityPlugin)this).Config.Bind<int>("Local playback", "MaxHeight", 720, new ConfigDescription("Preferred video height on this computer. Falls back to the closest available rendition.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[4] { 360, 480, 720, 1080 }), new object[0])); MaxActiveScreens = ((BaseUnityPlugin)this).Config.Bind<int>("Local playback", "MaxActiveScreens", 2, new ConfigDescription("Only this many nearest screens decode video concurrently.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 4), new object[0])); FullVolumeDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Local playback", "FullVolumeDistance", 3f, new ConfigDescription("Full sound up to this distance in metres.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10f), new object[0])); SilentDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Local playback", "SilentDistance", 30f, new ConfigDescription("Sound fades to zero at this distance in metres.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(12f, 80f), new object[0])); ViewingDistance = ((BaseUnityPlugin)this).Config.Bind<float>("Local playback", "ViewingDistance", 70f, new ConfigDescription("Unload video beyond this distance; resume at shared time on return.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 120f), new object[0])); ConfigEntry<string> youtubeSession = ((BaseUnityPlugin)this).Config.Bind<string>("YouTube", "BrowserSession", "", "Optional, personal authorization: yt-dlp browser[:profile] selector. Empty by default. Reads local browser cookies only after you configure it; never shared with other players."); MediaResolver.YoutubeBrowserSession = youtubeSession.Value; youtubeSession.SettingChanged += delegate { MediaResolver.YoutubeBrowserSession = youtubeSession.Value; }; ConfigEntry<string> youtubeCookies = ((BaseUnityPlugin)this).Config.Bind<string>("YouTube", "CookiesFile", "", "Optional private file containing only youtube.com session cookies. Import with tools/import-youtube-session.ps1 after explicit permission. Never distribute this file. Empty uses the imported default file when UseImportedSession is enabled."); MediaResolver.YoutubeCookiesFile = youtubeCookies.Value; youtubeCookies.SettingChanged += delegate { MediaResolver.YoutubeCookiesFile = youtubeCookies.Value; }; ConfigEntry<bool> importedSession = ((BaseUnityPlugin)this).Config.Bind<bool>("YouTube", "UseImportedSession", true, "Use only the YouTube session you explicitly saved with the import tool in LocalAppData/ValheimCinema/Auth. Does not read a browser automatically. Explicit CookiesFile or BrowserSession takes precedence."); MediaResolver.YoutubeUseImportedSession = importedSession.Value; importedSession.SettingChanged += delegate { MediaResolver.YoutubeUseImportedSession = importedSession.Value; }; ((Component)this).gameObject.AddComponent<CinemaPanel>(); harmony = new Harmony("local.valheim.cinema"); harmony.PatchAll(typeof(CinemaPlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Valheim Cinema 0.1.5 loaded; Valheim " + Version.GetVersionString(false) + "; Unity " + Application.unityVersion)); } private void OnDestroy() { CinemaPanel.Close(); if (harmony != null) { harmony.UnpatchAll("local.valheim.cinema"); } Instance = null; } } public sealed class CinemaScreen : MonoBehaviour, Hoverable, Interactable { internal const string StateKey = "ValheimCinema.State.v1"; private const string CommandRpc = "VHC_Command_v1"; private const string StateRpc = "VHC_State_v1"; private const string ReplyRpc = "VHC_Reply_v1"; private static readonly List<CinemaScreen> screens = new List<CinemaScreen>(); private static readonly FieldInfo WardList = AccessTools.Field(typeof(PrivateArea), "m_allAreas"); private static readonly MethodInfo WardEnabled = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null); private static readonly MethodInfo WardInside = AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null); private static readonly MethodInfo WardPermitted = AccessTools.Method(typeof(PrivateArea), "IsPermitted", (Type[])null, (Type[])null); private readonly Dictionary<long, float> commandsAt = new Dictionary<long, float>(); private ZNetView view; private Renderer surface; private CinemaPlayback playback; private CinemaState state = new CinemaState(); private string persisted = ""; private float nextPoll; private float lastRealTime; private double lastNetworkTime; private double clockError; private long clockRevision; private bool clockInitialized; private bool clockWasOwner; private CancellationTokenSource preflight; private bool initialized; internal ResolvedMedia CachedMedia; internal string CachedUrl; internal int CachedQuality; public string Status = "Вставьте ссылку на видео"; public string Title = "Valheim Cinema"; public CinemaState State => state; public Renderer ScreenRenderer => surface; public CinemaPlayback Playback => playback; public static double NetworkTime { get { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return 0.0; } return ZNet.instance.GetTimeSeconds(); } } public bool IsLive { get { if (!state.IsLive) { if (Object.op_Implicit((Object)(object)playback)) { return playback.IsLive; } return false; } return true; } } private void Start() { view = ((Component)this).GetComponent<ZNetView>(); if (!Object.op_Implicit((Object)(object)view) || !view.IsValid()) { return; } Transform val = ((Component)this).transform.Find("CinemaSurface"); if (!Object.op_Implicit((Object)(object)val)) { Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<Renderer>(true); foreach (Renderer val2 in componentsInChildren) { if (((Object)val2).name == "CinemaSurface") { val = ((Component)val2).transform; break; } } } surface = (Object.op_Implicit((Object)(object)val) ? ((Component)val).GetComponent<Renderer>() : null); if (!Object.op_Implicit((Object)(object)surface)) { CinemaPlugin.Log.LogError((object)("Missing cinema surface on " + ((Object)this).name)); return; } view.Register<ZPackage>("VHC_Command_v1", (Action<long, ZPackage>)OnCommand); view.Register<string>("VHC_State_v1", (Action<long, string>)OnState); view.Register<string>("VHC_Reply_v1", (Action<long, string>)OnReply); screens.Add(this); initialized = true; ReadPersisted(); if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsDedicated()) { playback = ((Component)this).gameObject.AddComponent<CinemaPlayback>(); playback.Initialize(this, surface); } } private void Update() { if (initialized && Object.op_Implicit((Object)(object)view) && view.IsValid()) { if (Time.unscaledTime >= nextPoll) { nextPoll = Time.unscaledTime + 0.35f; ReadPersisted(); } CorrectWorldClockJump(); if (Object.op_Implicit((Object)(object)playback)) { playback.Tick(ShouldDecode()); } } } private void CorrectWorldClockJump() { double networkTime = NetworkTime; float realtimeSinceStartup = Time.realtimeSinceStartup; bool flag = view.IsOwner(); if (clockInitialized && flag && clockWasOwner && state.Playing && !state.IsLive && clockRevision == state.Revision) { double num = Math.Max(0f, realtimeSinceStartup - lastRealTime); clockError += networkTime - lastNetworkTime - num; if (Math.Abs(clockError) > 0.75 && state.Revision < long.MaxValue) { state.Position = Math.Max(0.0, Math.Min(31536000.0, state.ExpectedPosition(networkTime) - clockError)); state.AnchorTime = networkTime; state.Revision++; persisted = state.Serialize(); view.GetZDO().Set("ValheimCinema.State.v1", persisted); view.InvokeRPC(ZNetView.Everybody, "VHC_State_v1", new object[1] { persisted }); clockError = 0.0; } } else { clockError = 0.0; } lastNetworkTime = networkTime; lastRealTime = realtimeSinceStartup; clockWasOwner = flag; clockInitialized = true; clockRevision = state.Revision; } public float DistanceTo(Vector3 point) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_001d: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)surface)) { Bounds bounds = surface.bounds; return Vector3.Distance(point, ((Bounds)(ref bounds)).ClosestPoint(point)); } return Vector3.Distance(point, ((Component)this).transform.position); } private bool ShouldDecode() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!Object.op_Implicit((Object)(object)localPlayer) || !CinemaPlugin.PlaybackEnabled.Value || string.IsNullOrEmpty(state.Url)) { return false; } float num = DistanceTo(((Component)localPlayer).transform.position); if (num > CinemaPlugin.ViewingDistance.Value) { return false; } int num2 = 0; foreach (CinemaScreen screen in screens) { if (Object.op_Implicit((Object)(object)screen) && !((Object)(object)screen == (Object)(object)this) && !string.IsNullOrEmpty(screen.state.Url)) { float num3 = screen.DistanceTo(((Component)localPlayer).transform.position); if (num3 < num || (Math.Abs(num3 - num) < 0.001f && ((Object)screen).GetInstanceID() < ((Object)this).GetInstanceID())) { num2++; } } } return num2 < CinemaPlugin.MaxActiveScreens.Value; } private void ReadPersisted() { string text = view.GetZDO().GetString("ValheimCinema.State.v1", ""); if (!(text == persisted) && CinemaState.TryDeserialize(text, out var cinemaState) && cinemaState.Revision >= state.Revision) { state = cinemaState; persisted = text; } } private void OnState(long sender, string snapshot) { if (Object.op_Implicit((Object)(object)view) && view.IsValid() && sender == view.GetZDO().GetOwner() && CinemaState.TryDeserialize(snapshot, out var cinemaState) && cinemaState.Revision > state.Revision) { state = cinemaState; } } private void OnReply(long sender, string message) { if (Object.op_Implicit((Object)(object)view) && view.IsValid() && sender == view.GetZDO().GetOwner() && message != null && message.Length <= 300) { Status = message; } } private static Player SenderPlayer(long sender) { foreach (Player allPlayer in Player.GetAllPlayers()) { ZNetView val = (Object.op_Implicit((Object)(object)allPlayer) ? ((Component)allPlayer).GetComponent<ZNetView>() : null); if (Object.op_Implicit((Object)(object)val) && val.IsValid() && val.GetZDO().GetOwner() == sender) { return allPlayer; } } return null; } private bool HasWardAccess(Player player) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)player)) { return false; } bool flag = false; bool flag2 = false; IEnumerable enumerable = ((WardList == null) ? null : (WardList.GetValue(null) as IEnumerable)); if (enumerable == null || WardEnabled == null || WardInside == null || WardPermitted == null) { return false; } foreach (PrivateArea item in enumerable) { PrivateArea val = item; if (Object.op_Implicit((Object)(object)val) && (bool)WardEnabled.Invoke(val, null) && (bool)WardInside.Invoke(val, new object[2] { ((Component)this).transform.position, 0f })) { Piece component = ((Component)val).GetComponent<Piece>(); if ((Object.op_Implicit((Object)(object)component) && component.GetCreator() == player.GetPlayerID()) || (bool)WardPermitted.Invoke(val, new object[1] { player.GetPlayerID() })) { flag2 = true; } else { flag = true; } } } if (!flag2) { return !flag; } return true; } public bool CanControlLocal() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (initialized && Object.op_Implicit((Object)(object)localPlayer) && !((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting() && DistanceTo(((Component)localPlayer).transform.position) <= 4f) { return HasWardAccess(localPlayer); } return false; } private void OnCommand(long sender, ZPackage package) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)view) || !view.IsValid() || !view.IsOwner()) { return; } try { if (package == null || package.Size() > 12000) { return; } Player val = SenderPlayer(sender); if (!Object.op_Implicit((Object)(object)val) || ((Character)val).IsDead() || DistanceTo(((Component)val).transform.position) > 5f || !HasWardAccess(val)) { view.InvokeRPC(sender, "VHC_Reply_v1", new object[1] { "Подойдите к экрану. Нужен доступ к оберегу." }); } else { if (commandsAt.TryGetValue(sender, out var value) && Time.unscaledTime - value < 0.12f) { return; } commandsAt[sender] = Time.unscaledTime; if (commandsAt.Count > 100) { commandsAt.Clear(); commandsAt[sender] = Time.unscaledTime; } CinemaCommand cinemaCommand = (CinemaCommand)package.ReadInt(); string text = package.ReadString(); double position = package.ReadDouble(); bool live = package.ReadBool(); if (cinemaCommand == CinemaCommand.Load) { if (!MediaResolver.TryNormalizeUrl(text, out var normalized, out var error)) { view.InvokeRPC(sender, "VHC_Reply_v1", new object[1] { error }); return; } text = normalized; } ReadPersisted(); if (!state.TryApply(cinemaCommand, text, position, live, NetworkTime, out var error2)) { view.InvokeRPC(sender, "VHC_Reply_v1", new object[1] { error2 }); return; } persisted = state.Serialize(); view.GetZDO().Set("ValheimCinema.State.v1", persisted); view.InvokeRPC(ZNetView.Everybody, "VHC_State_v1", new object[1] { persisted }); } } catch (Exception ex) { CinemaPlugin.Log.LogWarning((object)("Cinema command rejected: " + ex.GetType().Name)); } } public void Request(CinemaCommand command, string url, double position, bool live) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown if (!CanControlLocal()) { Status = "Подойдите к экрану. Нужен доступ к оберегу."; return; } if (command == CinemaCommand.Stop && preflight != null) { preflight.Cancel(); CachedMedia = null; } if (command == CinemaCommand.Seek && IsLive) { Status = "У прямого эфира перемотка недоступна"; return; } ZPackage val = new ZPackage(); val.Write((int)command); val.Write(url ?? ""); val.Write(position); val.Write(live); view.InvokeRPC("VHC_Command_v1", new object[1] { val }); } public void LoadFromInput(string input) { if (!MediaResolver.TryNormalizeUrl(input, out var normalized, out var error)) { Status = error; return; } if (preflight != null) { preflight.Cancel(); preflight.Dispose(); } preflight = new CancellationTokenSource(); ((MonoBehaviour)this).StartCoroutine(ResolveInput(normalized, CinemaPlugin.Quality.Value, preflight)); } private IEnumerator ResolveInput(string url, int quality, CancellationTokenSource cancellation) { Status = "Получаем ссылку на видео…"; Task<ResolvedMedia> task = MediaResolver.ResolveAsync(url, quality, cancellation.Token); while (!task.IsCompleted) { yield return null; } if (cancellation.IsCancellationRequested || preflight != cancellation) { if (task.IsFaulted) { _ = task.Exception; } } else if (!task.IsCanceled) { if (task.IsFaulted) { Status = FriendlyError(task.Exception.GetBaseException().Message); CinemaPlugin.Log.LogWarning((object)("Media link: " + Status)); yield break; } CachedMedia = task.Result; CachedUrl = url; CachedQuality = quality; Title = (string.IsNullOrEmpty(CachedMedia.Title) ? "Valheim Cinema" : CachedMedia.Title); Status = "Загружаем видео…"; Request(CinemaCommand.Load, url, 0.0, CachedMedia.IsLive); } } internal static string FriendlyError(string message) { if (string.IsNullOrEmpty(message)) { return "Не удалось открыть видео. Проверьте ссылку и соединение."; } if (message.Length <= 280) { return message; } return message.Substring(0, 280) + "…"; } public float GetHoverOffset() { return 0f; } public string GetHoverName() { Piece component = ((Component)this).GetComponent<Piece>(); if (!Object.op_Implicit((Object)(object)component)) { return "Valheim Cinema"; } return component.m_name; } public string GetHoverText() { return GetHoverName() + "\n[<color=yellow><b>$KEY_Use</b></color>] Смотреть / управление видео"; } public bool Interact(Humanoid user, bool hold, bool alt) { if (hold || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return false; } if (!CanControlLocal()) { ((Character)user).Message((MessageType)2, "Нет доступа к экрану", 0, (Sprite)null, false); return false; } CinemaPanel.Open(this); return true; } public bool UseItem(Humanoid user, ItemData item) { return false; } private void OnDestroy() { screens.Remove(this); if (preflight != null) { preflight.Cancel(); preflight.Dispose(); preflight = null; } if ((Object)(object)CinemaPanel.ActiveScreen == (Object)(object)this) { CinemaPanel.Close(); } } } public enum CinemaCommand { Load, Play, Pause, Seek, Stop } public sealed class CinemaState { public const int MaxUrlLength = 2048; public const int MaxSnapshotLength = 9000; public const double MaxPosition = 31536000.0; public const double MaxNetworkTime = 315537897599.0; private const string WireVersion = "VC2"; private const NumberStyles WireNumber = NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent; private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture; private static readonly Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); public string Url = ""; public bool Playing; public double Position; public double AnchorTime; public long Revision; public long LoadRevision; public bool IsLive; public double ExpectedPosition(double networkNow) { double num = (IsFinite(Position) ? Math.Max(0.0, Math.Min(31536000.0, Position)) : 0.0); if (!Playing || string.IsNullOrEmpty(Url) || !IsValidClock(networkNow) || !IsValidClock(AnchorTime)) { return num; } double num2 = Math.Max(0.0, networkNow - AnchorTime); return Math.Min(31536000.0, num + num2); } public bool TryApply(CinemaCommand command, string url, double position, bool live, double networkNow, out string error) { error = ""; if (!IsValidClock(networkNow)) { return Fail("Недопустимое время сети.", out error); } if (command < CinemaCommand.Load || command > CinemaCommand.Stop) { return Fail("Неизвестная команда видео.", out error); } if (Revision < 0 || Revision == long.MaxValue || LoadRevision < 0 || LoadRevision > Revision) { return Fail("Недопустимая версия состояния экрана.", out error); } string url2 = Url; bool playing = Playing; bool isLive = IsLive; double position2 = Position; switch (command) { case CinemaCommand.Load: { if (!TryNormalizeUrl(url, out var normalized, out error)) { return false; } if (!IsValidPosition(position)) { return Fail("Недопустимая позиция видео.", out error); } url2 = normalized; playing = true; isLive = live; position2 = (live ? 0.0 : position); break; } case CinemaCommand.Stop: if (string.Equals(Url, "", StringComparison.Ordinal) && !Playing && !IsLive && Position == 0.0) { return true; } url2 = ""; playing = false; isLive = false; position2 = 0.0; break; default: if (!TryValidate(out error)) { return false; } if (Url.Length == 0) { return Fail("Сначала вставьте ссылку на видео.", out error); } switch (command) { case CinemaCommand.Play: if (Playing) { return true; } playing = true; break; case CinemaCommand.Pause: if (!Playing) { return true; } playing = false; position2 = ExpectedPosition(networkNow); break; case CinemaCommand.Seek: if (IsLive) { return Fail("Перемотка прямого эфира недоступна.", out error); } if (!IsValidPosition(position)) { return Fail("Недопустимая позиция видео.", out error); } position2 = position; break; } break; } Url = url2; Playing = playing; IsLive = isLive; Position = position2; AnchorTime = networkNow; Revision++; if (command == CinemaCommand.Load) { LoadRevision = Revision; } return true; } public bool TryValidate(out string error) { error = ""; if (Revision < 0 || LoadRevision < 0 || LoadRevision > Revision || !IsValidClock(AnchorTime) || !IsValidPosition(Position)) { return Fail("Повреждено состояние времени экрана.", out error); } if (Url == null) { return Fail("Повреждена ссылка экрана.", out error); } if (Url.Length == 0) { if (Playing || IsLive || Position != 0.0) { return Fail("Пустой экран содержит состояние воспроизведения.", out error); } return true; } if (!TryNormalizeUrl(Url, out var normalized, out error)) { return false; } if (!string.Equals(normalized, Url, StringComparison.Ordinal)) { return Fail("Ссылка экрана не нормализована.", out error); } return true; } public static bool TryNormalizeUrl(string input, out string normalized, out string error) { normalized = ""; error = ""; if (input == null || input.Length > 2048) { return Fail("Ссылка отсутствует или слишком длинная.", out error); } string text = input.Trim(); if (text.Length == 0) { return Fail("Вставьте ссылку на видео.", out error); } for (int i = 0; i < text.Length; i++) { if (char.IsControl(text[i]) || char.IsWhiteSpace(text[i])) { return Fail("Ссылка содержит недопустимые пробелы или символы.", out error); } } if (!Uri.TryCreate(text, UriKind.Absolute, out Uri result) || (!(result.Scheme == Uri.UriSchemeHttps) && !(result.Scheme == Uri.UriSchemeHttp)) || string.IsNullOrEmpty(result.Host) || !string.IsNullOrEmpty(result.UserInfo)) { return Fail("Нужна ссылка HTTP или HTTPS без логина и пароля.", out error); } try { StrictUtf8.GetByteCount(text); } catch (EncoderFallbackException) { return Fail("Ссылка содержит недопустимые символы.", out error); } normalized = text; return true; } public string Serialize() { if (!TryValidate(out var error)) { throw new InvalidOperationException(error); } return "VC2|" + Revision.ToString(Invariant) + "|" + LoadRevision.ToString(Invariant) + "|" + (Playing ? "1" : "0") + "|" + (IsLive ? "1" : "0") + "|" + Position.ToString("R", Invariant) + "|" + AnchorTime.ToString("R", Invariant) + "|" + Convert.ToBase64String(StrictUtf8.GetBytes(Url)); } public static string Serialize(CinemaState state) { if (state == null) { throw new ArgumentNullException("state"); } return state.Serialize(); } public static bool TryDeserialize(string wire, out CinemaState state) { string error; return TryDeserialize(wire, out state, out error); } public static bool TryDeserialize(string wire, out CinemaState state, out string error) { state = null; error = ""; if (string.IsNullOrEmpty(wire) || wire.Length > 9000) { return Fail("Недопустимый размер состояния экрана.", out error); } string[] array = wire.Split('|'); bool flag = array[0] == "VC1"; int num = ((!flag) ? 1 : 0); if (array.Length != 7 + num || (!flag && array[0] != "VC2") || (array[2 + num] != "0" && array[2 + num] != "1") || (array[3 + num] != "0" && array[3 + num] != "1")) { return Fail("Неизвестный или поврежденный формат экрана.", out error); } CinemaState cinemaState = new CinemaState(); if (!long.TryParse(array[1], NumberStyles.None, Invariant, out cinemaState.Revision) || (!flag && !long.TryParse(array[2], NumberStyles.None, Invariant, out cinemaState.LoadRevision)) || !double.TryParse(array[4 + num], NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, Invariant, out cinemaState.Position) || !double.TryParse(array[5 + num], NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent, Invariant, out cinemaState.AnchorTime)) { return Fail("Повреждены числовые поля экрана.", out error); } cinemaState.Playing = array[2 + num] == "1"; cinemaState.IsLive = array[3 + num] == "1"; try { cinemaState.Url = StrictUtf8.GetString(Convert.FromBase64String(array[6 + num])); } catch (FormatException) { return Fail("Повреждена ссылка экрана.", out error); } catch (DecoderFallbackException) { return Fail("Повреждена кодировка ссылки экрана.", out error); } if (!cinemaState.TryValidate(out error)) { return false; } state = cinemaState; return true; } public static bool IsFinite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } private static bool IsValidClock(double value) { if (IsFinite(value) && value >= 0.0) { return value <= 315537897599.0; } return false; } private static bool IsValidPosition(double value) { if (IsFinite(value) && value >= 0.0) { return value <= 31536000.0; } return false; } private static bool Fail(string message, out string error) { error = message; return false; } } public sealed class ResolvedMedia { public string VideoUrl; public string AudioUrl; public string Title; public string UserAgent; public string Referer; public bool IsLive; } public static class MediaResolver { private sealed class ChildProcessJob : IDisposable { private struct BasicLimits { public long ProcessTime; public long JobTime; public uint LimitFlags; public UIntPtr MinimumWorkingSet; public UIntPtr MaximumWorkingSet; public uint ActiveProcessLimit; public UIntPtr Affinity; public uint PriorityClass; public uint SchedulingClass; } private struct IoCounters { public ulong ReadOperations; public ulong WriteOperations; public ulong OtherOperations; public ulong ReadBytes; public ulong WriteBytes; public ulong OtherBytes; } private struct ExtendedLimits { public BasicLimits Basic; public IoCounters Io; public UIntPtr ProcessMemory; public UIntPtr JobMemory; public UIntPtr PeakProcessMemory; public UIntPtr PeakJobMemory; } private IntPtr handle; public ChildProcessJob() { if (Environment.OSVersion.Platform != PlatformID.Win32NT) { throw new PlatformNotSupportedException("Этот пакет видеоплеера предназначен для Windows."); } handle = CreateJobObject(IntPtr.Zero, null); if (handle == IntPtr.Zero) { throw new Win32Exception(Marshal.GetLastWin32Error()); } ExtendedLimits info = new ExtendedLimits { Basic = { LimitFlags = 8192u } }; if (!SetInformationJobObject(handle, 9, ref info, (uint)Marshal.SizeOf(typeof(ExtendedLimits)))) { int lastWin32Error = Marshal.GetLastWin32Error(); Dispose(); throw new Win32Exception(lastWin32Error); } } public void Assign(Process process) { if (!AssignProcessToJobObject(handle, process.Handle)) { throw new Win32Exception(Marshal.GetLastWin32Error(), "Не удалось изолировать процесс yt-dlp."); } } public void Dispose() { if (handle != IntPtr.Zero) { CloseHandle(handle); handle = IntPtr.Zero; } } [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateJobObject(IntPtr attributes, string name); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetInformationJobObject(IntPtr job, int infoClass, ref ExtendedLimits info, uint length); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr handle); } private sealed class MiniJson { private readonly string input; private int position; private int nodes; private MiniJson(string value) { input = value; } public static object Parse(string value) { if (value == null || value.Length > 8388608) { throw new InvalidDataException("Invalid JSON size."); } MiniJson miniJson = new MiniJson(value); object result = miniJson.Value(0); miniJson.Space(); if (miniJson.position != value.Length) { throw new InvalidDataException("Unexpected trailing JSON."); } return result; } private void Space() { while (position < input.Length && char.IsWhiteSpace(input[position])) { position++; } } private object Value(int depth) { if (depth > 64 || ++nodes > 250000) { throw new InvalidDataException("JSON complexity limit."); } Space(); if (position >= input.Length) { throw new InvalidDataException("Incomplete JSON."); } char c = input[position++]; switch (c) { case '"': return StringValue(); case '{': { Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal); Space(); if (Take('}')) { return dictionary; } do { Space(); if (!Take('"')) { throw new InvalidDataException("Invalid JSON key."); } string key = StringValue(); Space(); if (!Take(':')) { throw new InvalidDataException("Invalid JSON object."); } dictionary[key] = Value(depth + 1); Space(); if (Take('}')) { return dictionary; } } while (Take(',')); throw new InvalidDataException("Invalid JSON object separator."); } case '[': { List<object> list = new List<object>(); Space(); if (Take(']')) { return list; } do { list.Add(Value(depth + 1)); Space(); if (Take(']')) { return list; } } while (Take(',')); throw new InvalidDataException("Invalid JSON array separator."); } case 't': if (Literal("rue")) { return true; } break; } if (c == 'f' && Literal("alse")) { return false; } if (c == 'n' && Literal("ull")) { return null; } int num = position - 1; while (position < input.Length && "0123456789+-.eE".IndexOf(input[position]) >= 0) { position++; } if (double.TryParse(input.Substring(num, position - num), NumberStyles.Float, CultureInfo.InvariantCulture, out var result) && !double.IsInfinity(result) && !double.IsNaN(result)) { return result; } throw new InvalidDataException("Invalid JSON value."); } private bool Take(char c) { if (position < input.Length && input[position] == c) { position++; return true; } return false; } private bool Literal(string value) { if (position + value.Length > input.Length || string.CompareOrdinal(input, position, value, 0, value.Length) != 0) { return false; } position += value.Length; return true; } private string StringValue() { StringBuilder stringBuilder = new StringBuilder(); while (position < input.Length) { char c = input[position++]; if (c == '"') { return stringBuilder.ToString(); } if (c < ' ') { throw new InvalidDataException("Invalid JSON string."); } if (c != '\\') { stringBuilder.Append(c); continue; } if (position >= input.Length) { break; } c = input[position++]; switch (c) { case '"': case '/': case '\\': stringBuilder.Append(c); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': { if (position + 4 > input.Length || !ushort.TryParse(input.Substring(position, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { throw new InvalidDataException("Invalid JSON unicode."); } stringBuilder.Append((char)result); position += 4; break; } default: throw new InvalidDataException("Invalid JSON escape."); } } throw new InvalidDataException("Incomplete JSON string."); } } private const int ResolveTimeoutMs = 90000; private const int MaximumJsonCharacters = 8388608; internal static string YoutubeBrowserSession = ""; internal static string YoutubeCookiesFile = ""; internal static bool YoutubeUseImportedSession = true; private static readonly Regex YoutubeId = new Regex("^[A-Za-z0-9_-]{11}$", RegexOptions.CultureInvariant); public static bool TryNormalizeUrl(string input, out string normalized, out string error) { normalized = null; error = null; if (string.IsNullOrWhiteSpace(input) || input.Length > 2048) { return Fail("Вставьте ссылку YouTube, RuTube, VK Видео или прямую HTTP(S)-ссылку.", out error); } input = input.Trim(); string text = input; foreach (char c in text) { if (char.IsControl(c) || c == '\\') { return Fail("Ссылка содержит недопустимые символы.", out error); } } if (input.StartsWith("www.youtube.com/", StringComparison.OrdinalIgnoreCase) || input.StartsWith("youtube.com/", StringComparison.OrdinalIgnoreCase) || input.StartsWith("youtu.be/", StringComparison.OrdinalIgnoreCase)) { input = "https://" + input; } if (!Uri.TryCreate(input, UriKind.Absolute, out Uri result) || !IsPublicHttpUri(result)) { return Fail("Нужна публичная ссылка HTTP(S), без логина, пароля и локального адреса.", out error); } if (IsYoutubeHost(result.DnsSafeHost)) { if (!result.IsDefaultPort) { return Fail("Для YouTube используйте стандартную ссылку HTTPS.", out error); } string text2 = null; string text3 = result.AbsolutePath.Trim('/'); string[] array = text3.Split('/'); if (result.DnsSafeHost.Equals("youtu.be", StringComparison.OrdinalIgnoreCase)) { text2 = array[0]; } else if (text3.Equals("watch", StringComparison.OrdinalIgnoreCase)) { text2 = QueryValue(result.Query, "v"); } else if (array.Length == 2 && (array[0] == "shorts" || array[0] == "live" || array[0] == "embed")) { text2 = array[1]; } if (string.IsNullOrEmpty(text2) || !YoutubeId.IsMatch(text2)) { return Fail("Вставьте ссылку на конкретное видео или прямой эфир YouTube.", out error); } normalized = "https://www.youtube.com/watch?v=" + text2; return true; } normalized = result.GetLeftPart(UriPartial.Path) + result.Query; return true; } public static Task<ResolvedMedia> ResolveAsync(string input, int maxHeight, CancellationToken token) { return Task.Run(delegate { token.ThrowIfCancellationRequested(); if (!TryNormalizeUrl(input, out var normalized, out var error)) { throw new ArgumentException(error); } Uri uri = new Uri(normalized); EnsurePublicDns(uri, token); string text = ProviderName(uri.DnsSafeHost); if (text == null) { return new ResolvedMedia { VideoUrl = normalized, Title = uri.DnsSafeHost + Uri.UnescapeDataString(uri.AbsolutePath), IsLive = uri.AbsolutePath.EndsWith(".m3u8", StringComparison.OrdinalIgnoreCase) }; } string text2 = FindToolsDirectory(); string text3 = Path.Combine(text2, "yt-dlp.exe"); string text4 = Path.Combine(text2, "deno.exe"); if (!File.Exists(text3) || !File.Exists(text4)) { throw new FileNotFoundException("Не найдены Tools/yt-dlp.exe и Tools/deno.exe рядом с ValheimCinema.dll. Установите полный пакет мода."); } string[] collection = new string[20] { "--ignore-config", "--no-playlist", "--no-cache-dir", "--no-plugin-dirs", "--no-remote-components", "--no-js-runtimes", "--js-runtimes", "deno:" + text4, "--socket-timeout", "15", "--extractor-retries", "1", "--retries", "1", "--skip-download", "--dump-single-json", "--no-progress", "--no-warnings", "--", normalized }; string text5 = null; try { List<string> list = new List<string>(collection); string importedFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "ValheimCinema", "Auth", "youtube.cookies.txt"); string text6 = ((text == "YouTube") ? SelectYoutubeCookiesFile(YoutubeCookiesFile, YoutubeBrowserSession, YoutubeUseImportedSession, importedFile) : null); if (!string.IsNullOrWhiteSpace(text6)) { string fullPath = Path.GetFullPath(Environment.ExpandEnvironmentVariables(text6.Trim())); if (!File.Exists(fullPath) || new FileInfo(fullPath).Length > 2097152) { throw new IOException("Личный файл сессии YouTube отсутствует или слишком большой. Повторите импорт сессии."); } string text7 = File.ReadAllText(fullPath); string[] array = text7.Split('\n'); foreach (string text8 in array) { string text9 = text8.TrimEnd('\r'); if (text9.StartsWith("#HttpOnly_", StringComparison.Ordinal)) { text9 = text9.Substring(10); } else if (text9.StartsWith("#", StringComparison.Ordinal) || text9.Length == 0) { continue; } string[] array2 = text9.Split('\t'); string text10 = array2[0].TrimStart('.').ToLowerInvariant(); if (array2.Length != 7 || (text10 != "youtube.com" && !text10.EndsWith(".youtube.com", StringComparison.Ordinal))) { throw new IOException("Файл сессии должен содержать только cookies YouTube. Используйте импорт сессии мода."); } } text5 = Path.Combine(Path.GetDirectoryName(fullPath), ".cinema-request-" + Guid.NewGuid().ToString("N") + ".tmp"); File.WriteAllText(text5, text7, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); list.InsertRange(0, new string[2] { "--cookies", text5 }); } else if (text == "YouTube" && !string.IsNullOrWhiteSpace(YoutubeBrowserSession)) { string text11 = YoutubeBrowserSession.Trim(); if (text11.Length > 2048 || text11.IndexOfAny(new char[3] { '\r', '\n', '\0' }) >= 0) { throw new ArgumentException("Неверная настройка локальной сессии YouTube."); } list.InsertRange(0, new string[2] { "--cookies-from-browser", text11 }); } ResolvedMedia resolvedMedia = SelectMedia(RunProcess(text3, list.ToArray(), text2, token, text == "YouTube", text5 != null || (text == "YouTube" && !string.IsNullOrWhiteSpace(YoutubeBrowserSession))), maxHeight, text); token.ThrowIfCancellationRequested(); EnsurePublicDns(new Uri(resolvedMedia.VideoUrl), token); if (resolvedMedia.AudioUrl != null) { EnsurePublicDns(new Uri(resolvedMedia.AudioUrl), token); } return resolvedMedia; } finally { if (text5 != null) { try { File.Delete(text5); } catch (IOException) { } } } }, token); } internal static ResolvedMedia SelectMedia(string json, int maxHeight, string provider) { int num = Math.Max(144, Math.Min(1080, maxHeight)); if (!(MiniJson.Parse(json) is Dictionary<string, object> obj)) { throw new InvalidDataException("Видеосервис вернул неверный ответ."); } bool flag = GetBool(obj, "is_live") || GetString(obj, "live_status") == "is_live"; if (GetString(obj, "live_status") == "is_upcoming") { throw new InvalidOperationException("Прямой эфир ещё не начался."); } if (!(GetValue(obj, "formats") is List<object> list)) { throw new InvalidOperationException(provider + " не предоставил доступных видеоформатов."); } Dictionary<string, object> dictionary = null; Dictionary<string, object> dictionary2 = null; double num2 = double.MinValue; double num3 = double.MinValue; foreach (object item in list) { if (!(item is Dictionary<string, object> dictionary3) || GetBool(dictionary3, "has_drm") || !IsUsableFormatUrl(GetString(dictionary3, "url"))) { continue; } string text = GetString(dictionary3, "protocol"); if (text != "https" && text != "http" && text != "m3u8_native" && text != "m3u8") { continue; } string text2 = GetString(dictionary3, "vcodec"); string text3 = GetString(dictionary3, "acodec"); bool flag2 = text2.StartsWith("avc1", StringComparison.OrdinalIgnoreCase) || text2.StartsWith("h264", StringComparison.OrdinalIgnoreCase); bool flag3 = text3.StartsWith("mp4a", StringComparison.OrdinalIgnoreCase) || text3.StartsWith("aac", StringComparison.OrdinalIgnoreCase); bool flag4 = text.StartsWith("m3u8", StringComparison.Ordinal); bool flag5 = provider == "VK Видео" && string.IsNullOrEmpty(text2) && string.IsNullOrEmpty(text3) && GetString(dictionary3, "ext") == "mp4"; bool flag6 = flag3 || flag5; double number = GetNumber(dictionary3, "height"); if ((flag2 || flag5) && number > 0.0 && number <= 4320.0 && (GetString(dictionary3, "ext") == "mp4" || flag4)) { if ((!flag || (flag4 && flag6)) && (!(text3 != "none") || string.IsNullOrEmpty(text3) || flag3)) { double num4 = ((number <= (double)num) ? (1000000000.0 + number * 100000.0) : ((0.0 - number) * 100000.0)) + Math.Min(GetNumber(dictionary3, "fps"), 60.0) * 100.0 + (double)(flag6 ? 5000 : 0) + (double)((!flag4) ? 1000 : 0) + Math.Min(GetNumber(dictionary3, "tbr"), 999.0); if (num4 > num2) { num2 = num4; dictionary = dictionary3; } } } else if (text2 == "none" && flag3 && !flag4 && GetString(dictionary3, "ext") == "m4a") { double num5 = GetNumber(dictionary3, "abr") * 100.0 + GetNumber(dictionary3, "asr") / 1000.0; if (num5 > num3) { num3 = num5; dictionary2 = dictionary3; } } } if (dictionary == null) { throw new InvalidOperationException(provider + " не предоставил совместимый видеопоток. Попробуйте другую ссылку."); } bool flag7 = (!(provider == "VK Видео") || !string.IsNullOrEmpty(GetString(dictionary, "vcodec")) || !string.IsNullOrEmpty(GetString(dictionary, "acodec"))) && (GetString(dictionary, "acodec") == "none" || string.IsNullOrEmpty(GetString(dictionary, "acodec"))); if (flag7 && dictionary2 == null) { throw new InvalidOperationException(provider + " не предоставил совместимую аудиодорожку AAC."); } string text4 = GetString(obj, "title").Replace('\n', ' ').Replace('\r', ' '); if (text4.Length > 160) { text4 = text4.Substring(0, 160); } Dictionary<string, object> obj2 = (GetValue(dictionary, "http_headers") as Dictionary<string, object>) ?? (GetValue(obj, "http_headers") as Dictionary<string, object>); ResolvedMedia resolvedMedia = new ResolvedMedia(); resolvedMedia.VideoUrl = GetString(dictionary, "url"); resolvedMedia.AudioUrl = (flag7 ? GetString(dictionary2, "url") : null); resolvedMedia.Title = text4; resolvedMedia.IsLive = flag; resolvedMedia.UserAgent = SafeHeader(GetString(obj2, "User-Agent")); resolvedMedia.Referer = SafeHeader(GetString(obj2, "Referer")); return resolvedMedia; } private static string SafeHeader(string value) { if (string.IsNullOrEmpty(value) || value.Length > 2048) { return null; } foreach (char c in value) { if (char.IsControl(c)) { return null; } } return value; } internal static string ProviderName(string host) { host = host.ToLowerInvariant(); if (IsYoutubeHost(host)) { return "YouTube"; } switch (host) { case "rutube.ru": case "www.rutube.ru": return "RuTube"; default: if (!host.EndsWith(".vkvideo.ru", StringComparison.Ordinal)) { return null; } goto case "vk.com"; case "vk.com": case "www.vk.com": case "m.vk.com": case "new.vk.com": case "vk.ru": case "www.vk.ru": case "m.vk.ru": case "vkvideo.ru": return "VK Видео"; } } private static string FindToolsDirectory() { string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); string text = Path.Combine(directoryName, "Tools"); if (File.Exists(Path.Combine(text, "yt-dlp.exe"))) { return text; } string directoryName2 = Path.GetDirectoryName(directoryName); if (directoryName2 != null) { return Path.Combine(directoryName2, "dependencies", "Tools"); } return text; } internal static string SelectYoutubeCookiesFile(string configuredFile, string browserSession, bool useImported, string importedFile) { if (!string.IsNullOrWhiteSpace(configuredFile)) { return configuredFile.Trim(); } if (!string.IsNullOrWhiteSpace(browserSession) || !useImported) { return null; } if (!File.Exists(importedFile)) { return null; } return importedFile; } internal static string DescribeResolverFailure(string diagnostic, int exitCode, bool youtube, bool authenticated) { string text = (diagnostic ?? "").ToLowerInvariant(); if (youtube && (text.Contains("could not copy chrome cookie database") || text.Contains("database is locked") || text.Contains("cookie database is locked"))) { return "Браузер блокирует сессию YouTube. Полностью закройте его и повторите импорт через Import-YouTube-Yandex.cmd."; } if (youtube && (text.Contains("failed to decrypt") || text.Contains("dpapi"))) { return "Не удалось прочитать сессию YouTube. Запустите импорт от того же пользователя Windows, что и браузер."; } if (youtube && (text.Contains("sign in") || text.Contains("confirm") || text.Contains("cookies are no longer valid") || text.Contains("cookies have expired"))) { if (!authenticated) { return "YouTube требует вход. Войдите в YouTube в браузере, закройте его и запустите Import-YouTube-Yandex.cmd в папке мода."; } return "YouTube отклонил сохранённую сессию. Войдите в YouTube в браузере, полностью закройте его и повторите импорт сессии."; } if (text.Contains("not available") || text.Contains("unavailable")) { return "Это видео сейчас недоступно с вашего подключения."; } return "Не удалось получить видео (yt-dlp, код " + exitCode + "). Проверьте подключение и актуальность Tools."; } private static string RunProcess(string executable, string[] arguments, string directory, CancellationToken token, bool youtube, bool authenticated) { StringBuilder stringBuilder = new StringBuilder(); foreach (string value in arguments) { if (stringBuilder.Length != 0) { stringBuilder.Append(' '); } stringBuilder.Append(QuoteArgument(value)); } ProcessStartInfo processStartInfo = new ProcessStartInfo(executable, stringBuilder.ToString()); processStartInfo.WorkingDirectory = directory; processStartInfo.UseShellExecute = false; processStartInfo.CreateNoWindow = true; processStartInfo.RedirectStandardOutput = true; processStartInfo.RedirectStandardError = true; processStartInfo.StandardOutputEncoding = Encoding.UTF8; processStartInfo.StandardErrorEncoding = Encoding.UTF8; ProcessStartInfo processStartInfo2 = processStartInfo; processStartInfo2.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8"; processStartInfo2.EnvironmentVariables["DENO_NO_UPDATE_CHECK"] = "1"; processStartInfo2.EnvironmentVariables["DENO_NO_PROMPT"] = "1"; processStartInfo2.EnvironmentVariables["NO_COLOR"] = "1"; using ChildProcessJob childProcessJob = new ChildProcessJob(); Process process = new Process { StartInfo = processStartInfo2 }; try { token.ThrowIfCancellationRequested(); if (!process.Start()) { throw new IOException("Не удалось запустить yt-dlp."); } try { childProcessJob.Assign(process); } catch { TryKill(process); throw; } Task<string> task = Task.Run(() => ReadBounded(process.StandardOutput, 8388608, failOnOverflow: true)); Task<string> task2 = Task.Run(() => ReadBounded(process.StandardError, 16000, failOnOverflow: false)); Stopwatch stopwatch = Stopwatch.StartNew(); try { while (!process.WaitForExit(100)) { token.ThrowIfCancellationRequested(); if (stopwatch.ElapsedMilliseconds > 90000) { throw new TimeoutException("YouTube не ответил за 90 секунд. Проверьте доступ к YouTube и попробуйте ещё раз."); } if (task.IsFaulted) { throw new InvalidDataException("Ответ YouTube слишком большой или повреждён."); } } token.ThrowIfCancellationRequested(); if (!Task.WaitAll(new Task[2] { task, task2 }, 5000)) { throw new IOException("Не удалось завершить чтение ответа yt-dlp."); } if (process.ExitCode != 0) { throw new InvalidOperationException(DescribeResolverFailure(task2.Result, process.ExitCode, youtube, authenticated)); } return task.Result; } finally { childProcessJob.Dispose(); TryKill(process); try { Task.WaitAll(new Task[2] { task, task2 }, 2000); } catch (AggregateException) { } } } finally { if (process != null) { ((IDisposable)process).Dispose(); } } } private static string ReadBounded(StreamReader reader, int limit, bool failOnOverflow) { char[] array = new char[4096]; StringBuilder stringBuilder = new StringBuilder(Math.Min(limit, 32768)); int num; while ((num = reader.Read(array, 0, array.Length)) > 0) { int num2 = limit - stringBuilder.Length; if (num > num2 && failOnOverflow) { throw new InvalidDataException("Resolver output limit exceeded."); } if (num2 > 0) { stringBuilder.Append(array, 0, Math.Min(num, num2)); } } return stringBuilder.ToString(); } private static void TryKill(Process process) { try { if (!process.HasExited) { process.Kill(); } } catch (InvalidOperationException) { } catch (Win32Exception) { } } private static string QuoteArgument(string value) { StringBuilder stringBuilder = new StringBuilder("\""); int num = 0; foreach (char c in value) { switch (c) { case '\\': num++; continue; case '"': stringBuilder.Append('\\', num * 2 + 1); stringBuilder.Append('"'); break; default: stringBuilder.Append('\\', num); stringBuilder.Append(c); break; } num = 0; } stringBuilder.Append('\\', num * 2); return stringBuilder.Append('"').ToString(); } private static bool IsYoutubeHost(string host) { if (!host.Equals("youtube.com", StringComparison.OrdinalIgnoreCase) && !host.Equals("www.youtube.com", StringComparison.OrdinalIgnoreCase) && !host.Equals("m.youtube.com", StringComparison.OrdinalIgnoreCase) && !host.Equals("music.youtube.com", StringComparison.OrdinalIgnoreCase) && !host.Equals("youtu.be", StringComparison.OrdinalIgnoreCase)) { return host.Equals("www.youtube-nocookie.com", StringComparison.OrdinalIgnoreCase); } return true; } internal static bool IsSafeResolvedUrl(string url) { return IsUsableFormatUrl(url); } private static bool IsUsableFormatUrl(string url) { if (!string.IsNullOrEmpty(url) && url.Length <= 65536 && Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { return IsPublicHttpUri(result); } return false; } private static bool IsPublicHttpUri(Uri uri) { if ((uri.Scheme != "https" && uri.Scheme != "http") || !string.IsNullOrEmpty(uri.UserInfo)) { return false; } string text = uri.DnsSafeHost.TrimEnd('.').ToLowerInvariant(); if (string.IsNullOrEmpty(text) || text == "localhost" || text.EndsWith(".localhost", StringComparison.Ordinal) || text.EndsWith(".local", StringComparison.Ordinal) || text.EndsWith(".internal", StringComparison.Ordinal) || text.EndsWith(".lan", StringComparison.Ordinal)) { return false; } if (IPAddress.TryParse(text.Trim('[', ']'), out IPAddress address)) { return IsPublicAddress(address); } if (uri.HostNameType == UriHostNameType.Dns) { return text.IndexOf('.') > 0; } return false; } private static bool IsPublicAddress(IPAddress address) { if (IPAddress.IsLoopback(address)) { return false; } if (address.AddressFamily == AddressFamily.InterNetworkV6) { if (address.IsIPv4MappedToIPv6) { return IsPublicAddress(address.MapToIPv4()); } byte[] addressBytes = address.GetAddressBytes(); if ((addressBytes[0] & 0xE0) == 32) { if (addressBytes[0] == 32 && addressBytes[1] == 1 && addressBytes[2] == 13) { return addressBytes[3] != 184; } return true; } return false; } byte[] addressBytes2 = address.GetAddressBytes(); if (addressBytes2[0] != 0 && addressBytes2[0] != 10 && addressBytes2[0] != 127 && addressBytes2[0] < 224 && (addressBytes2[0] != 100 || addressBytes2[1] < 64 || addressBytes2[1] > 127) && (addressBytes2[0] != 169 || addressBytes2[1] != 254) && (addressBytes2[0] != 172 || addressBytes2[1] < 16 || addressBytes2[1] > 31) && (addressBytes2[0] != 192 || (addressBytes2[1] != 168 && addressBytes2[1] != 0)) && (addressBytes2[0] != 198 || (addressBytes2[1] != 18 && addressBytes2[1] != 19 && (addressBytes2[1] != 51 || addressBytes2[2] != 100)))) { if (addressBytes2[0] == 203 && addressBytes2[1] == 0) { return addressBytes2[2] != 113; } return true; } return false; } private static void EnsurePublicDns(Uri uri, CancellationToken token) { IAsyncResult asyncResult = Dns.BeginGetHostAddresses(uri.DnsSafeHost, null, null); using WaitHandle waitHandle = asyncResult.AsyncWaitHandle; Stopwatch stopwatch = Stopwatch.StartNew(); while (!waitHandle.WaitOne(100)) { token.ThrowIfCancellationRequested(); if (stopwatch.ElapsedMilliseconds > 10000) { throw new TimeoutException("Не удалось определить адрес видеосервиса за 10 секунд."); } } token.ThrowIfCancellationRequested(); IPAddress[] array = Dns.EndGetHostAddresses(asyncResult); if (array.Length == 0) { throw new IOException("Адрес видеосервиса не найден."); } IPAddress[] array2 = array; foreach (IPAddress address in array2) { if (!IsPublicAddress(address)) { throw new InvalidOperationException("Ссылки на локальные и частные адреса не разрешены."); } } } private static string QueryValue(string query, string key) { string[] array = query.TrimStart('?').Split('&'); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0 && text.Substring(0, num) == key) { return Uri.UnescapeDataString(text.Substring(num + 1)); } } return null; } private static bool Fail(string message, out string error) { error = message; return false; } private static object GetValue(Dictionary<string, object> obj, string name) { if (obj == null || !obj.TryGetValue(name, out var value)) { return null; } return value; } private static string GetString(Dictionary<string, object> obj, string name) { return (GetValue(obj, name) as string) ?? string.Empty; } private static bool GetBool(Dictionary<string, object> obj, string name) { object value = GetValue(obj, name); if (value is bool) { return (bool)value; } return false; } private static double GetNumber(Dictionary<string, object> obj, string name) { object value = GetValue(obj, name); if (!(value is double)) { return 0.0; } return (double)value; } } public static class ScreenPrefabs { internal static readonly List<GameObject> Prefabs = new List<GameObject>(); private static readonly int[] Sizes = new int[5] { 32, 65, 120, 250, 500 }; private static readonly int[] WoodCosts = new int[5] { 6, 10, 16, 28, 48 }; private static readonly int[] NailCosts = new int[5] { 2, 4, 6, 10, 16 }; private static readonly FieldInfo NamedPrefabs = typeof(ZNetScene).GetField("m_namedPrefabs", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private static GameObject container; private static Material wood; private static Material darkWood; private static Material bronze; private static Material bezel; private static Material screen; private static Material led; private static ObjectDB pendingDatabase; internal static void Register(ZNetScene scene) { if ((Object)(object)scene == (Object)null) { return; } if (Prefabs.Count == 0) { BuildPrefabs(scene); } Dictionary<int, GameObject> dictionary = ((NamedPrefabs == null) ? null : (NamedPrefabs.GetValue(scene) as Dictionary<int, GameObject>)); if (dictionary == null) { throw new InvalidOperationException("ValheimCinema: ZNetScene prefab registry is unavailable."); } foreach (GameObject prefab in Prefabs) { if (!scene.m_prefabs.Contains(prefab)) { scene.m_prefabs.Add(prefab); } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); if (dictionary.TryGetValue(stableHashCode, out var value) && (Object)(object)value != (Object)(object)prefab) { throw new InvalidOperationException("ValheimCinema: prefab name/hash collision: " + ((Object)prefab).name); } dictionary[stableHashCode] = prefab; } ObjectDB val = (((Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance : pendingDatabase); if ((Object)(object)val != (Object)null) { AddToHammer(val); } } internal static void AddToHammer(ObjectDB database) { //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Expected O, but got Unknown if ((Object)(object)database == (Object)null) { return; } pendingDatabase = database; if (Prefabs.Count == 0) { return; } GameObject itemPrefab = database.GetItemPrefab("Hammer"); GameObject itemPrefab2 = database.GetItemPrefab("Wood"); GameObject itemPrefab3 = database.GetItemPrefab("BronzeNails"); if ((Object)(object)itemPrefab == (Object)null || (Object)(object)itemPrefab2 == (Object)null || (Object)(object)itemPrefab3 == (Object)null) { return; } ItemDrop component = itemPrefab.GetComponent<ItemDrop>(); ItemDrop component2 = itemPrefab2.GetComponent<ItemDrop>(); ItemDrop component3 = itemPrefab3.GetComponent<ItemDrop>(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || (Object)(object)component3 == (Object)null || (Object)(object)component.m_itemData.m_shared.m_buildPieces == (Object)null) { return; } PieceTable buildPieces = component.m_itemData.m_shared.m_buildPieces; CraftingStation val = null; if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab("piece_workbench"); if ((Object)(object)prefab != (Object)null) { val = prefab.GetComponent<CraftingStation>(); } } for (int i = 0; i < Prefabs.Count; i++) { GameObject val2 = Prefabs[i]; int num = i % Sizes.Length; bool flag = i >= Sizes.Length; Piece component4 = val2.GetComponent<Piece>(); Requirement[] array = (Requirement[])(object)new Requirement[2]; Requirement val3 = new Requirement(); val3.m_resItem = component2; val3.m_amount = WoodCosts[num] + (flag ? 8 : 0); val3.m_amountPerLevel = 0; val3.m_recover = true; array[0] = val3; Requirement val4 = new Requirement(); val4.m_resItem = component3; val4.m_amount = NailCosts[num]; val4.m_amountPerLevel = 0; val4.m_recover = true; array[1] = val4; component4.m_resources = array; if ((Object)(object)val != (Object)null) { component4.