Decompiled source of PortalPeek v1.0.0

plugins/PortalPeek.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("PortalPeek")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("PortalPeek")]
[assembly: AssemblyTitle("PortalPeek")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace PortalPeek
{
	internal static class PeekCapturer
	{
		private sealed class InRange
		{
			public float Since;

			public string Key;
		}

		private sealed class FrameJob
		{
			public byte[] Pixels;

			public int Faces;

			public int FacesDone;

			public int Returned;

			public bool Failed;

			public float Time;

			public Task<byte[]> Encode;
		}

		private sealed class DepthRig
		{
			private Camera m_cam;

			private Shader m_shader;

			private int m_n;

			private float m_maxDepth;

			private float[] m_depth;

			private int m_requested;

			private int m_returned;

			public bool Done => m_returned >= m_requested;

			public static DepthRig Create(Camera main, Transform parent, int n, float maxDepth, float[] depth)
			{
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Unknown result type (might be due to invalid IL or missing references)
				//IL_005e: Unknown result type (might be due to invalid IL or missing references)
				Shader depth2 = Shaders.Depth;
				if (!Object.op_Implicit((Object)(object)depth2))
				{
					return null;
				}
				GameObject val = new GameObject("Depth");
				val.transform.SetParent(parent, false);
				Camera val2 = val.AddComponent<Camera>();
				val2.CopyFrom(main);
				((Behaviour)val2).enabled = false;
				val2.renderingPath = (RenderingPath)1;
				val2.clearFlags = (CameraClearFlags)2;
				val2.backgroundColor = new Color(0.5f, 0.5f, 1f, 1f);
				val2.allowHDR = false;
				val2.allowMSAA = false;
				val2.depthTextureMode = (DepthTextureMode)0;
				val2.cullingMask = main.cullingMask & ~LayerMask.GetMask(new string[13]
				{
					"character", "character_net", "character_ghost", "character_noenv", "character_trigger", "hitbox", "ghost", "effect", "smoke", "Water",
					"WaterVolume", "UI", "viewblock"
				});
				val2.nearClipPlane = 0.05f;
				val2.farClipPlane = maxDepth;
				val2.aspect = 1f;
				val2.fieldOfView = 2f * Mathf.Atan(((float)n + 1f) / (float)n) * 57.29578f;
				return new DepthRig
				{
					m_cam = val2,
					m_shader = depth2,
					m_n = n,
					m_maxDepth = maxDepth,
					m_depth = depth
				};
			}

			public void Face(Vector3 origin, Quaternion rot, int f)
			{
				//IL_0065: Unknown result type (might be due to invalid IL or missing references)
				//IL_0066: Unknown result type (might be due to invalid IL or missing references)
				//IL_006d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0072: Unknown result type (might be due to invalid IL or missing references)
				//IL_0110: Unknown result type (might be due to invalid IL or missing references)
				int n = m_n;
				int side = n + 1;
				RenderTexture rt = RenderTexture.GetTemporary(side, side, 24, (RenderTextureFormat)0, (RenderTextureReadWrite)1);
				try
				{
					m_cam.targetTexture = rt;
					((Component)m_cam).transform.SetPositionAndRotation(origin, rot * CubeFaces.Rotation(f));
					m_cam.RenderWithShader(m_shader, "RenderType");
				}
				catch (Exception ex)
				{
					PortalPeekPlugin.Log.LogWarning((object)("GPU depth pass failed for a face, using raycasts there: " + ex.Message));
					RenderTexture.ReleaseTemporary(rt);
					return;
				}
				finally
				{
					if (Object.op_Implicit((Object)(object)m_cam))
					{
						m_cam.targetTexture = null;
					}
				}
				m_requested++;
				float[] depth = m_depth;
				float maxDepth = m_maxDepth;
				AsyncGPUReadback.Request((Texture)(object)rt, 0, (TextureFormat)4, (Action<AsyncGPUReadbackRequest>)delegate(AsyncGPUReadbackRequest req)
				{
					//IL_0030: Unknown result type (might be due to invalid IL or missing references)
					//IL_0035: Unknown result type (might be due to invalid IL or missing references)
					//IL_00db: Unknown result type (might be due to invalid IL or missing references)
					//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
					RenderTexture.ReleaseTemporary(rt);
					m_returned++;
					if (!((AsyncGPUReadbackRequest)(ref req)).hasError)
					{
						NativeArray<byte> data = ((AsyncGPUReadbackRequest)(ref req)).GetData<byte>(0);
						if (data.Length >= side * side * 4)
						{
							int num = side * side;
							for (int i = 0; i < side; i++)
							{
								for (int j = 0; j < side; j++)
								{
									int num2 = (i * side + j) * 4;
									float num3 = (float)(int)data[num2 + 2] / 255f + (float)(int)data[num2 + 3] / 65025f;
									if (!(num3 >= 0.995f))
									{
										Vector3 val = CubeFaces.Dir(f, (float)j * 2f / (float)n - 1f, (float)i * 2f / (float)n - 1f);
										float magnitude = ((Vector3)(ref val)).magnitude;
										int num4 = f * num + i * side + j;
										depth[num4] = Mathf.Min(depth[num4], num3 * maxDepth * magnitude);
									}
								}
							}
						}
					}
				});
			}

			public void Dispose()
			{
				if (Object.op_Implicit((Object)(object)m_cam))
				{
					Object.Destroy((Object)(object)((Component)m_cam).gameObject);
				}
				m_cam = null;
			}
		}

		private const float CheckCooldown = 60f;

		private const float RetryAfterCancel = 10f;

		private const int RayBuffer = 32;

		private static bool s_busy;

		private static int s_session;

		private static PeekPortal s_departing;

		private static bool s_departTried;

		private static PeekPortal s_capturing;

		private static readonly Dictionary<string, float> s_lastCheck = new Dictionary<string, float>();

		private static readonly Dictionary<int, InRange> s_inRange = new Dictionary<int, InRange>();

		private static readonly HashSet<int> s_seen = new HashSet<int>();

		private static readonly List<int> s_gone = new List<int>();

		private static readonly RaycastHit[] s_hits = (RaycastHit[])(object)new RaycastHit[32];

		private static readonly FieldRef<Player, GameObject> s_placementGhost = AccessTools.FieldRefAccess<Player, GameObject>("m_placementGhost");

		private const float ManualRange = 25f;

		private const int MaxEncoders = 2;

		private static readonly Stack<byte[]> s_pixelPool = new Stack<byte[]>();

		private const int RaysPerFrame = 1200;

		private static readonly List<Light> s_hiddenLights = new List<Light>();

		public static void Abort()
		{
			s_session++;
			s_busy = false;
			s_capturing = null;
			s_departing = null;
			s_inRange.Clear();
			s_lastCheck.Clear();
		}

		public static void OnDeparting(PeekPortal portal)
		{
			s_departing = portal;
			s_departTried = false;
			TryDepartureCapture();
		}

		private static void TryDepartureCapture()
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if (s_departTried || !Object.op_Implicit((Object)(object)s_departing))
			{
				return;
			}
			if (s_busy)
			{
				if ((Object)(object)s_capturing == (Object)(object)s_departing)
				{
					s_departTried = true;
				}
				return;
			}
			s_departTried = true;
			PeekPortal peekPortal = s_departing;
			if (Cfg.Enabled.Value && Cfg.CaptureEnabled.Value && Object.op_Implicit((Object)(object)ZNetScene.instance) && PeekLibrary.Ready && peekPortal.IsConnected && ZNetScene.instance.IsAreaReady(((Component)peekPortal).transform.position))
			{
				string text = PortalKey.For(peekPortal.Zdo);
				if (text != null && PeekLibrary.NeedsCaptureNow(text))
				{
					s_lastCheck[text] = Time.time;
					((MonoBehaviour)Runner.Instance).StartCoroutine(Capture(peekPortal, text, manual: false));
				}
			}
		}

		private static bool Rushing(PeekPortal portal)
		{
			Player localPlayer = Player.m_localPlayer;
			if (Object.op_Implicit((Object)(object)portal) && (Object)(object)s_departing == (Object)(object)portal && Object.op_Implicit((Object)(object)localPlayer))
			{
				return ((Character)localPlayer).IsTeleporting();
			}
			return false;
		}

		public static void Update()
		{
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_017b: Unknown result type (might be due to invalid IL or missing references)
			if (!Cfg.Enabled.Value || !Cfg.CaptureEnabled.Value || !Object.op_Implicit((Object)(object)ZNetScene.instance) || !PeekLibrary.Ready)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting())
			{
				s_inRange.Clear();
				if (Object.op_Implicit((Object)(object)localPlayer) && ((Character)localPlayer).IsTeleporting())
				{
					TryDepartureCapture();
				}
				else
				{
					s_departing = null;
				}
				return;
			}
			s_departing = null;
			float time = Time.time;
			float value = Cfg.SettleSeconds.Value;
			float num = Sq(Cfg.CaptureRadius.Value);
			Vector3 position = ((Component)localPlayer).transform.position;
			PeekPortal peekPortal = null;
			string key = null;
			float num2 = float.MaxValue;
			s_seen.Clear();
			foreach (PeekPortal item in PeekPortal.All)
			{
				if (!Object.op_Implicit((Object)(object)item) || !item.IsConnected)
				{
					continue;
				}
				Vector3 val = item.CaptureOriginWorld - position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				if (sqrMagnitude >= num)
				{
					continue;
				}
				int instanceID = ((Object)item).GetInstanceID();
				s_seen.Add(instanceID);
				if (!s_inRange.TryGetValue(instanceID, out var value2))
				{
					string text = PortalKey.For(item.Zdo);
					if (text != null)
					{
						s_inRange[instanceID] = new InRange
						{
							Since = time,
							Key = text
						};
					}
				}
				else if (!ZNetScene.instance.IsAreaReady(((Component)item).transform.position))
				{
					value2.Since = time;
				}
				else
				{
					if (s_busy || time - value2.Since < value || sqrMagnitude >= num2)
					{
						continue;
					}
					string key2 = value2.Key;
					if (s_lastCheck.TryGetValue(key2, out var value3) && time - value3 < 60f)
					{
						continue;
					}
					if (!PeekLibrary.NeedsCapture(key2, force: false))
					{
						if (!PeekLibrary.IsLoading(key2))
						{
							s_lastCheck[key2] = time;
						}
					}
					else
					{
						peekPortal = item;
						key = key2;
						num2 = sqrMagnitude;
					}
				}
			}
			s_gone.Clear();
			foreach (int key3 in s_inRange.Keys)
			{
				if (!s_seen.Contains(key3))
				{
					s_gone.Add(key3);
				}
			}
			foreach (int item2 in s_gone)
			{
				s_inRange.Remove(item2);
			}
			if (Object.op_Implicit((Object)(object)peekPortal))
			{
				s_lastCheck[key] = time;
				((MonoBehaviour)Runner.Instance).StartCoroutine(Capture(peekPortal, key, manual: false));
			}
		}

		public static void ForceNearest()
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!Object.op_Implicit((Object)(object)localPlayer) || !Cfg.Enabled.Value || !PeekLibrary.Ready)
			{
				return;
			}
			if (s_busy)
			{
				Notify("Portal Peek: already capturing");
				return;
			}
			PeekPortal peekPortal = Nearest(((Component)localPlayer).transform.position, 25f);
			if (!Object.op_Implicit((Object)(object)peekPortal))
			{
				Notify("Portal Peek: no connected portal nearby");
				return;
			}
			string text = PortalKey.For(peekPortal.Zdo);
			if (text != null)
			{
				s_lastCheck[text] = Time.time;
				((MonoBehaviour)Runner.Instance).StartCoroutine(Capture(peekPortal, text, manual: true));
			}
		}

		private static void Notify(string text)
		{
			Player localPlayer = Player.m_localPlayer;
			if (Object.op_Implicit((Object)(object)localPlayer))
			{
				((Character)localPlayer).Message((MessageType)1, text, 0, (Sprite)null, false);
			}
		}

		private static PeekPortal Nearest(Vector3 pos, float radius)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			PeekPortal result = null;
			float num = radius * radius;
			foreach (PeekPortal item in PeekPortal.All)
			{
				if (Object.op_Implicit((Object)(object)item) && item.IsConnected)
				{
					Vector3 val = item.CaptureOriginWorld - pos;
					float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
					if (sqrMagnitude < num)
					{
						num = sqrMagnitude;
						result = item;
					}
				}
			}
			return result;
		}

		private static bool StillValid(int session, PeekPortal portal, Vector3 origin, out string why)
		{
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			why = null;
			if (session != s_session)
			{
				why = "left the world";
			}
			else if (!Object.op_Implicit((Object)(object)ZNet.instance) || !Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				why = "disconnected";
			}
			else if (!Object.op_Implicit((Object)(object)portal))
			{
				why = "portal unloaded";
			}
			else
			{
				Player localPlayer = Player.m_localPlayer;
				if (!Object.op_Implicit((Object)(object)localPlayer))
				{
					why = "no local player";
				}
				else if (((Character)localPlayer).IsDead())
				{
					why = "player died";
				}
				else if (((Character)localPlayer).IsTeleporting() && (Object)(object)s_departing != (Object)(object)portal)
				{
					why = "player teleported";
				}
				else
				{
					Vector3 val = ((Component)localPlayer).transform.position - origin;
					if (((Vector3)(ref val)).sqrMagnitude > Sq(Mathf.Max(Cfg.CaptureRadius.Value, 25f) + 10f))
					{
						why = "player left the area";
					}
					else if (!Object.op_Implicit((Object)(object)ZoneSystem.instance) || !ZoneSystem.instance.IsZoneLoaded(origin))
					{
						why = "area unloading";
					}
				}
			}
			return why == null;
		}

		private static float Sq(float v)
		{
			return v * v;
		}

		private static IEnumerator Capture(PeekPortal portal, string key, bool manual)
		{
			int session = s_session;
			s_busy = true;
			s_capturing = portal;
			GameObject rig = null;
			DepthRig depthRig = null;
			List<Renderer> hidden = new List<Renderer>();
			if (manual)
			{
				Notify("Portal Peek: capturing portal view...");
			}
			PortalPeekPlugin.Log.LogInfo((object)("Capturing portal " + key + "..."));
			int faceSize = Cfg.FaceSize;
			int depthRes = Cfg.DepthResolution;
			int frameCount = Cfg.Frames;
			float fps = Cfg.FramesPerSecond.Value;
			float maxDepth = Cfg.MaxDepth.Value;
			Vector3 originLocal = Cfg.CaptureOrigin;
			try
			{
				GameCamera instance = GameCamera.instance;
				Camera main = PeekPortal.MainCamera();
				if (!Object.op_Implicit((Object)(object)instance) || !Object.op_Implicit((Object)(object)main) || !Object.op_Implicit((Object)(object)portal))
				{
					yield break;
				}
				Camera sky = instance.m_skyCamera;
				Transform transform = ((Component)portal).transform;
				Vector3 origin = transform.TransformPoint(originLocal);
				Quaternion rot = transform.rotation;
				if (!StillValid(session, portal, origin, out var why))
				{
					PortalPeekPlugin.Log.LogInfo((object)("Capture of " + key + " skipped (" + why + ")."));
					yield break;
				}
				rig = new GameObject("PortalPeek_CaptureRig");
				rig.SetActive(false);
				Camera skyCopy = (Object.op_Implicit((Object)(object)sky) ? CopyCamera(sky, rig.transform, "Sky") : null);
				Camera mainCopy = CopyCamera(main, rig.transform, "Main");
				rig.SetActive(true);
				int side = depthRes + 1;
				float[] depth = new float[5 * side * side];
				for (int i = 0; i < depth.Length; i++)
				{
					depth[i] = maxDepth;
				}
				depthRig = DepthRig.Create(main, rig.transform, depthRes, maxDepth, depth);
				if (depthRig != null)
				{
					for (int f = 0; f < 5; f++)
					{
						if (!StillValid(session, portal, origin, out var why2))
						{
							Cancelled(key, why2);
							yield break;
						}
						HideForCapture(portal, hidden);
						try
						{
							depthRig.Face(origin, rot, f);
						}
						finally
						{
							Unhide(hidden);
						}
						if (!Rushing(portal))
						{
							yield return null;
						}
					}
					float waitUntil = Time.time + 5f;
					while (!depthRig.Done && Time.time < waitUntil)
					{
						yield return null;
					}
				}
				int rowsPerSlice = Mathf.Max(1, 1200 / side);
				for (int f = 0; f < 5; f++)
				{
					for (int row = 0; row < side; row += rowsPerSlice)
					{
						if (!StillValid(session, portal, origin, out var why3))
						{
							Cancelled(key, why3);
							yield break;
						}
						RaycastRows(origin, rot, ((Component)portal).transform, f, row, Mathf.Min(side, row + rowsPerSlice), depthRes, maxDepth, depth);
						if (!Rushing(portal))
						{
							yield return null;
						}
					}
				}
				depthRig?.Dispose();
				depthRig = null;
				int size = Snapshot.TextureSizeFor(faceSize);
				FrameJob sides = new FrameJob
				{
					Pixels = new byte[size * size * 4],
					Faces = 4
				};
				for (int f = 1; f < 5; f++)
				{
					if (!StillValid(session, portal, origin, out var why4))
					{
						Cancelled(key, why4);
						yield break;
					}
					HideForCapture(portal, hidden);
					try
					{
						RenderFace(sides, f, mainCopy, skyCopy, main, sky, origin, rot, faceSize, size);
					}
					finally
					{
						Unhide(hidden);
					}
					if (!Rushing(portal))
					{
						yield return null;
					}
				}
				List<FrameJob> jobs = new List<FrameJob>();
				float start = Time.time;
				bool cutShort = false;
				for (int f = 0; f < frameCount; f++)
				{
					while (Time.time < start + (float)f / fps)
					{
						yield return null;
					}
					if (!StillValid(session, portal, origin, out var why5))
					{
						if (jobs.Count == 0 || session != s_session || !Object.op_Implicit((Object)(object)ZNet.instance))
						{
							Cancelled(key, why5);
							yield break;
						}
						PortalPeekPlugin.Log.LogInfo((object)$"Capture of {key} cut short ({why5}), keeping {jobs.Count} frame(s).");
						cutShort = true;
						break;
					}
					Reclaim(jobs);
					PumpEncodes(jobs, sides, size);
					FrameJob frameJob = new FrameJob
					{
						Pixels = Rent(size * size * 4),
						Faces = 1,
						Time = Time.time - start
					};
					jobs.Add(frameJob);
					HideForCapture(portal, hidden);
					try
					{
						RenderFace(frameJob, 0, mainCopy, skyCopy, main, sky, origin, rot, faceSize, size);
					}
					finally
					{
						Unhide(hidden);
					}
					yield return null;
				}
				float deadline = Time.time + 60f;
				while (Time.time < deadline)
				{
					PumpEncodes(jobs, sides, size);
					bool flag = sides.Failed || (sides.Encode != null && sides.Encode.IsCompleted);
					foreach (FrameJob item in jobs)
					{
						if (!item.Failed && (item.Encode == null || !item.Encode.IsCompleted))
						{
							flag = false;
							break;
						}
					}
					if (flag)
					{
						break;
					}
					if (session != s_session || !Object.op_Implicit((Object)(object)ZNet.instance))
					{
						yield break;
					}
					yield return null;
				}
				if (session != s_session || !Object.op_Implicit((Object)(object)ZNet.instance))
				{
					yield break;
				}
				if (sides.Failed || sides.Encode == null || sides.Encode.Status != TaskStatus.RanToCompletion)
				{
					PortalPeekPlugin.Log.LogWarning((object)("Capture of " + key + ": side views could not be captured."));
					yield break;
				}
				Snapshot snapshot = new Snapshot
				{
					Key = key,
					Version = DateTime.UtcNow.Ticks,
					Origin = originLocal,
					FaceSize = faceSize,
					DepthRes = depthRes,
					Fps = fps,
					DayFraction = (Object.op_Implicit((Object)(object)EnvMan.instance) ? EnvMan.instance.GetDayFraction() : 0f),
					MaxDepth = maxDepth,
					Depth = depth,
					Sides = sides.Encode.Result
				};
				List<byte[]> list = new List<byte[]>();
				List<float> list2 = new List<float>();
				foreach (FrameJob item2 in jobs)
				{
					if (!item2.Failed && item2.Encode != null && item2.Encode.Status == TaskStatus.RanToCompletion)
					{
						list.Add(item2.Encode.Result);
						list2.Add(item2.Time);
						continue;
					}
					break;
				}
				snapshot.Frames = list.ToArray();
				snapshot.FrameTimes = list2.ToArray();
				if (snapshot.FrameCount == 0)
				{
					PortalPeekPlugin.Log.LogWarning((object)("Capture of " + key + " produced no frames."));
					yield break;
				}
				if (cutShort || snapshot.FrameCount < frameCount)
				{
					snapshot.Version = PeekLibrary.StaleVersion();
				}
				PeekLibrary.Submit(snapshot);
				if (manual)
				{
					Notify("Portal Peek: portal view captured");
				}
				PortalPeekPlugin.Log.LogInfo((object)$"Captured {key}: {snapshot.FrameCount} frame(s) at {Snapshot.TextureSizeFor(faceSize)} px.");
			}
			finally
			{
				Unhide(hidden);
				depthRig?.Dispose();
				if (Object.op_Implicit((Object)(object)rig))
				{
					Object.Destroy((Object)(object)rig);
				}
				s_pixelPool.Clear();
				if (session == s_session)
				{
					s_busy = false;
					s_capturing = null;
				}
			}
		}

		private static void Cancelled(string key, string why)
		{
			PortalPeekPlugin.Log.LogInfo((object)("Capture of " + key + " cancelled (" + why + ")."));
			s_lastCheck[key] = Time.time - 60f + 10f;
		}

		private static void PumpEncodes(List<FrameJob> jobs, FrameJob sides, int size)
		{
			int num = (Running(sides) ? 1 : 0);
			foreach (FrameJob job in jobs)
			{
				if (Running(job))
				{
					num++;
				}
			}
			if (num < 2 && StartEncode(sides, size))
			{
				num++;
			}
			foreach (FrameJob job2 in jobs)
			{
				if (num < 2 && StartEncode(job2, size))
				{
					num++;
				}
			}
		}

		private static bool Running(FrameJob j)
		{
			if (j != null && j.Encode != null)
			{
				return !j.Encode.IsCompleted;
			}
			return false;
		}

		private static bool StartEncode(FrameJob j, int size)
		{
			if (j == null || j.Encode != null || j.Failed || j.Pixels == null || j.FacesDone < j.Faces)
			{
				return false;
			}
			byte[] pixels = j.Pixels;
			j.Encode = Task.Run(() => Bc1.Encode(pixels, size));
			return true;
		}

		private static byte[] Rent(int size)
		{
			while (s_pixelPool.Count > 0)
			{
				byte[] array = s_pixelPool.Pop();
				if (array.Length == size)
				{
					return array;
				}
			}
			return new byte[size];
		}

		private static void Reclaim(List<FrameJob> jobs)
		{
			foreach (FrameJob job in jobs)
			{
				if (job.Pixels != null && job.Returned >= job.Faces && !((job.Encode == null) ? (!job.Failed) : (!job.Encode.IsCompleted)))
				{
					s_pixelPool.Push(job.Pixels);
					job.Pixels = null;
				}
			}
		}

		private static Camera CopyCamera(Camera source, Transform parent, string name)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent, false);
			Camera obj = val.AddComponent<Camera>();
			obj.CopyFrom(source);
			((Behaviour)obj).enabled = false;
			obj.depthTextureMode = source.depthTextureMode;
			obj.allowMSAA = false;
			obj.aspect = 1f;
			obj.rect = new Rect(0f, 0f, 1f, 1f);
			obj.targetTexture = null;
			return obj;
		}

		private static void RenderFace(FrameJob job, int face, Camera mainCopy, Camera skyCopy, Camera main, Camera sky, Vector3 origin, Quaternion rot, int faceSize, int width)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012c: 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_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_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: 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_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			Snapshot.FaceRect(faceSize, face, out var fx, out var fy, out var fs);
			Quaternion val = rot * CubeFaces.Rotation(face);
			float fieldOfView = Snapshot.FaceFov(fs);
			RenderTexture rt = RenderTexture.GetTemporary(fs, fs, 24, (RenderTextureFormat)0, (RenderTextureReadWrite)0);
			RenderTexture active = RenderTexture.active;
			RenderTexture.active = rt;
			GL.Clear(true, true, Color.black);
			RenderTexture.active = active;
			if (Object.op_Implicit((Object)(object)skyCopy))
			{
				Quaternion val2 = Quaternion.Inverse(((Component)main).transform.rotation);
				Vector3 val3 = val2 * (((Component)sky).transform.position - ((Component)main).transform.position);
				((Component)skyCopy).transform.SetPositionAndRotation(origin + val * val3, val * (val2 * ((Component)sky).transform.rotation));
				skyCopy.fieldOfView = fieldOfView;
				skyCopy.aspect = 1f;
				skyCopy.targetTexture = rt;
				skyCopy.Render();
				skyCopy.targetTexture = null;
			}
			((Component)mainCopy).transform.SetPositionAndRotation(origin, val);
			mainCopy.fieldOfView = fieldOfView;
			mainCopy.aspect = 1f;
			mainCopy.nearClipPlane = 0.05f;
			mainCopy.targetTexture = rt;
			mainCopy.Render();
			mainCopy.targetTexture = null;
			AsyncGPUReadback.Request((Texture)(object)rt, 0, (TextureFormat)4, (Action<AsyncGPUReadbackRequest>)delegate(AsyncGPUReadbackRequest req)
			{
				//IL_0051: Unknown result type (might be due to invalid IL or missing references)
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_0089: Unknown result type (might be due to invalid IL or missing references)
				RenderTexture.ReleaseTemporary(rt);
				job.Returned++;
				if (((AsyncGPUReadbackRequest)(ref req)).hasError)
				{
					job.Failed = true;
				}
				if (!job.Failed && job.Pixels != null)
				{
					NativeArray<byte> data = ((AsyncGPUReadbackRequest)(ref req)).GetData<byte>(0);
					if (data.Length < fs * fs * 4)
					{
						job.Failed = true;
					}
					else
					{
						int num = fs * 4;
						for (int i = 0; i < fs; i++)
						{
							NativeArray<byte>.Copy(data, i * num, job.Pixels, ((fy + i) * width + fx) * 4, num);
						}
						job.FacesDone++;
					}
				}
			});
		}

		private static void RaycastRows(Vector3 origin, Quaternion rot, Transform portal, int f, int rowStart, int rowEnd, int n, float maxDepth, float[] depth)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: 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_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: 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_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			int num = n + 1;
			int num2 = num * num;
			int mask = LayerMask.GetMask(new string[7] { "Default", "static_solid", "Default_small", "piece", "piece_nonsolid", "terrain", "vehicle" });
			float num3 = (Object.op_Implicit((Object)(object)ZoneSystem.instance) ? ZoneSystem.instance.m_waterLevel : 30f);
			for (int i = rowStart; i < rowEnd; i++)
			{
				for (int j = 0; j <= n; j++)
				{
					int num4 = f * num2 + i * num + j;
					Vector3 val = CubeFaces.Dir(f, (float)j * 2f / (float)n - 1f, (float)i * 2f / (float)n - 1f);
					Vector3 val2 = rot * ((Vector3)(ref val)).normalized;
					float num5 = Mathf.Min(depth[num4], NearestHit(origin, val2, depth[num4], mask, portal));
					if (origin.y > num3 && val2.y < -0.0001f)
					{
						num5 = Mathf.Min(num5, (origin.y - num3) / (0f - val2.y));
					}
					depth[num4] = Mathf.Clamp(num5, 0.15f, maxDepth);
				}
			}
		}

		private static float NearestHit(Vector3 origin, Vector3 dir, float maxDist, int mask, Transform ignore)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			int num = Physics.RaycastNonAlloc(origin, dir, s_hits, maxDist, mask, (QueryTriggerInteraction)1);
			float num2 = float.MaxValue;
			for (int i = 0; i < num; i++)
			{
				RaycastHit val = s_hits[i];
				if (!(((RaycastHit)(ref val)).distance >= num2) && Object.op_Implicit((Object)(object)((RaycastHit)(ref val)).collider) && (!Object.op_Implicit((Object)(object)ignore) || !((Component)((RaycastHit)(ref val)).collider).transform.IsChildOf(ignore)))
				{
					num2 = ((RaycastHit)(ref val)).distance;
				}
			}
			return num2;
		}

		private static void HideForCapture(PeekPortal portal, List<Renderer> hidden)
		{
			PeekPortal.SetCaptureMode(capturing: true);
			if (Object.op_Implicit((Object)(object)portal))
			{
				Hide(((Component)portal).gameObject, hidden);
			}
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if (!Object.op_Implicit((Object)(object)allPlayer))
				{
					continue;
				}
				Hide(((Component)allPlayer).gameObject, hidden);
				Light[] componentsInChildren = ((Component)allPlayer).GetComponentsInChildren<Light>();
				foreach (Light val in componentsInChildren)
				{
					if (((Behaviour)val).enabled)
					{
						((Behaviour)val).enabled = false;
						s_hiddenLights.Add(val);
					}
				}
			}
			Player localPlayer = Player.m_localPlayer;
			GameObject val2 = (Object.op_Implicit((Object)(object)localPlayer) ? s_placementGhost.Invoke(localPlayer) : null);
			if (Object.op_Implicit((Object)(object)val2))
			{
				Hide(val2, hidden);
			}
		}

		private static void Hide(GameObject root, List<Renderer> hidden)
		{
			Renderer[] componentsInChildren = root.GetComponentsInChildren<Renderer>();
			foreach (Renderer val in componentsInChildren)
			{
				if (!val.forceRenderingOff)
				{
					val.forceRenderingOff = true;
					hidden.Add(val);
				}
			}
		}

		private static void Unhide(List<Renderer> hidden)
		{
			foreach (Renderer item in hidden)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					item.forceRenderingOff = false;
				}
			}
			hidden.Clear();
			foreach (Light s_hiddenLight in s_hiddenLights)
			{
				if (Object.op_Implicit((Object)(object)s_hiddenLight))
				{
					((Behaviour)s_hiddenLight).enabled = true;
				}
			}
			s_hiddenLights.Clear();
			PeekPortal.SetCaptureMode(capturing: false);
		}
	}
	internal enum LoopMode
	{
		Crossfade,
		PingPong
	}
	internal enum QualityPreset
	{
		Low,
		Medium,
		High,
		Ultra,
		Custom
	}
	internal static class Cfg
	{
		public static ConfigEntry<bool> Enabled;

		public static ConfigEntry<bool> CaptureEnabled;

		public static ConfigEntry<KeyboardShortcut> ForceCaptureKey;

		public static ConfigEntry<QualityPreset> Quality;

		public static ConfigEntry<int> CustomResolution;

		public static ConfigEntry<int> CustomDepthResolution;

		public static ConfigEntry<int> CustomMaxActiveViews;

		public static ConfigEntry<float> CustomRenderScale;

		public static ConfigEntry<bool> Animate;

		public static ConfigEntry<float> ClipSeconds;

		public static ConfigEntry<float> FramesPerSecond;

		public static ConfigEntry<LoopMode> Loop;

		public static ConfigEntry<float> AnimationDistance;

		public static ConfigEntry<int> MaxLoadingClips;

		public static ConfigEntry<float> LookUpDegrees;

		public static ConfigEntry<float> MaxDistance;

		public static ConfigEntry<string> HideEffects;

		public static ConfigEntry<string> Openings;

		public static ConfigEntry<float> Shimmer;

		public static ConfigEntry<float> EdgeSoftness;

		public static ConfigEntry<float> CaptureRadius;

		public static ConfigEntry<float> SettleSeconds;

		public static ConfigEntry<float> RefreshMinutes;

		public static ConfigEntry<float> CaptureHeight;

		public static ConfigEntry<int> MemoryBudgetMB;

		public static ConfigEntry<float> MaxDepth;

		public static ConfigEntry<int> MaxTextureSize;

		public static ConfigEntry<string> StorageFolder;

		public static int FaceSize => Mathf.Clamp(Pick(256, 384, 512, 768, CustomResolution) / 4 * 4, 64, 1024);

		public static int DepthResolution => Mathf.Clamp(Pick(48, 64, 80, 112, CustomDepthResolution), 16, 160);

		public static int MaxActiveViews => Mathf.Clamp(Pick(6, 8, 12, 16, CustomMaxActiveViews), 1, 64);

		public static float RenderScale => Quality.Value switch
		{
			QualityPreset.Low => 0.75f, 
			QualityPreset.Custom => CustomRenderScale.Value, 
			_ => 1f, 
		};

		public static int Frames
		{
			get
			{
				if (!Animate.Value)
				{
					return 1;
				}
				return Mathf.Clamp(Mathf.RoundToInt(ClipSeconds.Value * FramesPerSecond.Value), 2, 160);
			}
		}

		public static Vector3 CaptureOrigin => new Vector3(0f, CaptureHeight.Value, 0.05f);

		public static void Bind(ConfigFile c)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Expected O, but got Unknown
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Expected O, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Expected O, but got Unknown
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Expected O, but got Unknown
			//IL_019e: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Expected O, but got Unknown
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Expected O, but got Unknown
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			//IL_0233: Expected O, but got Unknown
			//IL_0255: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Expected O, but got Unknown
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0297: 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_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0307: Expected O, but got Unknown
			//IL_0335: Unknown result type (might be due to invalid IL or missing references)
			//IL_033f: Expected O, but got Unknown
			//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b5: Expected O, but got Unknown
			//IL_03e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_03ed: Expected O, but got Unknown
			//IL_041b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0425: Expected O, but got Unknown
			//IL_0453: Unknown result type (might be due to invalid IL or missing references)
			//IL_045d: Expected O, but got Unknown
			//IL_048b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0495: Expected O, but got Unknown
			//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_04cd: Expected O, but got Unknown
			//IL_04fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0505: Expected O, but got Unknown
			Enabled = c.Bind<bool>("1 - General", "Enabled", true, "Show what is on the other side inside connected portals.");
			CaptureEnabled = c.Bind<bool>("1 - General", "AutoCapture", true, "Automatically snapshot a portal's surroundings when you stand near it for a few seconds. Snapshots are stored on this PC only; every player keeps their own.");
			ForceCaptureKey = c.Bind<KeyboardShortcut>("1 - General", "RecaptureKey", new KeyboardShortcut((KeyCode)289, (KeyCode[])(object)new KeyCode[1] { (KeyCode)306 }), "Re-snapshot the nearest connected portal right now (e.g. after building around it or at a nicer time of day).");
			Quality = c.Bind<QualityPreset>("2 - Quality", "Preset", QualityPreset.High, "Overall quality. Lower = lighter captures, less memory and smaller files.\n  Low    : 256 px side views / 512 px forward view. Smoothest; noticeably soft. Good for older PCs.\n  Medium : 384 / 768. Slightly soft up close.\n  High   : 512 / 1024 (default). Sharp.\n  Ultra  : 768 / 1536. Very sharp; heavier capture and loading. Strong PCs only.\n  Custom : use the Custom... settings below.\nAffects new snapshots; press the RecaptureKey near a portal to refresh one.");
			CustomResolution = c.Bind<int>("2 - Quality", "CustomResolution", 512, new ConfigDescription("Only with Preset = Custom. Pixels per side view; the forward view is twice this. 256 = fastest, 384 = balanced, 512 = sharp, 768 = very sharp. If you notice stutter, go down one step.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[7] { 256, 320, 384, 448, 512, 640, 768 }), Array.Empty<object>()));
			CustomDepthResolution = c.Bind<int>("2 - Quality", "CustomDepthDetail", 80, new ConfigDescription("Only with Preset = Custom. How finely distances are measured (per view edge). Higher = more accurate 3D around object edges. 48 = light, 80 = good, 112 = fine.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 128), Array.Empty<object>()));
			CustomMaxActiveViews = c.Bind<int>("2 - Quality", "CustomMaxPortals", 12, new ConfigDescription("Only with Preset = Custom. How many portals (closest first) show a peek at once. Each one costs a little GPU time; 6-12 is plenty.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 32), Array.Empty<object>()));
			CustomRenderScale = c.Bind<float>("2 - Quality", "CustomRenderScale", 1f, new ConfigDescription("Only with Preset = Custom. Peek sharpness relative to its size on screen. 1 = pixel-perfect, 0.75 = cheaper and slightly softer.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 1.5f), Array.Empty<object>()));
			Animate = c.Bind<bool>("3 - Animation", "Animated", true, "On: portals show a short looping clip (moving trees, water, clouds).\nOff: portals show a still image. Captures are much quicker and it uses far less memory and disk space. Existing clips just show their first frame.");
			ClipSeconds = c.Bind<float>("3 - Animation", "ClipSeconds", 3f, new ConfigDescription("Length of the captured clip. Longer = less obvious looping, but more memory, disk space and a longer capture.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 8f), Array.Empty<object>()));
			FramesPerSecond = c.Bind<float>("3 - Animation", "FramesPerSecond", 10f, new ConfigDescription("Captured frames per second. Playback blends between frames, so 8-12 already looks smooth. Lower = lighter.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(4f, 20f), Array.Empty<object>()));
			Loop = c.Bind<LoopMode>("3 - Animation", "LoopMode", LoopMode.Crossfade, "Crossfade: plays forward and blends the end into the start (default).\nPingPong: plays forward then backward.");
			AnimationDistance = c.Bind<float>("3 - Animation", "AnimationDistance", 25f, new ConfigDescription("Only portals closer than this (meters) play their clip; farther ones show a still frame.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 150f), Array.Empty<object>()));
			MaxLoadingClips = c.Bind<int>("3 - Animation", "MaxLoadingClips", 2, new ConfigDescription("How many portals (nearest first) may load their clip at the same time; the others show a still image until it is their turn. Lower = smoother when arriving at a base full of portals.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 8), Array.Empty<object>()));
			LookUpDegrees = c.Bind<float>("4 - Look", "LookUpDegrees", 12f, new ConfigDescription("Tilts the view through a portal upward. The third-person camera looks down, so without this you mostly see the ground on the other side. 0 = geometrically exact.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 35f), Array.Empty<object>()));
			Shimmer = c.Bind<float>("4 - Look", "Shimmer", 1f, new ConfigDescription("Strength of the magical ripple/swirl over the view (stronger toward the rim). 0 = a perfectly clear window, 1 = subtle (default), 2-3 = dreamy.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			EdgeSoftness = c.Bind<float>("4 - Look", "EdgeSoftness", 0.2f, new ConfigDescription("How far in from the rim the view fades out (fraction of the opening's radius). Hides the edge of the view inside the frame. 0.05 = crisp, 0.2 = soft (default), 0.4 = very soft.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.02f, 0.5f), Array.Empty<object>()));
			MaxDistance = c.Bind<float>("4 - Look", "MaxDistance", 40f, new ConfigDescription("Portals farther away than this (meters) keep their normal look.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 150f), Array.Empty<object>()));
			HideEffects = c.Bind<string>("4 - Look", "HideEffects", "Black_suck,suck particles", "Vanilla portal effect parts hidden while a peek is shown (comma-separated). Parts: Black_suck (dark inward smoke), suck particles (inward sparks), Particle System (swirling trails), blue flames, Runes and GroundFog (stone portal). Empty = hide nothing.");
			Openings = c.Bind<string>("4 - Look", "Openings", "portal_wood=0,1.37,1.42,1.42;portal_stone=0,2.4,2.25,2.25", "Shape of each portal's opening: prefab=centerX,centerY,radiusX,radiusY (meters). The frame hides a slightly oversized shape; only change this if the peek pokes out past the frame or leaves a gap. Applies instantly.");
			CaptureRadius = c.Bind<float>("5 - Capture", "ProximityRadius", 12f, new ConfigDescription("How close (meters) you must be to a connected portal for it to be snapshotted.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(2f, 40f), Array.Empty<object>()));
			SettleSeconds = c.Bind<float>("5 - Capture", "SettleSeconds", 5f, new ConfigDescription("Seconds you must be nearby (with the area fully loaded) before a snapshot is taken. Longer = never captures while things are still popping in after a teleport.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>()));
			RefreshMinutes = c.Bind<float>("5 - Capture", "RefreshMinutes", 30f, new ConfigDescription("A snapshot older than this is retaken the next time you hang around that portal.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 10080f), Array.Empty<object>()));
			CaptureHeight = c.Bind<float>("5 - Capture", "EyeHeight", 1.7f, new ConfigDescription("Height above the portal's base the snapshot is taken from (a standing player's eye).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 4f), Array.Empty<object>()));
			MemoryBudgetMB = c.Bind<int>("6 - Advanced", "MemoryBudgetMB", 1024, new ConfigDescription("Memory (mostly video memory) for loaded peeks. They stay loaded, so returning to a portal is instant, until this is exceeded; then the least recently seen are unloaded. One animated portal takes about 5 MB at Low, 12 at Medium, 22 at High, 49 at Ultra.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(128, 8192), Array.Empty<object>()));
			MaxDepth = c.Bind<float>("6 - Advanced", "MaxDepth", 400f, new ConfigDescription("Anything farther than this (meters), including the sky, is treated as being at this distance.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(100f, 600f), Array.Empty<object>()));
			MaxTextureSize = c.Bind<int>("6 - Advanced", "MaxTextureSize", 1536, new ConfigDescription("Upper bound for one peek's on-screen render, in pixels per side.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(256, 4096), Array.Empty<object>()));
			StorageFolder = c.Bind<string>("6 - Advanced", "StorageFolder", "", "Where snapshots are kept. Empty = <Valheim save folder>\\portalpeek\\<world>\\ (next to worlds_local, never inside your world or character files).");
			RemoveStaleEntries(c);
		}

		private static void RemoveStaleEntries(ConfigFile c)
		{
			try
			{
				if (AccessTools.Property(typeof(ConfigFile), "OrphanedEntries")?.GetValue(c) is IDictionary { Count: >0 } dictionary)
				{
					dictionary.Clear();
					c.Save();
				}
			}
			catch
			{
			}
		}

		private static int Pick(int low, int med, int high, int ultra, ConfigEntry<int> custom)
		{
			return Quality.Value switch
			{
				QualityPreset.Low => low, 
				QualityPreset.Medium => med, 
				QualityPreset.Ultra => ultra, 
				QualityPreset.Custom => custom.Value, 
				_ => high, 
			};
		}
	}
	[HarmonyPatch(typeof(TeleportWorld), "Awake")]
	internal static class TeleportWorldAwakePatch
	{
		private static void Postfix(TeleportWorld __instance)
		{
			try
			{
				if (Object.op_Implicit((Object)(object)__instance) && ((Behaviour)__instance).enabled && !PortalPeekPlugin.Headless && !Object.op_Implicit((Object)(object)((Component)__instance).GetComponent<PeekPortal>()))
				{
					((Component)__instance).gameObject.AddComponent<PeekPortal>();
				}
			}
			catch (Exception e)
			{
				Safe.Report("TeleportWorld.Awake", e);
			}
		}
	}
	[HarmonyPatch(typeof(TeleportWorld), "Teleport")]
	internal static class TeleportWorldTeleportPatch
	{
		private static void Postfix(TeleportWorld __instance, Player player)
		{
			try
			{
				if (Object.op_Implicit((Object)(object)__instance) && Object.op_Implicit((Object)(object)player) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && ((Character)player).IsTeleporting())
				{
					PeekPortal component = ((Component)__instance).GetComponent<PeekPortal>();
					if (Object.op_Implicit((Object)(object)component))
					{
						PeekCapturer.OnDeparting(component);
					}
				}
			}
			catch (Exception e)
			{
				Safe.Report("TeleportWorld.Teleport", e);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class ZNetOnDestroyPatch
	{
		private static void Postfix()
		{
			try
			{
				PeekCapturer.Abort();
				PeekLibrary.Reset();
			}
			catch (Exception e)
			{
				Safe.Report("ZNet.OnDestroy", e);
			}
		}
	}
	[BepInPlugin("tboods.PortalPeek", "Portal Peek", "1.0.0")]
	public sealed class PortalPeekPlugin : BaseUnityPlugin
	{
		public const string Guid = "tboods.PortalPeek";

		public const string Name = "Portal Peek";

		public const string Version = "1.0.0";

		internal static ManualLogSource Log;

		private Harmony m_harmony;

		internal static bool Headless => (int)SystemInfo.graphicsDeviceType == 4;

		private void Awake()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Cfg.Bind(((BaseUnityPlugin)this).Config);
			Runner.Create();
			m_harmony = new Harmony("tboods.PortalPeek");
			m_harmony.PatchAll(typeof(PortalPeekPlugin).Assembly);
			Log.LogInfo((object)("Portal Peek 1.0.0 loaded (" + (Headless ? "server, headless" : "client") + ")."));
		}

		private void OnDestroy()
		{
			Harmony harmony = m_harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}
	}
	internal sealed class Runner : MonoBehaviour
	{
		internal static Runner Instance { get; private set; }

		internal static void Create()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Expected O, but got Unknown
			if (!Object.op_Implicit((Object)(object)Instance))
			{
				GameObject val = new GameObject("PortalPeek_Runner");
				Object.DontDestroyOnLoad((Object)val);
				Instance = val.AddComponent<Runner>();
			}
		}

		private void Update()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			if (PortalPeekPlugin.Headless)
			{
				return;
			}
			try
			{
				PeekLibrary.Update();
			}
			catch (Exception e)
			{
				Safe.Report("Library.Update", e);
			}
			try
			{
				PeekPortal.UpdateBudget();
			}
			catch (Exception e2)
			{
				Safe.Report("Portal.Budget", e2);
			}
			try
			{
				PeekCapturer.Update();
				KeyboardShortcut value = Cfg.ForceCaptureKey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown() && !IsTyping())
				{
					PeekCapturer.ForceNearest();
				}
			}
			catch (Exception e3)
			{
				Safe.Report("Capturer.Update", e3);
			}
		}

		private static bool IsTyping()
		{
			if ((!Object.op_Implicit((Object)(object)Chat.instance) || !Chat.instance.HasFocus()) && !Console.IsVisible() && !TextInput.IsVisible())
			{
				return Menu.IsVisible();
			}
			return true;
		}
	}
	internal static class FileUtil
	{
		public static void WriteAtomic(string path, Action<Stream> write)
		{
			string text = path + ".tmp";
			using (FileStream obj = new FileStream(text, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				write(obj);
			}
			if (File.Exists(path))
			{
				try
				{
					File.Replace(text, path, null);
					return;
				}
				catch (PlatformNotSupportedException)
				{
				}
				catch (IOException)
				{
				}
				File.Delete(path);
			}
			File.Move(text, path);
		}
	}
	internal static class Safe
	{
		private const float RepeatSeconds = 10f;

		private static readonly Dictionary<string, float> s_lastReport = new Dictionary<string, float>();

		public static void Report(string where, Exception e)
		{
			try
			{
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				if (!s_lastReport.TryGetValue(where, out var value) || !(realtimeSinceStartup - value < 10f))
				{
					s_lastReport[where] = realtimeSinceStartup;
					PortalPeekPlugin.Log.LogError((object)$"[{where}] {e}");
				}
			}
			catch
			{
			}
		}
	}
	internal static class CubeFaces
	{
		public const int Count = 5;

		public static readonly Vector3[] Forward = (Vector3[])(object)new Vector3[5]
		{
			new Vector3(0f, 0f, 1f),
			new Vector3(1f, 0f, 0f),
			new Vector3(-1f, 0f, 0f),
			new Vector3(0f, 1f, 0f),
			new Vector3(0f, -1f, 0f)
		};

		public static readonly Vector3[] Up = (Vector3[])(object)new Vector3[5]
		{
			new Vector3(0f, 1f, 0f),
			new Vector3(0f, 1f, 0f),
			new Vector3(0f, 1f, 0f),
			new Vector3(0f, 0f, -1f),
			new Vector3(0f, 0f, 1f)
		};

		public static Vector3 Right(int f)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			return Vector3.Cross(Up[f], Forward[f]);
		}

		public static Quaternion Rotation(int f)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			return Quaternion.LookRotation(Forward[f], Up[f]);
		}

		public static Vector3 Dir(int f, float s, float t)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			return Forward[f] + Right(f) * s + Up[f] * t;
		}
	}
	internal static class PortalKey
	{
		public static string For(ZDO zdo)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: 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_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			if (zdo == null)
			{
				return null;
			}
			Vector3 position = zdo.GetPosition();
			Quaternion rotation = zdo.GetRotation();
			int num = (Mathf.RoundToInt(((Quaternion)(ref rotation)).eulerAngles.y) % 360 + 360) % 360;
			return $"{(uint)zdo.GetPrefab():x8}_{R(position.x)}_{R(position.y)}_{R(position.z)}_{num}";
		}

		private static int R(float v)
		{
			return Mathf.RoundToInt(v * 10f);
		}

		public static bool IsValid(string key)
		{
			if (string.IsNullOrEmpty(key) || key.Length > 80)
			{
				return false;
			}
			foreach (char c in key)
			{
				if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && c != '_' && c != '-')
				{
					return false;
				}
			}
			return true;
		}
	}
	internal sealed class Snapshot
	{
		public const uint Magic = 1263554640u;

		public const int Format = 4;

		public long Version;

		public string Key;

		public Vector3 Origin;

		public int FaceSize;

		public int DepthRes;

		public float Fps;

		public float DayFraction;

		public float MaxDepth;

		public float[] Depth;

		public float[] FrameTimes;

		public byte[] Sides;

		public byte[][] Frames;

		public string FilePath;

		public long ImagesStart;

		public int TextureSize => TextureSizeFor(FaceSize);

		public int SamplesPerFace => (DepthRes + 1) * (DepthRes + 1);

		public int ImageBytes => Bc1.DataSize(TextureSize);

		public int MipCount => Bc1.MipCount(TextureSize);

		public int FrameCount => Frames.Length;

		public static int TextureSizeFor(int s)
		{
			return s * 2;
		}

		public static int Gutter(int faceSize)
		{
			return Math.Max(4, faceSize / 32);
		}

		public static float FaceFov(int faceSize)
		{
			int num = Gutter(faceSize);
			return 2f * (float)Math.Atan((double)faceSize / (double)(faceSize - 2 * num)) * 57.29578f;
		}

		public static void FaceRect(int s, int f, out int x, out int y, out int size)
		{
			switch (f)
			{
			case 0:
				x = 0;
				y = 0;
				size = s * 2;
				break;
			case 1:
				x = 0;
				y = 0;
				size = s;
				break;
			case 2:
				x = s;
				y = 0;
				size = s;
				break;
			case 3:
				x = 0;
				y = s;
				size = s;
				break;
			default:
				x = s;
				y = s;
				size = s;
				break;
			}
		}

		public long HeldBytes()
		{
			long num = ((Sides != null) ? Sides.Length : 0);
			byte[][] frames = Frames;
			for (int i = 0; i < frames.Length; i++)
			{
				if (frames[i] != null)
				{
					num += frames[i].Length;
				}
			}
			return num;
		}

		public byte[] Prefix()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1263554640u);
			binaryWriter.Write(4);
			binaryWriter.Write(Version);
			binaryWriter.Write(Key ?? "");
			binaryWriter.Write(Origin.x);
			binaryWriter.Write(Origin.y);
			binaryWriter.Write(Origin.z);
			binaryWriter.Write(FaceSize);
			binaryWriter.Write(DepthRes);
			binaryWriter.Write(Fps);
			binaryWriter.Write(DayFraction);
			binaryWriter.Write(MaxDepth);
			binaryWriter.Write(Depth.Length);
			float[] depth = Depth;
			foreach (float num in depth)
			{
				binaryWriter.Write((ushort)Math.Min(65535, Math.Max(0, (int)Math.Round(num * 100f))));
			}
			binaryWriter.Write(ImageBytes);
			binaryWriter.Write(Frames.Length);
			for (int j = 0; j < Frames.Length; j++)
			{
				binaryWriter.Write((j < FrameTimes.Length) ? FrameTimes[j] : ((float)j / Math.Max(1f, Fps)));
			}
			binaryWriter.Flush();
			return memoryStream.ToArray();
		}

		public void ReleaseToFile()
		{
			Sides = null;
			byte[][] frames = Frames;
			for (int i = 0; i < frames.Length; i++)
			{
				frames[i] = null;
			}
		}

		private static FileStream OpenRead(string path)
		{
			return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
		}

		public static Snapshot FromFile(string path)
		{
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			using FileStream fileStream = OpenRead(path);
			using BinaryReader binaryReader = new BinaryReader(fileStream);
			Snapshot snapshot = new Snapshot();
			ReadHeader(binaryReader, out snapshot.Version, out snapshot.Key);
			snapshot.Origin = new Vector3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
			snapshot.FaceSize = binaryReader.ReadInt32();
			snapshot.DepthRes = binaryReader.ReadInt32();
			snapshot.Fps = binaryReader.ReadSingle();
			snapshot.DayFraction = binaryReader.ReadSingle();
			snapshot.MaxDepth = binaryReader.ReadSingle();
			if (snapshot.FaceSize < 16 || snapshot.FaceSize > 1024 || snapshot.FaceSize % 4 != 0)
			{
				throw new InvalidDataException("bad face size");
			}
			if (snapshot.DepthRes < 2 || snapshot.DepthRes > 256)
			{
				throw new InvalidDataException("bad depth resolution");
			}
			int num = binaryReader.ReadInt32();
			if (num != 5 * snapshot.SamplesPerFace)
			{
				throw new InvalidDataException("bad depth length");
			}
			snapshot.Depth = new float[num];
			for (int i = 0; i < num; i++)
			{
				snapshot.Depth[i] = (float)(int)binaryReader.ReadUInt16() / 100f;
			}
			if (binaryReader.ReadInt32() != snapshot.ImageBytes)
			{
				throw new InvalidDataException("bad image size");
			}
			int num2 = binaryReader.ReadInt32();
			if (num2 < 1 || num2 > 512)
			{
				throw new InvalidDataException("bad frame count");
			}
			snapshot.FrameTimes = new float[num2];
			float num3 = -1f;
			for (int j = 0; j < num2; j++)
			{
				float num4 = binaryReader.ReadSingle();
				if (float.IsNaN(num4) || num4 < num3 || num4 > 600f)
				{
					throw new InvalidDataException("bad frame time");
				}
				num3 = (snapshot.FrameTimes[j] = num4);
			}
			if (float.IsNaN(snapshot.Fps) || snapshot.Fps <= 0f)
			{
				snapshot.Fps = 10f;
			}
			snapshot.ImagesStart = fileStream.Position;
			if (fileStream.Length < snapshot.ImagesStart + ((long)num2 + 1L) * snapshot.ImageBytes)
			{
				throw new InvalidDataException("truncated");
			}
			snapshot.Sides = binaryReader.ReadBytes(snapshot.ImageBytes);
			snapshot.Frames = new byte[num2][];
			snapshot.Frames[0] = binaryReader.ReadBytes(snapshot.ImageBytes);
			snapshot.FilePath = path;
			return snapshot;
		}

		public byte[] ReadImage(int index)
		{
			using FileStream fileStream = OpenRead(FilePath);
			using BinaryReader binaryReader = new BinaryReader(fileStream);
			ReadHeader(binaryReader, out var version, out var key);
			if (version != Version || key != Key)
			{
				throw new InvalidDataException("snapshot file was replaced");
			}
			fileStream.Position = ImagesStart + ((long)index + 1L) * ImageBytes;
			byte[] array = binaryReader.ReadBytes(ImageBytes);
			if (array.Length != ImageBytes)
			{
				throw new InvalidDataException("truncated");
			}
			return array;
		}

		public static void ReadHeader(BinaryReader r, out long version, out string key)
		{
			if (r.ReadUInt32() != 1263554640)
			{
				throw new InvalidDataException("not a portal peek snapshot");
			}
			if (r.ReadInt32() != 4)
			{
				throw new InvalidDataException("unsupported snapshot format");
			}
			version = r.ReadInt64();
			key = r.ReadString();
		}

		public static bool TryReadHeader(string path, out long version, out string key)
		{
			version = 0L;
			key = null;
			try
			{
				using (FileStream input = OpenRead(path))
				{
					using BinaryReader r = new BinaryReader(input);
					ReadHeader(r, out version, out key);
				}
				return true;
			}
			catch
			{
				return false;
			}
		}
	}
	internal static class Bc1
	{
		private const int BlockBytes = 8;

		private static readonly ushort[] s_toLinear = BuildToLinear();

		private static readonly byte[] s_toSrgb = BuildToSrgb();

		public static int MipCount(int size)
		{
			int num = 1;
			while (size % 8 == 0)
			{
				size /= 2;
				num++;
			}
			return num;
		}

		public static int DataSize(int size)
		{
			int num = 0;
			int num2 = MipCount(size);
			int num3 = 0;
			while (num3 < num2)
			{
				num += size / 4 * (size / 4) * 8;
				num3++;
				size /= 2;
			}
			return num;
		}

		public static byte[] Encode(byte[] rgba, int size)
		{
			if (size <= 0 || size % 4 != 0 || rgba == null || rgba.Length < size * size * 4)
			{
				throw new ArgumentException("bad image for BC1");
			}
			byte[] array = new byte[DataSize(size)];
			int num = MipCount(size);
			int offset = 0;
			byte[] array2 = rgba;
			int[] block = new int[48];
			int[] pal = new int[12];
			for (int i = 0; i < num; i++)
			{
				if (i > 0)
				{
					array2 = Downsample(array2, size);
					size /= 2;
				}
				offset = EncodeLevel(array2, size, array, offset, block, pal);
			}
			return array;
		}

		private static int EncodeLevel(byte[] px, int size, byte[] output, int offset, int[] block, int[] pal)
		{
			int num = size / 4;
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					for (int k = 0; k < 4; k++)
					{
						int num2 = ((i * 4 + k) * size + j * 4) * 4;
						int num3 = 0;
						while (num3 < 4)
						{
							int num4 = (k * 4 + num3) * 3;
							block[num4] = px[num2];
							block[num4 + 1] = px[num2 + 1];
							block[num4 + 2] = px[num2 + 2];
							num3++;
							num2 += 4;
						}
					}
					EncodeBlock(block, output, offset, pal);
					offset += 8;
				}
			}
			return offset;
		}

		private static void EncodeBlock(int[] px, byte[] dst, int o, int[] pal)
		{
			int num = 255;
			int num2 = 255;
			int num3 = 255;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			int num8 = 0;
			int num9 = 0;
			for (int i = 0; i < 48; i += 3)
			{
				int num10 = px[i];
				int num11 = px[i + 1];
				int num12 = px[i + 2];
				num7 += num10;
				num8 += num11;
				num9 += num12;
				if (num10 < num)
				{
					num = num10;
				}
				if (num10 > num4)
				{
					num4 = num10;
				}
				if (num11 < num2)
				{
					num2 = num11;
				}
				if (num11 > num5)
				{
					num5 = num11;
				}
				if (num12 < num3)
				{
					num3 = num12;
				}
				if (num12 > num6)
				{
					num6 = num12;
				}
			}
			if (num == num4 && num2 == num5 && num3 == num6)
			{
				ushort num13 = To565(num, num2, num3);
				Write(dst, o, num13, num13, 0u);
				return;
			}
			float num14 = (float)num7 / 16f;
			float num15 = (float)num8 / 16f;
			float num16 = (float)num9 / 16f;
			float num17 = 0f;
			float num18 = 0f;
			float num19 = 0f;
			float num20 = 0f;
			float num21 = 0f;
			float num22 = 0f;
			for (int j = 0; j < 48; j += 3)
			{
				float num23 = (float)px[j] - num14;
				float num24 = (float)px[j + 1] - num15;
				float num25 = (float)px[j + 2] - num16;
				num17 += num23 * num23;
				num18 += num23 * num24;
				num19 += num23 * num25;
				num20 += num24 * num24;
				num21 += num24 * num25;
				num22 += num25 * num25;
			}
			float num26 = num4 - num;
			float num27 = num5 - num2;
			float num28 = num6 - num3;
			for (int k = 0; k < 4; k++)
			{
				float num29 = num26 * num17 + num27 * num18 + num28 * num19;
				float num30 = num26 * num18 + num27 * num20 + num28 * num21;
				float num31 = num26 * num19 + num27 * num21 + num28 * num22;
				float num32 = Math.Max(Math.Abs(num29), Math.Max(Math.Abs(num30), Math.Abs(num31)));
				if (num32 < 1E-06f)
				{
					break;
				}
				num26 = num29 / num32;
				num27 = num30 / num32;
				num28 = num31 / num32;
			}
			if (Math.Abs(num26) + Math.Abs(num27) + Math.Abs(num28) < 1E-06f)
			{
				num26 = 0.299f;
				num27 = 0.587f;
				num28 = 0.114f;
			}
			float num33 = float.MaxValue;
			float num34 = float.MinValue;
			int num35 = 0;
			int num36 = 0;
			for (int l = 0; l < 48; l += 3)
			{
				float num37 = (float)px[l] * num26 + (float)px[l + 1] * num27 + (float)px[l + 2] * num28;
				if (num37 < num33)
				{
					num33 = num37;
					num35 = l;
				}
				if (num37 > num34)
				{
					num34 = num37;
					num36 = l;
				}
			}
			ushort num38 = To565(px[num36], px[num36 + 1], px[num36 + 2]);
			ushort num39 = To565(px[num35], px[num35 + 1], px[num35 + 2]);
			uint indices;
			int num40 = Match(px, num38, num39, pal, out indices);
			if (num38 != num39 && Refit(px, indices, out var c, out var c2) && (c != num38 || c2 != num39) && Match(px, c, c2, pal, out var indices2) < num40)
			{
				num38 = c;
				num39 = c2;
				indices = indices2;
			}
			Write(dst, o, num38, num39, indices);
		}

		private static int Match(int[] px, ushort c0, ushort c1, int[] pal, out uint indices)
		{
			indices = 0u;
			Unpack(c0, out pal[0], out pal[1], out pal[2]);
			Unpack(c1, out pal[3], out pal[4], out pal[5]);
			for (int i = 0; i < 3; i++)
			{
				pal[6 + i] = (2 * pal[i] + pal[3 + i]) / 3;
				pal[9 + i] = (pal[i] + 2 * pal[3 + i]) / 3;
			}
			int num = 0;
			for (int j = 0; j < 16; j++)
			{
				int num2 = j * 3;
				int num3 = int.MaxValue;
				int num4 = 0;
				for (int k = 0; k < 4; k++)
				{
					int num5 = px[num2] - pal[k * 3];
					int num6 = px[num2 + 1] - pal[k * 3 + 1];
					int num7 = px[num2 + 2] - pal[k * 3 + 2];
					int num8 = num5 * num5 + num6 * num6 + num7 * num7;
					if (num8 < num3)
					{
						num3 = num8;
						num4 = k;
					}
				}
				num += num3;
				indices |= (uint)(num4 << j * 2);
			}
			return num;
		}

		private static bool Refit(int[] px, uint indices, out ushort c0, out ushort c1)
		{
			c0 = (c1 = 0);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			int num5 = 0;
			int num6 = 0;
			int num7 = 0;
			int num8 = 0;
			int num9 = 0;
			for (int i = 0; i < 16; i++)
			{
				int num10 = (int)((indices >> i * 2) & 3) switch
				{
					2 => 2, 
					1 => 0, 
					0 => 3, 
					_ => 1, 
				};
				int num11 = 3 - num10;
				int num12 = i * 3;
				num += num10 * num10;
				num2 += num11 * num11;
				num3 += num10 * num11;
				num4 += num10 * px[num12];
				num5 += num10 * px[num12 + 1];
				num6 += num10 * px[num12 + 2];
				num7 += num11 * px[num12];
				num8 += num11 * px[num12 + 1];
				num9 += num11 * px[num12 + 2];
			}
			float num13 = (float)num * (float)num2 - (float)num3 * (float)num3;
			if (Math.Abs(num13) < 0.001f)
			{
				return false;
			}
			float num14 = 3f / num13;
			c0 = To565(Clamp((float)(num4 * num2 - num7 * num3) * num14), Clamp((float)(num5 * num2 - num8 * num3) * num14), Clamp((float)(num6 * num2 - num9 * num3) * num14));
			c1 = To565(Clamp((float)(num7 * num - num4 * num3) * num14), Clamp((float)(num8 * num - num5 * num3) * num14), Clamp((float)(num9 * num - num6 * num3) * num14));
			return true;
		}

		private static void Write(byte[] dst, int o, ushort c0, ushort c1, uint indices)
		{
			if (c0 < c1)
			{
				ushort num = c0;
				c0 = c1;
				c1 = num;
				indices ^= 0x55555555;
			}
			else if (c0 == c1)
			{
				indices = 0u;
			}
			dst[o] = (byte)c0;
			dst[o + 1] = (byte)(c0 >> 8);
			dst[o + 2] = (byte)c1;
			dst[o + 3] = (byte)(c1 >> 8);
			dst[o + 4] = (byte)indices;
			dst[o + 5] = (byte)(indices >> 8);
			dst[o + 6] = (byte)(indices >> 16);
			dst[o + 7] = (byte)(indices >> 24);
		}

		private static int Clamp(float v)
		{
			if (!(v <= 0f))
			{
				if (!(v >= 255f))
				{
					return (int)(v + 0.5f);
				}
				return 255;
			}
			return 0;
		}

		private static int Mul8Bit(int a, int b)
		{
			int num = a * b + 128;
			return num + (num >> 8) >> 8;
		}

		private static ushort To565(int r, int g, int b)
		{
			return (ushort)((Mul8Bit(r, 31) << 11) | (Mul8Bit(g, 63) << 5) | Mul8Bit(b, 31));
		}

		private static void Unpack(ushort c, out int r, out int g, out int b)
		{
			int num = (c >> 11) & 0x1F;
			int num2 = (c >> 5) & 0x3F;
			int num3 = c & 0x1F;
			r = (num << 3) | (num >> 2);
			g = (num2 << 2) | (num2 >> 4);
			b = (num3 << 3) | (num3 >> 2);
		}

		private static ushort[] BuildToLinear()
		{
			ushort[] array = new ushort[256];
			for (int i = 0; i < 256; i++)
			{
				double num = (double)i / 255.0;
				double num2 = ((num <= 0.04045) ? (num / 12.92) : Math.Pow((num + 0.055) / 1.055, 2.4));
				array[i] = (ushort)Math.Round(num2 * 65535.0);
			}
			return array;
		}

		private static byte[] BuildToSrgb()
		{
			byte[] array = new byte[65536];
			for (int i = 0; i < 65536; i++)
			{
				double num = (double)i / 65535.0;
				double val = ((num <= 0.0031308) ? (num * 12.92) : (1.055 * Math.Pow(num, 5.0 / 12.0) - 0.055));
				array[i] = (byte)Math.Round(Math.Min(1.0, Math.Max(0.0, val)) * 255.0);
			}
			return array;
		}

		private static byte[] Downsample(byte[] src, int size)
		{
			int num = size / 2;
			byte[] array = new byte[num * num * 4];
			for (int i = 0; i < num; i++)
			{
				int num2 = i * 2 * size * 4;
				int num3 = num2 + size * 4;
				for (int j = 0; j < num; j++)
				{
					int num4 = num2 + j * 8;
					int num5 = num3 + j * 8;
					int num6 = (i * num + j) * 4;
					for (int k = 0; k < 3; k++)
					{
						int num7 = s_toLinear[src[num4 + k]] + s_toLinear[src[num4 + 4 + k]] + s_toLinear[src[num5 + k]] + s_toLinear[src[num5 + 4 + k]];
						array[num6 + k] = s_toSrgb[num7 + 2 >> 2];
					}
					array[num6 + 3] = byte.MaxValue;
				}
			}
			return array;
		}
	}
	internal static class PeekLibrary
	{
		private sealed class Entry
		{
			public string Key;

			public long Version;

			public Snapshot Snap;

			public PeekAsset Asset;

			public PeekAsset Previous;

			public float LastShown = float.NegativeInfinity;

			public float LastTouched;

			public float Distance = float.MaxValue;

			public int DistanceFrame = -1;

			public long Bytes;

			public bool DiskChecked;

			public Task<Snapshot> DiskTask;
		}

		private const float ShownRecently = 2f;

		private const float InUse = 5f;

		private const float ForgetAfter = 300f;

		private const float LoadMsPerFrame = 2f;

		private const float LoadMsBurst = 6f;

		private const int KeepDays = 60;

		private static string s_dir;

		private static bool s_dirFailed;

		private static float s_loadCredit;

		private static readonly Dictionary<string, Entry> s_entries = new Dictionary<string, Entry>();

		private static readonly Stopwatch s_watch = new Stopwatch();

		private static readonly List<Entry> s_tmp = new List<Entry>();

		public static bool Ready => EnsureDir();

		public static void Reset()
		{
			foreach (Entry value in s_entries.Values)
			{
				value.Asset?.Destroy();
				value.Previous?.Destroy();
			}
			s_entries.Clear();
			s_dir = null;
			s_dirFailed = false;
		}

		private static bool EnsureDir()
		{
			if (s_dir != null)
			{
				return true;
			}
			if (s_dirFailed)
			{
				return false;
			}
			World world = ZNet.World;
			if (world == null || string.IsNullOrEmpty(world.m_name))
			{
				return false;
			}
			string text = Cfg.StorageFolder.Value;
			if (string.IsNullOrWhiteSpace(text))
			{
				text = Path.Combine(Utils.GetSaveDataPath((FileSource)2), "portalpeek");
			}
			string text2 = Path.Combine(text, Sanitize(world.m_name) + "_" + world.m_uid);
			try
			{
				Directory.CreateDirectory(text2);
			}
			catch (Exception ex)
			{
				s_dirFailed = true;
				PortalPeekPlugin.Log.LogError((object)("Cannot use snapshot folder '" + text2 + "': " + ex.Message + ". Peeks disabled this session."));
				return false;
			}
			s_dir = text2;
			Housekeeping(text2);
			PortalPeekPlugin.Log.LogInfo((object)("Portal snapshots for this world are kept in " + text2));
			return true;
		}

		private static void Housekeeping(string dir)
		{
			Task.Run(delegate
			{
				try
				{
					DateTime dateTime = DateTime.UtcNow.AddDays(-60.0);
					string[] files = Directory.GetFiles(dir, "*.ppk.tmp");
					for (int i = 0; i < files.Length; i++)
					{
						File.Delete(files[i]);
					}
					files = Directory.GetFiles(dir, "*.ppk");
					foreach (string path in files)
					{
						if (File.GetLastWriteTimeUtc(path) < dateTime || !Snapshot.TryReadHeader(path, out var _, out var _))
						{
							File.Delete(path);
						}
					}
				}
				catch
				{
				}
			});
		}

		private static string Sanitize(string name)
		{
			char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
			foreach (char oldChar in invalidFileNameChars)
			{
				name = name.Replace(oldChar, '_');
			}
			return name;
		}

		private static string PathFor(string key)
		{
			return Path.Combine(s_dir, key + ".ppk");
		}

		public static void Update()
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance))
			{
				if (s_dir != null || s_entries.Count > 0)
				{
					Reset();
				}
			}
			else
			{
				LoadAssets();
				Evict();
			}
		}

		public static PeekAsset Get(string key, float distance)
		{
			if (string.IsNullOrEmpty(key) || !EnsureDir())
			{
				return null;
			}
			Entry entry = EntryFor(key);
			entry.LastShown = (entry.LastTouched = Time.time);
			if (entry.DistanceFrame != Time.frameCount)
			{
				entry.DistanceFrame = Time.frameCount;
				entry.Distance = distance;
			}
			else if (distance < entry.Distance)
			{
				entry.Distance = distance;
			}
			AdvanceDiskRead(entry);
			if (entry.Snap != null && entry.Asset == null)
			{
				entry.Asset = new PeekAsset(entry.Snap);
			}
			if (entry.Asset != null && entry.Asset.Ready)
			{
				if (entry.Previous != null)
				{
					entry.Previous.Destroy();
					entry.Previous = null;
				}
				return entry.Asset;
			}
			if (entry.Previous == null || !entry.Previous.Ready)
			{
				return null;
			}
			return entry.Previous;
		}

		private static void AdvanceDiskRead(Entry e)
		{
			if (!e.DiskChecked)
			{
				e.DiskChecked = true;
				string path = PathFor(e.Key);
				if (File.Exists(path))
				{
					e.DiskTask = Task.Run(() => Snapshot.FromFile(path));
				}
			}
			if (e.DiskTask == null || !e.DiskTask.IsCompleted)
			{
				return;
			}
			if (e.DiskTask.Status == TaskStatus.RanToCompletion)
			{
				Snapshot result = e.DiskTask.Result;
				if (e.Snap == null && result.Key == e.Key)
				{
					Adopt(e, result);
				}
			}
			else if (IsCorrupt(e.DiskTask.Exception))
			{
				TryDelete(PathFor(e.Key));
			}
			e.DiskTask = null;
		}

		public static bool NeedsCapture(string key, bool force)
		{
			if (!EnsureDir())
			{
				return false;
			}
			Entry entry = EntryFor(key);
			entry.LastTouched = Time.time;
			AdvanceDiskRead(entry);
			if (entry.DiskTask != null)
			{
				return false;
			}
			if (force)
			{
				return true;
			}
			if (entry.Version == 0L)
			{
				return true;
			}
			if (entry.Asset != null && entry.Asset.Failed)
			{
				return true;
			}
			return IsStale(entry.Version);
		}

		public static bool NeedsCaptureNow(string key)
		{
			if (!EnsureDir())
			{
				return false;
			}
			Entry entry = EntryFor(key);
			entry.LastTouched = Time.time;
			AdvanceDiskRead(entry);
			if (entry.DiskTask == null)
			{
				return NeedsCapture(key, force: false);
			}
			string path = PathFor(key);
			if (!File.Exists(path))
			{
				return true;
			}
			if (Snapshot.TryReadHeader(path, out var version, out var _))
			{
				return IsStale(version);
			}
			return false;
		}

		public static long StaleVersion()
		{
			return DateTime.UtcNow.Ticks - TimeSpan.FromMinutes(Cfg.RefreshMinutes.Value + 1f).Ticks;
		}

		private static bool IsStale(long version)
		{
			return (double)(DateTime.UtcNow.Ticks - version) / 10000000.0 > (double)Cfg.RefreshMinutes.Value * 60.0;
		}

		public static bool IsLoading(string key)
		{
			if (s_entries.TryGetValue(key, out var value))
			{
				if (value.DiskChecked)
				{
					return value.DiskTask != null;
				}
				return true;
			}
			return false;
		}

		private static Entry EntryFor(string key)
		{
			if (!s_entries.TryGetValue(key, out var value))
			{
				value = new Entry
				{
					Key = key,
					LastTouched = Time.time
				};
				s_entries[key] = value;
			}
			return value;
		}

		private static bool IsCorrupt(AggregateException ex)
		{
			Exception ex2 = ex?.GetBaseException();
			if (!(ex2 is InvalidDataException))
			{
				return ex2 is EndOfStreamException;
			}
			return true;
		}

		private static void Adopt(Entry e, Snapshot snap)
		{
			if (e.Asset != null && e.Asset.Ready)
			{
				e.Previous?.Destroy();
				e.Previous = e.Asset;
			}
			else
			{
				e.Asset?.Destroy();
			}
			e.Asset = null;
			e.Snap = snap;
			e.Version = snap.Version;
		}

		private static void TryDelete(string path)
		{
			try
			{
				File.Delete(path);
			}
			catch
			{
			}
		}

		public static void Submit(Snapshot snap)
		{
			if (!EnsureDir())
			{
				return;
			}
			Entry entry = EntryFor(snap.Key);
			entry.DiskChecked = true;
			entry.DiskTask = null;
			Adopt(entry, snap);
			string path = PathFor(snap.Key);
			byte[] prefix = snap.Prefix();
			byte[] sides = snap.Sides;
			byte[][] frames = (byte[][])snap.Frames.Clone();
			snap.FilePath = path;
			snap.ImagesStart = prefix.Length;
			Task.Run(delegate
			{
				try
				{
					FileUtil.WriteAtomic(path, delegate(Stream s)
					{
						s.Write(prefix, 0, prefix.Length);
						s.Write(sides, 0, sides.Length);
						byte[][] array = frames;
						foreach (byte[] array2 in array)
						{
							s.Write(array2, 0, array2.Length);
						}
					});
					snap.ReleaseToFile();
				}
				catch (Exception ex)
				{
					PortalPeekPlugin.Log.LogWarning((object)("Could not save snapshot " + snap.Key + ": " + ex.Message));
				}
			});
		}

		private static void LoadAssets()
		{
			s_loadCredit = Mathf.Min(6f, s_loadCredit + 2f);
			if (s_loadCredit <= 0f)
			{
				return;
			}
			float time = Time.time;
			s_tmp.Clear();
			foreach (Entry value2 in s_entries.Values)
			{
				if (time - value2.LastShown < 2f && value2.Asset != null && !value2.Asset.Failed && !value2.Asset.FullyDecoded)
				{
					s_tmp.Add(value2);
				}
			}
			if (s_tmp.Count == 0)
			{
				return;
			}
			s_tmp.Sort((Entry a, Entry b) => a.Distance.CompareTo(b.Distance));
			foreach (Entry item in s_tmp)
			{
				while (!item.Asset.Ready && !item.Asset.Failed && Step(item))
				{
					if (s_loadCredit <= 0f)
					{
						return;
					}
				}
			}
			if (!Cfg.Animate.Value)
			{
				return;
			}
			float value = Cfg.AnimationDistance.Value;
			int num = Cfg.MaxLoadingClips.Value;
			foreach (Entry item2 in s_tmp)
			{
				if (item2.Distance > value || num <= 0)
				{
					break;
				}
				if (!item2.Asset.Ready || item2.Asset.Failed)
				{
					continue;
				}
				num--;
				while (!item2.Asset.FullyDecoded && Step(item2))
				{
					if (s_loadCredit <= 0f)
					{
						return;
					}
				}
			}
		}

		private static bool Step(Entry e)
		{
			s_watch.Restart();
			bool result;
			try
			{
				result = e.Asset.LoadStep();
			}
			catch (Exception e2)
			{
				Safe.Report("Load " + e.Key, e2);
				e.Asset.Abandon();
				result = false;
			}
			s_loadCredit -= (float)s_watch.Elapsed.TotalMilliseconds;
			return result;
		}

		private static long Cost(PeekAsset a)
		{
			if (a != null)
			{
				return a.GpuBytes + a.Snap.HeldBytes();
			}
			return 0L;
		}

		private static void Evict()
		{
			float time = Time.time;
			long num = 0L;
			s_tmp.Clear();
			foreach (Entry value in s_entries.Values)
			{
				value.Bytes = Cost(value.Asset) + Cost(value.Previous) + ((value.Asset == null && value.Snap != null) ? value.Snap.HeldBytes() : 0);
				num += value.Bytes;
				if (value.DiskTask == null && !(time - value.LastShown < 5f) && ((value.Asset == null && value.Previous == null && time - value.LastTouched > 300f) || value.Bytes > 0))
				{
					s_tmp.Add(value);
				}
			}
			long num2 = (long)Cfg.MemoryBudgetMB.Value * 1024L * 1024;
			s_tmp.Sort((Entry a, Entry b) => (a.LastShown == b.LastShown) ? a.LastTouched.CompareTo(b.LastTouched) : a.LastShown.CompareTo(b.LastShown));
			foreach (Entry item in s_tmp)
			{
				if ((item.Asset == null && item.Previous == null && time - item.LastTouched > 300f) || num > num2)
				{
					item.Asset?.Destroy();
					item.Previous?.Destroy();
					s_entries.Remove(item.Key);
					num -= item.Bytes;
				}
			}
		}
	}
	internal sealed class PeekAsset
	{
		private sealed class MeshData
		{
			public Vector3[] Vertices;

			public Vector2[] Uvs;

			public Color32[] Colors;

			public List<int> Triangles;

			public long Bytes => (long)Vertices.Length * 24L + (long)Triangles.Count * 4L;

			public Mesh ToMesh(string name)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0005: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Expected O, but got Unknown
				Mesh val = new Mesh
				{
					name = name
				};
				if (Vertices.Length > 65000)
				{
					val.indexFormat = (IndexFormat)1;
				}
				val.vertices = Vertices;
				val.uv = Uvs;
				val.colors32 = Colors;
				val.SetTriangles(Triangles, 0);
				val.RecalculateBounds();
				val.UploadMeshData(true);
				return val;
			}
		}

		public const int MaxLayers = 4;

		private const float JumpRatio = 1.25f;

		private const float JumpMeters = 0.5f;

		private const float BackgroundPush = 1.015f;

		private const int SidesImage = -1;

		public readonly Snapshot Snap;

		public readonly Texture2D[] Frames;

		private Task<MeshData[]> m_meshTask;

		private long m_meshBytes;

		private Task<byte[]> m_read;

		private int m_readIndex;

		private int m_limit = int.MaxValue;

		private float m_playStart = -1f;

		private bool m_destroyed;

		public Mesh ForwardMesh { get; private set; }

		public Mesh SidesMesh { get; private set; }

		public Texture2D Sides { get; private set; }

		public int Decoded { get; private set; }

		public bool Ready
		{
			get
			{
				if (Object.op_Implicit((Object)(object)ForwardMesh) && Object.op_Implicit((Object)(object)Sides))
				{
					return Decoded > 0;
				}
				return false;
			}
		}

		public bool FullyDecoded
		{
			get
			{
				if (Object.op_Implicit((Object)(object)Sides))
				{
					return Decoded >= Mathf.Min(Frames.Length, m_limit);
				}
				return false;
			}
		}

		public bool Failed { get; private set; }

		public long GpuBytes => m_meshBytes + (long)((Object.op_Implicit((Object)(object)Sides) ? 1 : 0) + Decoded) * (long)Snap.ImageBytes;

		public PeekAsset(Snapshot snap)
		{
			Snap = snap;
			Frames = (Texture2D[])(object)new Texture2D[snap.FrameCount];
			m_meshTask = Task.Run(() => BuildMeshData(snap));
		}

		public bool LoadStep()
		{
			if (m_destroyed || Failed)
			{
				return false;
			}
			if (!Object.op_Implicit((Object)(object)ForwardMesh))
			{
				if (m_meshTask == null || !m_meshTask.IsCompleted)
				{
					return false;
				}
				if (m_meshTask.Status != TaskStatus.RanToCompletion)
				{
					Failed = true;
					PortalPeekPlugin.Log.LogWarning((object)("Snapshot " + Snap.Key + ": mesh build failed: " + m_meshTask.Exception?.GetBaseException().Message));
					return false;
				}
				MeshData[] result = m_meshTask.Result;
				ForwardMesh = result[0].ToMesh("PortalPeek_Fwd_" + Snap.Key);
				SidesMesh = result[1].ToMesh("PortalPeek_Sides_" + Snap.Key);
				m_meshBytes = result[0].Bytes + result[1].Bytes;
				m_meshTask = null;
				return true;
			}
			if (!Object.op_Implicit((Object)(object)Sides))
			{
				if (!TakeImage(-1, out var bytes))
				{
					return false;
				}
				Sides = ((bytes != null) ? Upload(bytes, "sides") : null);
				if (!Object.op_Implicit((Object)(object)Sides))
				{
					Failed = true;
				}
				return true;
			}
			if (FullyDecoded)
			{
				return false;
			}
			int decoded = Decoded;
			if (!TakeImage(decoded, out var bytes2))
			{
				return false;
			}
			Texture2D val = ((bytes2 != null) ? Upload(bytes2, "frame " + decoded) : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				m_limit = decoded;
				if (decoded == 0)
				{
					Failed = true;
				}
				return true;
			}
			Frames[decoded] = val;
			Decoded++;
			return true;
		}

		private bool TakeImage(int index, out byte[] bytes)
		{
			bytes = ((index == -1) ? Snap.Sides : Snap.Frames[index]);
			if (bytes != null)
			{
				if (index == -1)
				{
					Snap.Sides = null;
				}
				else
				{
					Snap.Frames[index] = null;
				}
				return true;
			}
			if (m_read != null && m_readIndex == index)
			{
				if (!m_read.IsCompleted)
				{
					return false;
				}
				Task<byte[]> read = m_read;
				m_read = null;
				if (read.Status == TaskStatus.RanToCompletion)
				{
					bytes = read.Result;
				}
				else
				{
					PortalPeekPlugin.Log.LogWarning((object)$"Snapshot {Snap.Key}: could not read image {index}: {read.Exception?.GetBaseException().Message}");
				}
				return true;
			}
			if (Snap.FilePath == null)
			{
				return true;
			}
			Snapshot snap = Snap;
			m_readIndex = index;
			m_read = Task.Run(() => snap.ReadImage(index));
			return false;
		}

		private Texture2D Upload(byte[] bc1, string what)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Expected O, but got Unknown
			int textureSize = Snap.TextureSize;
			if (bc1.Length != Snap.ImageBytes)
			{
				PortalPeekPlugin.Log.LogWarning((object)("Snapshot " + Snap.Key + ": " + what + " has the wrong size."));
				return null;
			}
			Texture2D val = new Texture2D(textureSize, textureSize, (TextureFormat)10, Snap.MipCount, false)
			{
				name = "PortalPeek_" + Snap.Key + "_" + what,
				wrapMode = (TextureWrapMode)1,
				filterMode = (FilterMode)2,
				anisoLevel = 4
			};
			try
			{
				val.LoadRawTextureData(bc1);
				val.Apply(false, true);
				return val;
			}
			catch (Exception ex)
			{
				PortalPeekPlugin.Log.LogWarning((object)("Snapshot " + Snap.Key + ": " + what + " could not be uploaded: " + ex.Message));
				Object.Destroy((Object)(object)val);
				return null;
			}
		}

		public void Abandon()
		{
			m_limit = Decoded;
		}

		public void Destroy()
		{
			m_destroyed = true;
			Texture2D[] frames = Frames;
			foreach (Texture2D val in frames)
			{
				if (Object.op_Implicit((Object)(object)val))
				{
					Object.Destroy((Object)(object)val);
				}
			}
			if (Object.op_Implicit((Object)(object)Sides))
			{
				Object.Destroy((Object)(object)Sides);
			}
			if (Object.op_Implicit((Object)(object)ForwardMesh))
			{
				Object.Destroy((Object)(object)ForwardMesh);
			}
			if (Object.op_Implicit((Object)(object)SidesMesh))
			{
				Object.Destroy((Object)(object)SidesMesh);
			}
		}

		public int Sample(float now, Texture2D[] tex, float[] weight)
		{
			tex[0] = Frames[0];
			weight[0] = 1f;
			int num = ((!Cfg.Animate.Value || !FullyDecoded) ? 1 : Decoded);
			if (num <= 1)
			{
				m_playStart = -1f;
				return 1;
			}
			if (m_playStart < 0f)
			{
				m_playStart = now;
			}
			float num2 = now - m_playStart;
			float num3 = TimeOf(num - 1);
			if (num3 <= 0f)
			{
				return 1;
			}
			if (Cfg.Loop.Value == LoopMode.Crossfade && num >= 4)
			{
				float num4 = num3 * 0.25f;
				float num5 = num3 - num4;
				if (num2 < num4)
				{
					return AddAt(num2, 1f, num, tex, weight, 0);
				}
				float num6 = Mathf.Repeat(num2, num5);
				if (num6 < num4)
				{
					float num7 = Mathf.SmoothStep(0f, 1f, num6 / num4);
					int count = AddAt(num6 + num5, 1f - num7, num, tex, weight, 0);
					return AddAt(num6, num7, num, tex, weight, count);
				}
				return AddAt(num6, 1f, num, tex, weight, 0);
			}
			return AddAt(Mathf.PingPong(num2, num3), 1f, num, tex, weight, 0);
		}

		private float TimeOf(int i)
		{
			float[] frameTimes = Snap.FrameTimes;
			if (i < frameTimes.Length)
			{
				return frameTimes[i];
			}
			return (float)i / Mathf.Max(1f, Snap.Fps);
		}

		private int AddAt(float t, float w, int n, Texture2D[] tex, float[] weight, int count)
		{
			int i;
			for (i = 0; i < n - 2 && TimeOf(i + 1) <= t; i++)
			{
			}
			float num = TimeOf(i);
			float num2 = TimeOf(i + 1);
			float num3 = ((num2 > num) ? Mathf.Clamp01((t - num) / (num2 - num)) : 0f);
			count = Add(Frames[i], w * (1f - num3), tex, weight, count);
			return Add(Frames[i + 1], w * num3, tex, weight, count);
		}

		private static int Add(Texture2D t, float w, Texture2D[] tex, float[] weight, int count)
		{
			if (w <= 0.001f)
			{
				return count;
			}
			for (int i = 0; i < count; i++)
			{
				if (!((Object)(object)tex[i] != (Object)(object)t))
				{
					weight[i] += w;
					return count;
				}
			}
			if (count >= 4)
			{
				return count;
			}
			tex[count] = t;
			weight[count] = w;
			return count + 1;
		}

		private static MeshData[] BuildMeshData(Snapshot s)
		{
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			int depthRes = s.DepthRes;
			int num = depthRes + 1;
			int num2 = num * num;
			float[] array = (float[])s.Depth.Clone();
			float[] array2 = new float[array.Length];
			for (int i = 0; i < 5; i++)
			{
				int num3 = i * num2;
				for (int j = 0; j <= depthRes; j++)
				{
					for (int k = 0; k <= depthRes; k++)
					{
						float num4 = 0f;
						for (int l = -1; l <= 1; l++)
						{
							for (int m = -1; m <= 1; m++)
							{
								int num5 = Mathf.Clamp(j + l, 0, depthRes);
								int num6 = Mathf.Clamp(k + m, 0, depthRes);
								num4 = Mathf.Max(num4, s.Depth[num3 + num5 * num + num6]);
							}
						}
						array2[num3 + j * num + k] = num4;
					}
				}
			}
			Dictionary<long, List<int>> dictionary = new Dictionary<long, List<int>>();
			for (int n = 0; n < 5; n++)
			{
				for (int num7 = 0; num7 <= depthRes; num7++)
				{
					for (int num8 = 0; num8 <= depthRes; num8++)
					{
						if (num8 == 0 || num8 == depthRes || num7 == 0 || num7 == depthRes)
						{
							Vector3 val = CubeFaces.Dir(n, (float)num8 * 2f / (float)depthRes - 1f, (float)num7 * 2f / (float)depthRes - 1f);
							Vector3 normalized = ((Vector3)(ref val)).normalized;
							long key = ((long)Mathf.RoundToInt(normalized.x * 20000f) + 32768L << 32) | ((long)Mathf.RoundToInt(normalized.y * 20000f) + 32768L << 16) | ((long)Mathf.RoundToInt(normalized.z * 20000f) + 32768L);
							if (!dictionary.TryGetValue(key, out var value))
							{
								value = (dictionary[key] = new List<int>(3));
							}
							value.Add(n * num2 + num7 * num + num8);
						}
					}
				}
			}
			foreach (List<int> value2 in dictionary.Values)
			{
				if (value2.Count < 2)
				{
					continue;
				}
				float num9 = float.MaxValue;
				float num10 = 0f;
				foreach (int item in value2)
				{
					num9 = Mathf.Min(num9, array[item]);
					num10 = Mathf.Max(num10, array2[item]);
				}
				foreach (int item2 in value2)
				{
					array[item2] = num9;
					array2[item2] = num10;
				}
			}
			return new MeshData[2]
			{
				BuildGroup(s, array, array2, 0, 1),
				BuildGroup(s, array, array2, 1, 5)
			};
		}

		private static MeshData BuildGroup(Snapshot s, float[] near, float[] far, int firstFace, int endFace)
		{
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: 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_0181: Unknown result type (might be due to invalid IL or missing references)
			//IL_018d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0192: Unknown result type (might be due to invalid IL or missing references)
			//IL_0197: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bd: 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_01d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01da: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e1: 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)
			int depthRes = s.DepthRes;
			int num = depthRes + 1;
			int num2 = num * num;
			int num3 = endFace - firstFace;
			int num4 = num3 * num2;
			MeshData meshData = new MeshData
			{
				Vertices = (Vector3[])(object)new Vector3[num4 * 2],
				Uvs = (Vector2[])(object)new Vector2[num4 * 2],
				Colors = (Color32[])(object)new Color32[num4 * 2],
				Triangles = new List<int>(num3 * depthRes * depthRes * 12)
			};
			float num5 = s.TextureSize;
			Color32 val = default(Color32);
			((Color32)(ref val))..ctor(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
			Vector2 val3 = default(Vector2);
			for (int i = firstFace; i < endFace; i++)
			{
				Snapshot.FaceRect(s.FaceSize, i, out var x, out var y, out var size);
				int num6 = Snapshot.Gutter(size);
				float num7 = size - 2 * num6;
				int num8 = i * num2;
				int num9 = (i - firstFace) * num2;
				for (int j = 0; j <= depthRes; j++)
				{
					for (int k = 0; k <= depthRes; k++)
					{
						float num10 = (float)k / (float)depthRes;
						float num11 = (float)j / (float)depthRes;
						Vector3 val2 = CubeFaces.Dir(i, num10 * 2f - 1f, num11 * 2f - 1f);
						Vector3 normalized = ((Vector3)(ref val2)).normalized;
						int num12 = num8 + j * num + k;
						int num13 = num9 + j * num + k;
						((Vector2)(ref val3))..ctor(((float)(x + num6) + num10 * num7) / num5, ((float)(y + num6) + num11 * num7) / num5);
						meshData.Vertices[num13] = s.Origin + normalized * near[num12];
						meshData.Vertices[num4 + num13] = s.Origin + normalized * (far[num12] * 1.015f);
						meshData.Uvs[num13] = (meshData.Uvs[num4 + num13] = val3);
						meshData.Colors[num13] = (meshData.Colors[num4 + num13] = val);
					}
				}
				for (int l = 0; l < depthRes; l++)
				{
					for (int m = 0; m < depthRes; m++)
					{
						int num14 = num9 + l * num + m;
						int c = num14 + 1;
						int num15 = num14 + num;
						int num16 = num15 + 1;
						int num17 = num8 + l * num + m;
						int num18 = num17 + 1;
						int num19 = num17 + num;
						int num20 = num19 + 1;
						if (!IsJump(near[num17], near[num19], near[num20]))
						{
							Tri(meshData.Triangles, num14, num15, num16, 0);
						}
						if (!IsJump(near[num17], near[num20], near[num18]))
						{
							Tri(meshData.Triangles, num14, num16, c, 0);
						}
						Tri(meshData.Triangles, num14, num15, num16, num4);
						Tri(meshData.Triangles, num14, num16, c, num4);
					}
				}
			}
			return meshData;
		}

		private static bool IsJump(float a, float b, float c)
		{
			float num = Mathf.Min(a, Mathf.Min(b, c));
			float num2 = Mathf.Max(a, Mathf.Max(b, c));
			if (num2 > num * 1.25f)
			{
				return num2 - num > 0.5f;
			}
			return false;
		}

		private static void Tri(List<int> tris, int a, int b, int c, int offset)
		{
			tris.Add(a + offset);
			tris.Add(b + offset);
			tris.Add(c + offset);
		}
	}
	internal struct Opening
	{
		public float CenterX;

		public float CenterY;

		public float RadiusX;

		public float RadiusY;

		public const float Floor = 0.03f;

		public Rect Rect => Rect.MinMaxRect(CenterX - RadiusX, Mathf.Max(0.03f, CenterY - RadiusY), CenterX + RadiusX, CenterY + RadiusY);

		public static Opening For(TeleportWorld portal)
		{
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Unknown result type (might be due to invalid IL or missing references)
			//IL_015f: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be du