Decompiled source of ValheimCinema v0.1.5

BepInEx/plugins/ValheimCinema/ValheimCinema.dll

Decompiled a week ago
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.