Decompiled source of PortalPreview v1.5.6

PortalPreview.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SoftReferenceableAssets;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.PostProcessing;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("PortalPreviewProbe")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("PortalPreview")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.5.6.0")]
[assembly: AssemblyInformationalVersion("1.5.6+3e51051fb4da1e6202f7bb5ba1ab7ef6b608a2b5")]
[assembly: AssemblyProduct("PortalPreview")]
[assembly: AssemblyTitle("PortalPreview")]
[assembly: AssemblyVersion("1.5.6.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 PortalPreview
{
	internal sealed class ArchiveBlobPrefetch : IDisposable
	{
		private const long MemoryBudget = 100663296L;

		private const int ScanLimit = 1048576;

		private readonly object _gate = new object();

		private readonly Func<string, Stream> _read;

		private readonly Queue<string> _ahead = new Queue<string>();

		private readonly Queue<string> _demand = new Queue<string>();

		private readonly HashSet<string> _discovered = new HashSet<string>();

		private readonly HashSet<string> _queued = new HashSet<string>();

		private readonly HashSet<string> _demanded = new HashSet<string>();

		private readonly HashSet<string> _working = new HashSet<string>();

		private readonly HashSet<string> _discarded = new HashSet<string>();

		private readonly Dictionary<string, Stream> _ready = new Dictionary<string, Stream>();

		private readonly Dictionary<string, Exception> _errors = new Dictionary<string, Exception>();

		private long _buffered;

		private bool _stopped;

		internal long RetainedBytes
		{
			get
			{
				lock (_gate)
				{
					return _buffered;
				}
			}
		}

		private bool Full => _buffered >= 100663296;

		internal bool Idle
		{
			get
			{
				lock (_gate)
				{
					return _stopped || (_queued.Count == 0 && _working.Count == 0) || Full;
				}
			}
		}

		internal ArchiveBlobPrefetch(byte[] manifest, Func<string, Stream> read, int workers = 0)
		{
			_read = read;
			EnqueueReferences(manifest, manifest.Length);
			workers = ((workers > 0) ? Math.Min(6, workers) : Math.Max(1, Math.Min(6, Environment.ProcessorCount - 2)));
			for (int i = 0; i < workers; i++)
			{
				Thread thread = new Thread(Run);
				thread.IsBackground = true;
				thread.Name = "PortalPreview blob prefetch " + i;
				thread.Start();
			}
		}

		internal bool TryTake(string hash, out Stream result)
		{
			lock (_gate)
			{
				result = null;
				if (_errors.TryGetValue(hash, out var value))
				{
					throw value;
				}
				if (_ready.TryGetValue(hash, out result))
				{
					_ready.Remove(hash);
					_buffered -= Retained(result);
					Monitor.PulseAll(_gate);
					return true;
				}
				if (_stopped)
				{
					throw new ObjectDisposedException("ArchiveBlobPrefetch");
				}
				if (_working.Contains(hash))
				{
					return false;
				}
				while (Full && _ready.Count > 0)
				{
					string key = null;
					using (Dictionary<string, Stream>.KeyCollection.Enumerator enumerator = _ready.Keys.GetEnumerator())
					{
						if (enumerator.MoveNext())
						{
							key = enumerator.Current;
						}
					}
					Stream stream = _ready[key];
					_ready.Remove(key);
					_buffered -= Retained(stream);
					stream.Dispose();
				}
				_discarded.Remove(hash);
				_queued.Add(hash);
				if (_demanded.Add(hash))
				{
					_demand.Enqueue(hash);
				}
				Monitor.PulseAll(_gate);
				return false;
			}
		}

		internal void Discard(string hash)
		{
			lock (_gate)
			{
				_discarded.Add(hash);
				_queued.Remove(hash);
				_errors.Remove(hash);
				if (_ready.TryGetValue(hash, out var value))
				{
					_ready.Remove(hash);
					_buffered -= Retained(value);
					value.Dispose();
				}
				Monitor.PulseAll(_gate);
			}
		}

		private static long Retained(Stream stream)
		{
			return stream.Length;
		}

		private void Run()
		{
			while (true)
			{
				string text;
				lock (_gate)
				{
					while (!_stopped && (_queued.Count == 0 || Full))
					{
						Monitor.Wait(_gate);
					}
					if (_stopped)
					{
						break;
					}
					Queue<string> queue = ((_demand.Count > 0) ? _demand : _ahead);
					if (queue.Count != 0)
					{
						text = queue.Dequeue();
						_demanded.Remove(text);
						if (_queued.Remove(text) && !_discarded.Contains(text))
						{
							_working.Add(text);
							goto IL_00c3;
						}
					}
				}
				continue;
				IL_00c3:
				Stream stream = null;
				Exception ex = null;
				try
				{
					stream = _read(text);
				}
				catch (Exception ex2)
				{
					stream?.Dispose();
					stream = null;
					ex = ex2;
				}
				lock (_gate)
				{
					_working.Remove(text);
					if (_stopped || _discarded.Contains(text))
					{
						stream?.Dispose();
					}
					else if (ex != null)
					{
						_errors[text] = ex;
					}
					else
					{
						_ready.Add(text, stream);
						_buffered += Retained(stream);
						if (stream.Length <= 1048576)
						{
							if (stream is MemoryStream memoryStream && memoryStream.TryGetBuffer(out var buffer) && buffer.Offset == 0)
							{
								EnqueueReferences(buffer.Array, (int)stream.Length);
							}
							else
							{
								byte[] array = new byte[(int)stream.Length];
								int i;
								int num;
								for (i = 0; i < array.Length; i += num)
								{
									if ((num = stream.Read(array, i, array.Length - i)) <= 0)
									{
										break;
									}
								}
								stream.Position = 0L;
								EnqueueReferences(array, i);
							}
						}
					}
					Monitor.PulseAll(_gate);
				}
			}
		}

		private void EnqueueReferences(byte[] data, int length)
		{
			for (int i = 0; i + 64 < length; i++)
			{
				if (data[i] != 64)
				{
					continue;
				}
				int j;
				for (j = 1; j <= 64; j++)
				{
					byte b = data[i + j];
					if ((b < 48 || b > 57) && (b < 97 || b > 102))
					{
						break;
					}
				}
				if (j <= 64)
				{
					continue;
				}
				if (!IsAssetFallback(data, i))
				{
					string item = Encoding.ASCII.GetString(data, i + 1, 64);
					if (_discovered.Add(item) && _queued.Add(item))
					{
						_ahead.Enqueue(item);
					}
				}
				i += 64;
			}
		}

		private static bool IsAssetFallback(byte[] data, int prefix)
		{
			int num;
			for (num = prefix - 1; num >= 0 && prefix - num < 64; num--)
			{
				byte b = data[num];
				switch (b)
				{
				default:
					if ((b >= 97 && b <= 122) || (b >= 48 && b <= 57) || b == 95)
					{
						continue;
					}
					return false;
				case 65:
				case 66:
				case 67:
				case 68:
				case 69:
				case 70:
				case 71:
				case 72:
				case 73:
				case 74:
				case 75:
				case 76:
				case 77:
				case 78:
				case 79:
				case 80:
				case 81:
				case 82:
				case 83:
				case 84:
				case 85:
				case 86:
				case 87:
				case 88:
				case 89:
				case 90:
					continue;
				case 124:
					break;
				}
				break;
			}
			if (num < "|Tex2D".Length || data[num] != 124)
			{
				return false;
			}
			for (int i = 0; i < "|Tex2D".Length; i++)
			{
				if (data[num - "|Tex2D".Length + i] != "|Tex2D"[i])
				{
					return false;
				}
			}
			return true;
		}

		public void Dispose()
		{
			lock (_gate)
			{
				_stopped = true;
				foreach (Stream value in _ready.Values)
				{
					value.Dispose();
				}
				_ready.Clear();
				_ahead.Clear();
				_demand.Clear();
				_queued.Clear();
				_errors.Clear();
				_buffered = 0L;
				Monitor.PulseAll(_gate);
			}
		}
	}
	internal static class BlobCollection
	{
		internal struct Result
		{
			public int Blobs;

			public long Bytes;

			public bool Aborted;

			public bool Skipped;
		}

		internal const string StateFile = "blob-cleanup.state";

		private const string StateVersion = "2";

		internal static string Key(string hash)
		{
			if (hash.Length <= 32)
			{
				return hash;
			}
			return hash.Substring(0, 32);
		}

		internal static string PathOf(string blobs, string hash)
		{
			string text = Path.Combine(blobs, Key(hash) + ".bin");
			if (hash.Length > 32 && !File.Exists(text))
			{
				string text2 = Path.Combine(blobs, hash + ".bin");
				if (File.Exists(text2))
				{
					return text2;
				}
			}
			return text;
		}

		internal static Result Collect(string directory, DateTime nowUtc, TimeSpan grace, object gate)
		{
			Result result = default(Result);
			string text = Path.Combine(directory, "blobs");
			if (!Directory.Exists(text))
			{
				return result;
			}
			string path = Path.Combine(directory, "blob-cleanup.state");
			string text2 = ManifestFingerprint(directory);
			if (TryReadState(path, out var manifests, out var blobs, out var rescanAfter) && manifests == text2 && blobs == CountBlobs(text) && nowUtc < rescanAfter)
			{
				result.Skipped = true;
				return result;
			}
			BlobReferences blobReferences = new BlobReferences();
			string[] array = Manifests(directory);
			foreach (string path2 in array)
			{
				try
				{
					using FileStream stream = OpenShared(path2);
					blobReferences.Scan(stream);
				}
				catch (FileNotFoundException)
				{
				}
			}
			string hash;
			while (blobReferences.TryNext(out hash))
			{
				string text3 = PathOf(text, hash);
				try
				{
					using FileStream stream2 = OpenShared(text3);
					using DeflateStream stream3 = new DeflateStream(stream2, CompressionMode.Decompress);
					if (!blobReferences.ScanVerified(stream3, hash))
					{
						throw new InvalidDataException("Portal blob checksum mismatch.");
					}
				}
				catch (Exception ex2) when (ex2 is IOException || ex2 is InvalidDataException || ex2 is UnauthorizedAccessException)
				{
					throw new IOException("Cannot complete portal blob cleanup: " + text3, ex2);
				}
			}
			rescanAfter = DateTime.MaxValue;
			List<FileInfo> list = new List<FileInfo>();
			array = Directory.GetFiles(text, "*.bin");
			foreach (string text4 in array)
			{
				if (blobReferences.Reaches(Path.GetFileNameWithoutExtension(text4)))
				{
					continue;
				}
				FileInfo fileInfo = new FileInfo(text4);
				if (nowUtc - fileInfo.LastWriteTimeUtc < grace)
				{
					DateTime dateTime = fileInfo.LastWriteTimeUtc + grace;
					if (dateTime < rescanAfter)
					{
						rescanAfter = dateTime;
					}
				}
				else
				{
					list.Add(fileInfo);
				}
			}
			for (int j = 0; j < list.Count; j += 100)
			{
				lock (gate)
				{
					if (SavedSince(directory, nowUtc))
					{
						result.Aborted = true;
						return result;
					}
					for (int k = j; k < Math.Min(j + 100, list.Count); k++)
					{
						result.Bytes += list[k].Length;
						list[k].Delete();
						result.Blobs++;
					}
				}
			}
			if (list.Count > 0)
			{
				PruneAssetIndex(text, gate);
			}
			lock (gate)
			{
				if (!SavedSince(directory, nowUtc) && ManifestFingerprint(directory) == text2)
				{
					try
					{
						WriteState(path, text2, CountBlobs(text), rescanAfter);
					}
					catch (Exception ex3) when (ex3 is IOException || ex3 is UnauthorizedAccessException)
					{
					}
				}
			}
			return result;
		}

		private static string[] Manifests(string directory)
		{
			return Array.FindAll(Directory.GetFiles(directory, "*.portal*"), (string p) => !p.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase));
		}

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

		private static bool SavedSince(string directory, DateTime started)
		{
			string[] array = Manifests(directory);
			for (int i = 0; i < array.Length; i++)
			{
				if (File.GetLastWriteTimeUtc(array[i]) > started)
				{
					return true;
				}
			}
			return false;
		}

		private static void PruneAssetIndex(string blobs, object gate)
		{
			string path = Path.Combine(blobs, "asset-textures.index");
			lock (gate)
			{
				if (!File.Exists(path))
				{
					return;
				}
				List<string> list = new List<string>();
				string[] array = File.ReadAllLines(path);
				foreach (string text in array)
				{
					int num = text.LastIndexOf('\t');
					if (num > 0 && File.Exists(PathOf(blobs, text.Substring(num + 1))))
					{
						list.Add(text);
					}
				}
				File.WriteAllLines(path, list);
			}
		}

		private static int CountBlobs(string blobs)
		{
			return Directory.GetFiles(blobs, "*.bin").Length;
		}

		internal static string ManifestFingerprint(string directory)
		{
			string[] array = Manifests(directory);
			Array.Sort(array, (IComparer<string>?)StringComparer.Ordinal);
			StringBuilder stringBuilder = new StringBuilder();
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				FileInfo fileInfo = new FileInfo(array2[i]);
				stringBuilder.Append(fileInfo.Name).Append('|').Append(fileInfo.Length)
					.Append('|')
					.Append(fileInfo.LastWriteTimeUtc.Ticks)
					.Append('\n');
			}
			return stringBuilder.ToString();
		}

		private static bool TryReadState(string path, out string manifests, out int blobs, out DateTime rescanAfter)
		{
			manifests = null;
			blobs = -1;
			rescanAfter = DateTime.MinValue;
			try
			{
				if (!File.Exists(path))
				{
					return false;
				}
				string[] array = File.ReadAllText(path).Split(new char[1] { '\n' }, 4);
				if (array.Length < 4 || array[0] != "2")
				{
					return false;
				}
				blobs = int.Parse(array[1], CultureInfo.InvariantCulture);
				rescanAfter = new DateTime(long.Parse(array[2], CultureInfo.InvariantCulture), DateTimeKind.Utc);
				manifests = array[3];
				return true;
			}
			catch (Exception ex) when (ex is IOException || ex is FormatException || ex is OverflowException || ex is ArgumentException || ex is UnauthorizedAccessException)
			{
				return false;
			}
		}

		private static void WriteState(string path, string manifests, int blobs, DateTime rescanAfter)
		{
			string contents = "2\n" + blobs.ToString(CultureInfo.InvariantCulture) + "\n" + rescanAfter.Ticks.ToString(CultureInfo.InvariantCulture) + "\n" + manifests;
			string text = path + ".tmp";
			File.WriteAllText(text, contents);
			if (File.Exists(path))
			{
				File.Delete(path);
			}
			File.Move(text, path);
		}
	}
	internal sealed class BlobReferences
	{
		private readonly struct Key128 : IEquatable<Key128>
		{
			private readonly ulong _high;

			private readonly ulong _low;

			private Key128(ulong high, ulong low)
			{
				_high = high;
				_low = low;
			}

			internal static Key128 From(byte[] data, int offset)
			{
				ulong num = 0uL;
				ulong num2 = 0uL;
				for (int i = 0; i < 16; i++)
				{
					num = (num << 4) | Nibble(data[offset + i]);
				}
				for (int j = 16; j < 32; j++)
				{
					num2 = (num2 << 4) | Nibble(data[offset + j]);
				}
				return new Key128(num, num2);
			}

			internal static bool TryParse(string name, out Key128 key)
			{
				ulong num = 0uL;
				ulong num2 = 0uL;
				for (int i = 0; i < 32; i++)
				{
					char c = name[i];
					if ((c < '0' || c > '9') && (c < 'a' || c > 'f'))
					{
						key = default(Key128);
						return false;
					}
					if (i < 16)
					{
						num = (num << 4) | Nibble((byte)c);
					}
					else
					{
						num2 = (num2 << 4) | Nibble((byte)c);
					}
				}
				key = new Key128(num, num2);
				return true;
			}

			private static ulong Nibble(byte c)
			{
				return (ulong)((c <= 57) ? (c - 48) : (c - 97 + 10));
			}

			public bool Equals(Key128 other)
			{
				if (_high == other._high)
				{
					return _low == other._low;
				}
				return false;
			}

			public override bool Equals(object obj)
			{
				if (obj is Key128 other)
				{
					return Equals(other);
				}
				return false;
			}

			public override int GetHashCode()
			{
				return (_high ^ (ulong)((long)_low * -7046029254386353131L)).GetHashCode();
			}
		}

		private const int HashLength = 64;

		private readonly byte[] _buffer = new byte[65600];

		private readonly HashSet<Key128> _reached = new HashSet<Key128>();

		private readonly Queue<string> _pending = new Queue<string>();

		internal int Count => _reached.Count;

		internal bool TryNext(out string hash)
		{
			if (_pending.Count == 0)
			{
				hash = null;
				return false;
			}
			hash = _pending.Dequeue();
			return true;
		}

		internal bool Reaches(string name)
		{
			if (name.Length >= 32 && Key128.TryParse(name, out var key))
			{
				return _reached.Contains(key);
			}
			return false;
		}

		internal void Scan(Stream stream)
		{
			Scan(stream, null);
		}

		internal bool ScanVerified(Stream stream, string expectedHash)
		{
			using Sha256.Hasher hasher = new Sha256.Hasher();
			Scan(stream, hasher);
			return Sha256.Hex(hasher.Finish()) == expectedHash;
		}

		private void Scan(Stream stream, Sha256.Hasher checksum)
		{
			int num = 0;
			int num2;
			while ((num2 = stream.Read(_buffer, num, _buffer.Length - num)) > 0)
			{
				checksum?.Append(_buffer, num, num2);
				num += num2;
				int num3 = ScanBuffer(num);
				int num4 = num - num3;
				Buffer.BlockCopy(_buffer, num3, _buffer, 0, num4);
				num = num4;
			}
		}

		private int ScanBuffer(int length)
		{
			byte[] buffer = _buffer;
			int i;
			for (i = 0; i + 64 < length; i++)
			{
				if (buffer[i] != 64)
				{
					continue;
				}
				int j;
				for (j = 1; j <= 64; j++)
				{
					byte b = buffer[i + j];
					if ((b < 48 || b > 57) && (b < 97 || b > 102))
					{
						break;
					}
				}
				if (j > 64)
				{
					if (_reached.Add(Key128.From(buffer, i + 1)))
					{
						_pending.Enqueue(Encoding.ASCII.GetString(buffer, i + 1, 64));
					}
					i += 64;
				}
			}
			return i;
		}
	}
	internal sealed class BoundedArchiveWriter : IDisposable
	{
		private sealed class Job
		{
			internal MemoryStream Payload;

			internal int Capacity;

			internal Task<bool> Task;
		}

		internal const long DefaultCapacityLimit = 33554432L;

		internal const int DefaultJobLimit = 8;

		internal const int DefaultWorkers = 4;

		private readonly long _capacityLimit;

		private readonly int _jobLimit;

		private readonly SemaphoreSlim _workers;

		private readonly List<Job> _jobs = new List<Job>();

		private readonly HashSet<string> _scheduled = new HashSet<string>(StringComparer.Ordinal);

		private long _retainedCapacity;

		private ExceptionDispatchInfo _failure;

		private bool _disposed;

		private int _activeWorkers;

		private int _peakWorkers;

		internal int WrittenCount { get; private set; }

		internal long PeakRetainedCapacity { get; private set; }

		internal int PeakJobs { get; private set; }

		internal int PeakWorkers => Volatile.Read(in _peakWorkers);

		internal long RetainedCapacity => _retainedCapacity;

		internal int PendingJobs => _jobs.Count;

		internal bool Full
		{
			get
			{
				ReapCompleted();
				if (_jobs.Count < _jobLimit)
				{
					return _retainedCapacity >= _capacityLimit * 3 / 4;
				}
				return true;
			}
		}

		internal bool Idle
		{
			get
			{
				ReapCompleted();
				return _jobs.Count == 0;
			}
		}

		internal BoundedArchiveWriter(long capacityLimit = 33554432L, int jobLimit = 8, int workers = 4)
		{
			if (capacityLimit <= 0 || jobLimit <= 0 || workers <= 0)
			{
				throw new ArgumentOutOfRangeException();
			}
			_capacityLimit = capacityLimit;
			_jobLimit = jobLimit;
			_workers = new SemaphoreSlim(workers, workers);
		}

		internal void Enqueue(string hash, MemoryStream payload, Func<Stream, bool> write)
		{
			bool flag = false;
			try
			{
				if (_disposed)
				{
					throw new ObjectDisposedException("BoundedArchiveWriter");
				}
				ReapCompleted();
				if (_failure != null)
				{
					Drain();
				}
				if (_scheduled.Contains(hash))
				{
					return;
				}
				int capacity = payload.Capacity;
				if (capacity > _capacityLimit)
				{
					Drain();
					_scheduled.Add(hash);
					payload.Position = 0L;
					if (write(payload))
					{
						WrittenCount++;
					}
					return;
				}
				while (_retainedCapacity + capacity > _capacityLimit || _jobs.Count >= _jobLimit)
				{
					WaitForAny();
					if (_failure != null)
					{
						Drain();
					}
				}
				_scheduled.Add(hash);
				Job job = new Job
				{
					Payload = payload,
					Capacity = capacity
				};
				job.Task = Task.Run(delegate
				{
					_workers.Wait();
					int num = Interlocked.Increment(ref _activeWorkers);
					int num2;
					do
					{
						num2 = Volatile.Read(in _peakWorkers);
					}
					while (num > num2 && Interlocked.CompareExchange(ref _peakWorkers, num, num2) != num2);
					try
					{
						job.Payload.Position = 0L;
						return write(job.Payload);
					}
					finally
					{
						job.Payload.Dispose();
						job.Payload = null;
						Interlocked.Decrement(ref _activeWorkers);
						_workers.Release();
					}
				});
				flag = true;
				_jobs.Add(job);
				_retainedCapacity += capacity;
				PeakRetainedCapacity = Math.Max(PeakRetainedCapacity, _retainedCapacity);
				PeakJobs = Math.Max(PeakJobs, _jobs.Count);
			}
			finally
			{
				if (!flag)
				{
					payload.Dispose();
				}
			}
		}

		private void ReapCompleted()
		{
			for (int num = _jobs.Count - 1; num >= 0; num--)
			{
				Job job = _jobs[num];
				if (job.Task.IsCompleted)
				{
					try
					{
						if (job.Task.GetAwaiter().GetResult())
						{
							WrittenCount++;
						}
					}
					catch (Exception source)
					{
						if (_failure == null)
						{
							_failure = ExceptionDispatchInfo.Capture(source);
						}
					}
					_retainedCapacity -= job.Capacity;
					_jobs.RemoveAt(num);
				}
			}
		}

		private void WaitForAny()
		{
			Task[] array = new Task[_jobs.Count];
			for (int i = 0; i < array.Length; i++)
			{
				array[i] = _jobs[i].Task;
			}
			Task.WaitAny(array);
			ReapCompleted();
		}

		internal void Drain()
		{
			while (_jobs.Count > 0)
			{
				WaitForAny();
			}
			_failure?.Throw();
		}

		public void Dispose()
		{
			if (_disposed)
			{
				return;
			}
			try
			{
				Drain();
			}
			finally
			{
				_disposed = true;
				_workers.Dispose();
			}
		}
	}
	internal static class BuildGuides
	{
		private static readonly HashSet<CircleProjector> Circles = new HashSet<CircleProjector>();

		private static readonly HashSet<StationExtension> Lines = new HashSet<StationExtension>();

		private static readonly FieldInfo PlacementMarker = AccessTools.Field(typeof(Player), "m_placementMarkerInstance");

		private static readonly FieldInfo ConnectionLine = AccessTools.Field(typeof(StationExtension), "m_connection");

		internal static void Noted(CircleProjector circle)
		{
			if ((Object)(object)circle != (Object)null)
			{
				Circles.Add(circle);
			}
		}

		internal static void Noted(StationExtension extension)
		{
			if ((Object)(object)extension != (Object)null)
			{
				Lines.Add(extension);
			}
		}

		internal static void Collect(HashSet<Renderer> excluded)
		{
			Circles.RemoveWhere((CircleProjector circle) => (Object)(object)circle == (Object)null);
			foreach (CircleProjector circle in Circles)
			{
				Add(((Component)circle).gameObject, excluded);
			}
			Lines.RemoveWhere((StationExtension extension) => (Object)(object)extension == (Object)null);
			if (ConnectionLine != null)
			{
				foreach (StationExtension line in Lines)
				{
					if (!line.m_continousConnection)
					{
						object? value = ConnectionLine.GetValue(line);
						GameObject val = (GameObject)((value is GameObject) ? value : null);
						if (val != null && (Object)(object)val != (Object)null)
						{
							Add(val, excluded);
						}
					}
				}
			}
			if (!(PlacementMarker != null))
			{
				return;
			}
			foreach (Player allPlayer in Player.GetAllPlayers())
			{
				if ((Object)(object)allPlayer != (Object)null)
				{
					object? value2 = PlacementMarker.GetValue(allPlayer);
					GameObject val2 = (GameObject)((value2 is GameObject) ? value2 : null);
					if (val2 != null && (Object)(object)val2 != (Object)null)
					{
						Add(val2, excluded);
					}
				}
			}
		}

		private static void Add(GameObject root, HashSet<Renderer> excluded)
		{
			Renderer[] componentsInChildren = root.GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null)
				{
					excluded.Add(val);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class Patch_CircleProjector_Noted
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(CircleProjector), "Start", (Type[])null, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		private static void Postfix(CircleProjector __instance)
		{
			BuildGuides.Noted(__instance);
		}
	}
	[HarmonyPatch]
	internal static class Patch_StationExtension_Noted
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(StationExtension), "StartConnectionEffect", new Type[2]
			{
				typeof(Vector3),
				typeof(float)
			}, (Type[])null);
		}

		private static bool Prepare()
		{
			return TargetMethod() != null;
		}

		private static void Postfix(StationExtension __instance)
		{
			BuildGuides.Noted(__instance);
		}
	}
	internal sealed class CaptureBudget
	{
		private readonly long _ticks;

		private long _deadline;

		internal static CaptureBudget Unlimited => new CaptureBudget(double.PositiveInfinity);

		internal bool IsUnlimited => _ticks == long.MaxValue;

		internal bool Spent
		{
			get
			{
				if (!IsUnlimited)
				{
					return Stopwatch.GetTimestamp() >= _deadline;
				}
				return false;
			}
		}

		internal CaptureBudget(double milliseconds)
		{
			_ticks = (double.IsPositiveInfinity(milliseconds) ? long.MaxValue : ((long)(milliseconds * (double)Stopwatch.Frequency / 1000.0)));
			Restart();
		}

		internal void Restart()
		{
			_deadline = (IsUnlimited ? long.MaxValue : (Stopwatch.GetTimestamp() + _ticks));
		}
	}
	internal sealed class CaptureCostSamples
	{
		internal const int Keep = 7;

		internal const int MinSamples = 3;

		internal const float MinSecondsBetween = 10f;

		private readonly List<float> _samples = new List<float>();

		private readonly List<float> _sorted = new List<float>();

		internal int Count => _samples.Count;

		internal bool Valid => _samples.Count >= 3;

		internal float MedianMs
		{
			get
			{
				if (_samples.Count == 0)
				{
					return 0f;
				}
				_sorted.Clear();
				_sorted.AddRange(_samples);
				_sorted.Sort();
				int num = _sorted.Count / 2;
				if (_sorted.Count % 2 != 1)
				{
					return (_sorted[num - 1] + _sorted[num]) / 2f;
				}
				return _sorted[num];
			}
		}

		internal void Add(float ms)
		{
			if (ms > 0f && !float.IsInfinity(ms))
			{
				_samples.Add(ms);
				if (_samples.Count > 7)
				{
					_samples.RemoveAt(0);
				}
			}
		}

		internal static bool WantsSample(int capturesOfScene, bool sceneTimed, float secondsSinceLast)
		{
			if (capturesOfScene >= 2 && !sceneTimed)
			{
				return secondsSinceLast >= 10f;
			}
			return false;
		}
	}
	internal enum MeasuredLoad
	{
		Normal,
		Reduced,
		Minimal
	}
	internal static class CaptureBudgetPolicy
	{
		internal const float Share = 0.25f;

		internal const int MaxFramesBetween = 30;

		internal const float ReducedAbove = 12f;

		internal const float MinimalAbove = 24f;

		internal const float Hysteresis = 0.8f;

		private static readonly int[] Sizes = new int[5] { 1024, 1536, 2048, 3072, 4096 };

		private static readonly float[] Factors = new float[5] { 0.78f, 0.94f, 1f, 1.2f, 1.4f };

		internal static int MinFramesBetween(bool valid, float costMs, float baseMs, float share = 0.25f)
		{
			if (!valid || !(costMs > 0f) || !(baseMs > 0f))
			{
				return 1;
			}
			double val = Math.Ceiling((double)((1f - share) * costMs / (share * baseMs)) - 1E-06);
			return (int)Math.Max(1.0, Math.Min(30.0, val));
		}

		internal static MeasuredLoad Level(MeasuredLoad current, bool valid, float costAtReference)
		{
			if (!valid)
			{
				return current;
			}
			MeasuredLoad measuredLoad = ((costAtReference > 24f) ? MeasuredLoad.Minimal : ((costAtReference > 12f) ? MeasuredLoad.Reduced : MeasuredLoad.Normal));
			if (measuredLoad >= current)
			{
				return measuredLoad;
			}
			if (current == MeasuredLoad.Minimal && costAtReference >= 19.2f)
			{
				return MeasuredLoad.Minimal;
			}
			if (costAtReference >= 9.6f)
			{
				return MeasuredLoad.Reduced;
			}
			return MeasuredLoad.Normal;
		}

		internal static float CostAtReference(float costMs, int eyeCaptureLimit)
		{
			return costMs / Factor(eyeCaptureLimit);
		}

		internal static float CostAt(float costAtReference, int eyeCaptureLimit)
		{
			return costAtReference * Factor(eyeCaptureLimit);
		}

		private static float Factor(int eyeCaptureLimit)
		{
			float result = Factors[Factors.Length - 1];
			if (eyeCaptureLimit <= Sizes[0])
			{
				result = Factors[0];
			}
			else
			{
				for (int i = 1; i < Sizes.Length; i++)
				{
					if (eyeCaptureLimit <= Sizes[i])
					{
						float num = (float)(eyeCaptureLimit - Sizes[i - 1]) / (float)(Sizes[i] - Sizes[i - 1]);
						result = Factors[i - 1] + (Factors[i] - Factors[i - 1]) * num;
						break;
					}
				}
			}
			return result;
		}
	}
	internal static class CaptureFilterChecks
	{
		internal static void Verify(int layer, Action<bool, string> require)
		{
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: 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_00d9: Expected O, but got Unknown
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_010a: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: 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_012d: 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_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: 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)
			int num = LayerMask.NameToLayer("ghost");
			require(num >= 0, "Valheim's placement-ghost layer is available");
			List<Object> owned = new List<Object>();
			try
			{
				Vector3 val = default(Vector3);
				((Vector3)(ref val))..ctor(0f, 14000f, 0f);
				Mesh mesh = new Mesh();
				owned.Add((Object)(object)mesh);
				mesh.vertices = (Vector3[])(object)new Vector3[3]
				{
					Vector3.zero,
					Vector3.right,
					Vector3.up
				};
				mesh.triangles = new int[3] { 0, 1, 2 };
				mesh.RecalculateBounds();
				mesh.RecalculateNormals();
				Material material = new Material(PortalProjection.FindShader("Standard"));
				owned.Add((Object)(object)material);
				Func<string, Vector3, int, Renderer> obj = delegate(string name, Vector3 position, int sourceLayer)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_0007: Expected O, but got Unknown
					//IL_0020: Unknown result type (might be due to invalid IL or missing references)
					GameObject val8 = new GameObject(name);
					owned.Add((Object)(object)val8);
					val8.layer = sourceLayer;
					val8.transform.position = position;
					val8.AddComponent<MeshFilter>().sharedMesh = mesh;
					MeshRenderer obj2 = val8.AddComponent<MeshRenderer>();
					((Renderer)obj2).sharedMaterial = material;
					return (Renderer)(object)obj2;
				};
				Renderer val2 = obj("Capture filter ordinary piece", val, 0);
				Renderer val3 = obj("Capture filter nearby ghost", val + Vector3.right * 2f, num);
				Renderer val4 = obj("Capture filter distant ghost", val + Vector3.forward * 50f, num);
				Light val5 = ((Component)val2).gameObject.AddComponent<Light>();
				val5.type = (LightType)2;
				val5.range = 5f;
				Light val6 = ((Component)val3).gameObject.AddComponent<Light>();
				val6.type = (LightType)2;
				val6.range = 5f;
				using PortalSnapshot portalSnapshot = PortalSnapshot.Capture(val, Quaternion.identity, 8f, layer, (Renderer[])(object)new Renderer[3] { val2, val3, val4 }, (Light[])(object)new Light[2] { val5, val6 });
				require(portalSnapshot.MeshCount == 1, "placement ghost does not become frozen scene geometry");
				require(portalSnapshot.LightCount == 1, "placement ghost light is excluded while ordinary light is kept");
				HashSet<Renderer> hashSet = (HashSet<Renderer>)AccessTools.Field(typeof(PortalSnapshot), "_backdropExcludedSources").GetValue(portalSnapshot);
				require(hashSet.Contains(val3) && hashSet.Contains(val4), "near and beyond-radius placement ghosts are both hidden from the panorama");
				require(!val3.forceRenderingOff && !val4.forceRenderingOff && ((Behaviour)val6).enabled && ((Behaviour)val5).enabled, "capture restores the live ghost renderers and lights");
				require(!portalSnapshot.ShouldExcludeCaptureSource(((Component)val2).transform), "ordinary world geometry remains eligible");
				foreach (Player allPlayer in Player.GetAllPlayers())
				{
					require(portalSnapshot.ShouldExcludeCaptureSource(((Component)allPlayer).transform), "every player's root is excluded independently of capture distance");
					Light[] componentsInChildren = ((Component)allPlayer).GetComponentsInChildren<Light>(true);
					foreach (Light val7 in componentsInChildren)
					{
						require(portalSnapshot.ShouldExcludeCaptureSource(((Component)val7).transform), "players' attached lights are excluded");
					}
				}
			}
			finally
			{
				foreach (Object item in owned)
				{
					if (item != (Object)null)
					{
						Object.Destroy(item);
					}
				}
			}
		}
	}
	internal static class CaptureRetry
	{
		internal const float MinimumRadius = 32f;

		internal const float MaximumDelay = 600f;

		internal static float Delay(int failures)
		{
			return (float)Math.Min(600.0, 30.0 * Math.Pow(2.0, Math.Max(0, failures - 1)));
		}

		internal static float SmallerRadius(float radius)
		{
			return Math.Max(32f, radius * 0.75f);
		}
	}
	internal sealed class SnapshotLimitException : InvalidOperationException
	{
		internal SnapshotLimitException(string message)
			: base(message)
		{
		}
	}
	internal struct Lighting
	{
		public string Environment;

		public Texture AuroraGradient;

		public Quaternion SunRotation;

		public Vector3 SkyboxSunDir;

		public Color SunColor;

		public Color FogColor;

		public Color SunFogColor;

		public Color Ambient;

		public float SunIntensity;

		public float FogDensity;

		public float Wet;

		public float Aurora;

		internal bool Differs(in Lighting other)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			if (Environment != other.Environment)
			{
				return true;
			}
			if (Quaternion.Angle(SunRotation, other.SunRotation) >= 10f)
			{
				return true;
			}
			if (Far(SunColor * SunIntensity, other.SunColor * other.SunIntensity) || Far(Ambient, other.Ambient) || Far(FogColor, other.FogColor) || Far(SunFogColor, other.SunFogColor))
			{
				return true;
			}
			if (Mathf.Abs(FogDensity - other.FogDensity) > 0.15f * Mathf.Max(new float[3] { FogDensity, other.FogDensity, 0.0001f }))
			{
				return true;
			}
			if (!(Mathf.Abs(Aurora - other.Aurora) > 0.05f))
			{
				return Wet != other.Wet;
			}
			return true;
		}

		private static bool Far(Color a, Color b)
		{
			//IL_0000: 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_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (!(Mathf.Abs(a.r - b.r) > 0.03f) && !(Mathf.Abs(a.g - b.g) > 0.03f))
			{
				return Mathf.Abs(a.b - b.b) > 0.03f;
			}
			return true;
		}
	}
	internal static class DestinationLighting
	{
		private static readonly List<EnvEntry> Available = new List<EnvEntry>();

		private const float EyeHeight = 1.6f;

		internal static bool TryCompute(Vector3 position, out Lighting lighting)
		{
			//IL_0007: 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)
			lighting = default(Lighting);
			if (!TryEnvironment(position, out var setup))
			{
				return false;
			}
			EnvMan instance = EnvMan.instance;
			float dayFraction = instance.GetDayFraction();
			lighting = At(instance, setup, dayFraction);
			lighting.FogDensity = OtherAir.FogDensity(setup, lighting.FogDensity, dayFraction, position.y + 1.6f);
			return true;
		}

		internal static bool TryEnvironment(Vector3 position, out EnvSetup setup)
		{
			//IL_0026: 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)
			setup = null;
			EnvMan instance = EnvMan.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)ZNet.instance == (Object)null || WorldGenerator.instance == null || Character.InInterior(position))
			{
				return false;
			}
			setup = Weather(instance, position);
			return setup != null;
		}

		internal static string CompareWithLive(Vector3 position)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: 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_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ff: 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_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_0198: Unknown result type (might be due to invalid IL or missing references)
			EnvMan instance = EnvMan.instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)instance.m_dirLight == (Object)null)
			{
				return "no EnvMan";
			}
			if (!TryCompute(position, out var lighting))
			{
				return "no lighting computed here (interior or no weather)";
			}
			Light dirLight = instance.m_dirLight;
			EnvSetup currentEnvironment = instance.GetCurrentEnvironment();
			return "weather computed " + lighting.Environment + ", live " + ((currentEnvironment != null) ? currentEnvironment.m_name : "none") + "; " + $"sun angle {Quaternion.Angle(lighting.SunRotation, ((Component)dirLight).transform.rotation):F1} deg, " + $"intensity {lighting.SunIntensity:F3}/{dirLight.intensity:F3}, colour {lighting.SunColor}/{dirLight.color}; " + $"ambient {lighting.Ambient}/{RenderSettings.ambientLight}; fog {lighting.FogColor}/{RenderSettings.fogColor}, " + string.Format("density {0:F5}/{1:F5}; aurora {2:F2}/{3:F2}; ", lighting.FogDensity, RenderSettings.fogDensity, lighting.Aurora, Shader.GetGlobalFloat("_AuroraStrength")) + $"probe difference {ProbeDifference(PortalSnapshot.FlatProbe(RenderSettings.ambientLight), RenderSettings.ambientProbe):F4}";
		}

		internal static float ProbeDifference(SphericalHarmonicsL2 a, SphericalHarmonicsL2 b)
		{
			float num = 0f;
			for (int i = 0; i < 3; i++)
			{
				for (int j = 0; j < 9; j++)
				{
					num = Mathf.Max(num, Mathf.Abs(((SphericalHarmonicsL2)(ref a))[i, j] - ((SphericalHarmonicsL2)(ref b))[i, j]));
				}
			}
			return num;
		}

		internal static Lighting At(EnvMan env, EnvSetup setup, float fraction)
		{
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0191: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: 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_019c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cb: 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_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_0242: Unknown result type (might be due to invalid IL or missing references)
			//IL_0244: Unknown result type (might be due to invalid IL or missing references)
			//IL_024c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_0258: Unknown result type (might be due to invalid IL or missing references)
			//IL_025e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0263: Unknown result type (might be due to invalid IL or missing references)
			//IL_0269: Unknown result type (might be due to invalid IL or missing references)
			//IL_0270: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Unknown result type (might be due to invalid IL or missing references)
			//IL_027b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0282: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_028c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Unknown result type (might be due to invalid IL or missing references)
			//IL_020a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0210: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Unknown result type (might be due to invalid IL or missing references)
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_0228: Unknown result type (might be due to invalid IL or missing references)
			//IL_022f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0239: Unknown result type (might be due to invalid IL or missing references)
			//IL_023e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0302: Unknown result type (might be due to invalid IL or missing references)
			//IL_0336: Unknown result type (might be due to invalid IL or missing references)
			//IL_033c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0342: Unknown result type (might be due to invalid IL or missing references)
			//IL_0347: Unknown result type (might be due to invalid IL or missing references)
			//IL_0350: Unknown result type (might be due to invalid IL or missing references)
			//IL_0356: Unknown result type (might be due to invalid IL or missing references)
			//IL_035c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0361: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02de: Unknown result type (might be due to invalid IL or missing references)
			float num = (((Object)(object)env != (Object)null) ? env.m_sunHorizonTransitionL : 0.02f);
			float num2 = (((Object)(object)env != (Object)null) ? env.m_sunHorizonTransitionH : 0.08f);
			float num3 = Mathf.Pow(Mathf.Max(1f - Mathf.Clamp01(fraction / 0.25f), Mathf.Clamp01((fraction - 0.75f) / 0.25f)), 0.5f);
			float num4 = Mathf.Pow(Mathf.Clamp01(1f - Mathf.Abs(fraction - 0.5f) / 0.25f), 0.5f);
			float num5 = Mathf.Min(Mathf.Clamp01(1f - (fraction - 0.26f) / (0f - num)), Mathf.Clamp01(1f - (fraction - 0.26f) / num2));
			float num6 = Mathf.Min(Mathf.Clamp01(1f - (fraction - 0.74f) / (0f - num2)), Mathf.Clamp01(1f - (fraction - 0.74f) / num));
			float num7 = num4 + num3 + num5 + num6;
			if (num7 > 0f)
			{
				num4 /= num7;
				num3 /= num7;
				num5 /= num7;
				num6 /= num7;
			}
			Lighting result = new Lighting
			{
				Environment = setup.m_name,
				AuroraGradient = (Texture)(object)setup.m_auroraGradientTexture
			};
			Quaternion val = Quaternion.Euler(-90f + setup.m_sunAngle, 0f, 0f) * Quaternion.Euler(0f, -90f, 0f) * Quaternion.Euler(-90f + 360f * fraction, 0f, 0f);
			result.SkyboxSunDir = -(val * Vector3.forward);
			result.SunIntensity = setup.m_lightIntensityDay * num4 + setup.m_lightIntensityNight * num3;
			if (num3 > 0f)
			{
				val *= Quaternion.Euler(180f, 0f, 0f);
			}
			result.SunRotation = val;
			Color val2 = setup.m_sunColorNight * num3;
			if (num4 > 0f)
			{
				val2 += setup.m_sunColorDay * num4 + setup.m_sunColorMorning * num5 + setup.m_sunColorEvening * num6;
			}
			result.SunColor = val2;
			result.FogColor = setup.m_fogColorNight * num3 + setup.m_fogColorDay * num4 + setup.m_fogColorMorning * num5 + setup.m_fogColorEvening * num6;
			Color val3 = setup.m_fogColorSunNight * num3;
			if (num4 > 0f)
			{
				val3 += setup.m_fogColorSunDay * num4 + setup.m_fogColorSunMorning * num5 + setup.m_fogColorSunEvening * num6;
			}
			result.SunFogColor = Color.Lerp(result.FogColor, val3, Mathf.Clamp01(Mathf.Max(num3, num4) * 3f));
			result.FogDensity = setup.m_fogDensityNight * num3 + setup.m_fogDensityDay * num4 + setup.m_fogDensityMorning * num5 + setup.m_fogDensityEvening * num6;
			result.Ambient = Color.Lerp(setup.m_ambColorNight, setup.m_ambColorDay, num4);
			result.Ambient = WeatherOverhaulBridge.Ambient(result.Ambient, setup.m_ambColorDay, num3);
			result.Wet = (setup.m_isWet ? 1f : 0f);
			result.Aurora = setup.m_auroraIntensityDay * num4 + setup.m_auroraIntensityEvening * num6 + setup.m_auroraIntensityMorning * num5 + setup.m_auroraIntensityNight * num3;
			return result;
		}

		private static EnvSetup Weather(EnvMan env, Vector3 position)
		{
			//IL_0005: 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_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			BiomeSector biomeSector = WorldGenerator.instance.GetBiomeSector(position, false);
			string text = null;
			foreach (AltBiome altBiome in biomeSector.AltBiomes)
			{
				if (!string.IsNullOrEmpty(altBiome.m_forceEnvironment))
				{
					text = altBiome.m_forceEnvironment;
					break;
				}
			}
			if (text == null)
			{
				text = PersistentEventEnvironment(position);
			}
			if (text != null)
			{
				foreach (EnvSetup environment in env.m_environments)
				{
					if (environment.m_name == text)
					{
						return environment;
					}
				}
				return null;
			}
			long num = (long)ZNet.instance.GetTimeSeconds() / env.m_environmentDuration;
			List<EnvEntry> availableEnvironments = env.GetAvailableEnvironments(biomeSector);
			if (availableEnvironments == null || availableEnvironments.Count == 0)
			{
				return null;
			}
			Available.Clear();
			Available.AddRange(availableEnvironments);
			bool flag = WorldGenerator.IsAshlands(position.x, position.z);
			bool flag2 = WorldGenerator.IsDeepnorth(position.x, position.y);
			State state = Random.state;
			try
			{
				Random.InitState((int)num);
				EnvSetup result = SelectWeighted(Available);
				foreach (EnvEntry item in Available)
				{
					if (item.m_ashlandsOverride && flag)
					{
						result = item.m_env;
					}
					if (item.m_deepnorthOverride && flag2)
					{
						result = item.m_env;
					}
				}
				return result;
			}
			finally
			{
				Random.state = state;
			}
		}

		private static string PersistentEventEnvironment(Vector3 position)
		{
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			PersistentEventSystem instance = PersistentEventSystem.instance;
			if ((Object)(object)instance == (Object)null || instance.m_activePersistentEvents == null)
			{
				return null;
			}
			foreach (ActivePersistentEvent item in instance.m_activePersistentEvents.list)
			{
				Vector3 val = item.position - position;
				if (((Vector3)(ref val)).sqrMagnitude < item.radius * item.radius)
				{
					string environmentOverride = item.Source.GetEnvironmentOverride(position);
					return string.IsNullOrEmpty(environmentOverride) ? null : environmentOverride;
				}
			}
			return null;
		}

		private static EnvSetup SelectWeighted(List<EnvEntry> environments)
		{
			float num = 0f;
			foreach (EnvEntry environment in environments)
			{
				if (!environment.m_ashlandsOverride && !environment.m_deepnorthOverride)
				{
					num += environment.m_weight;
				}
			}
			float num2 = Random.Range(0f, num);
			float num3 = 0f;
			foreach (EnvEntry environment2 in environments)
			{
				if (!environment2.m_ashlandsOverride && !environment2.m_deepnorthOverride)
				{
					num3 += environment2.m_weight;
					if (num3 >= num2)
					{
						return environment2.m_env;
					}
				}
			}
			EnvEntry val = environments[environments.Count - 1];
			if (!val.m_ashlandsOverride && !val.m_deepnorthOverride)
			{
				return val.m_env;
			}
			return null;
		}
	}
	internal static class FarLandsBridge
	{
		private static bool _probed;

		private static Func<bool> _ownsFog;

		private static Func<EnvSetup, float, float, float, float> _fogDensity;

		internal static bool OwnsFog
		{
			get
			{
				Probe();
				if (_ownsFog == null)
				{
					return false;
				}
				try
				{
					return _ownsFog();
				}
				catch (Exception ex)
				{
					_ownsFog = null;
					ManualLogSource log = PortalPreviewPlugin.Log;
					if (log != null)
					{
						log.LogWarning((object)("FarLands' air could not be read (" + ex.GetType().Name + ": " + ex.Message + "); portals follow the game's fog switch."));
					}
					return false;
				}
			}
		}

		internal static float FogDensity(EnvSetup setup, float density, float dayFraction, float height)
		{
			Probe();
			if (_fogDensity == null)
			{
				return density;
			}
			try
			{
				float num = _fogDensity(setup, density, dayFraction, height);
				return (float.IsNaN(num) || float.IsInfinity(num) || num < 0f) ? density : num;
			}
			catch (Exception ex)
			{
				_fogDensity = null;
				ManualLogSource log = PortalPreviewPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("FarLands' air could not be read (" + ex.GetType().Name + ": " + ex.Message + "); portals keep the game's fog density."));
				}
				return density;
			}
		}

		private static void Probe()
		{
			if (_probed)
			{
				return;
			}
			_probed = true;
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			for (int i = 0; i < assemblies.Length; i++)
			{
				Type type = assemblies[i].GetType("FarLands.FarLandsAir");
				if (!(type == null))
				{
					MethodInfo methodInfo = type.GetProperty("OwnsFog", BindingFlags.Static | BindingFlags.Public)?.GetGetMethod();
					if (methodInfo != null && methodInfo.ReturnType == typeof(bool))
					{
						_ownsFog = (Func<bool>)Delegate.CreateDelegate(typeof(Func<bool>), methodInfo);
					}
					MethodInfo method = type.GetMethod("Density", BindingFlags.Static | BindingFlags.Public, null, new Type[4]
					{
						typeof(EnvSetup),
						typeof(float),
						typeof(float),
						typeof(float)
					}, null);
					if (method != null && method.ReturnType == typeof(float))
					{
						_fogDensity = (Func<EnvSetup, float, float, float, float>)Delegate.CreateDelegate(typeof(Func<EnvSetup, float, float, float, float>), method);
					}
					if (_ownsFog != null)
					{
						PortalPreviewPlugin.Diag("FarLands found: portal views take its air's fog while it runs.");
					}
					break;
				}
			}
		}
	}
	internal static class OtherAir
	{
		internal static bool OwnsFog
		{
			get
			{
				if (!WeatherOverhaulBridge.OwnsFog)
				{
					return FarLandsBridge.OwnsFog;
				}
				return true;
			}
		}

		internal static float FogDensity(EnvSetup setup, float density, float dayFraction, float height)
		{
			return FarLandsBridge.FogDensity(setup, WeatherOverhaulBridge.FogDensity(setup, density, dayFraction, height), dayFraction, height);
		}
	}
	internal static class FrozenPortals
	{
		private sealed class SceneCaptureJob
		{
			public Endpoint Capture;

			public IEnumerator<bool> Steps;

			public CaptureBudget Budget;

			public long Started;

			public int Frames;

			public double LongestStepMs;

			public bool SceneTaken;

			public string LongestStepDetail;
		}

		private sealed class Endpoint
		{
			public readonly PortalViewBudget.Entry<Endpoint> ViewState = new PortalViewBudget.Entry<Endpoint>();

			public ZDOID Id;

			public ZDOID Target;

			public TeleportWorld Portal;

			public PortalSnapshot Snapshot;

			public bool SnapshotVerified;

			public float RetryAt;

			public float LoadRetryAt;

			public float LastUsed;

			public bool Reported;

			private string _lastError;

			public string WaitReason;

			public bool Refresh;

			public bool FirstLinkCapture;

			public bool SkipArchiveLoad;

			public float StableAt;

			public float NextZdoRequestAt;

			public int Failures;

			public float CaptureRadius;

			public float CapturedAt = float.NegativeInfinity;

			public CaptureWhen When;

			public bool Observed;

			public float NoArrivalHoldUntil;

			public ulong? Fingerprint;

			public bool FingerprintKnown;

			public bool CopiesPending;

			public string LastError
			{
				get
				{
					return _lastError;
				}
				set
				{
					_lastError = value;
					DiskLoadFailed = false;
				}
			}

			public bool DiskLoadFailed { get; private set; }

			public void FailDiskLoad(string message)
			{
				LastError = "disk load failed: " + message;
				DiskLoadFailed = true;
			}
		}

		private enum CaptureWhen
		{
			Now,
			WhenShown,
			AtTeleport
		}

		private sealed class ArchivedScene
		{
			public string Id;

			public string Path;

			public Vector3 Position;

			public Quaternion Rotation;

			public int Version;

			public DateTime WrittenUtc;
		}

		private sealed class AtmosphereState
		{
			internal readonly PortalAtmosphere Effect = new PortalAtmosphere();

			internal EnvSetup Environment;

			internal float CheckAt;

			internal ZDOID Target;
		}

		private struct ViewCandidate
		{
			public Endpoint Endpoint;

			public PortalSnapshot Snapshot;

			public PortalView View;
		}

		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__15_0;

			public static Comparison<Endpoint> <>9__154_0;

			internal void <Initialize>b__15_0(ConsoleEventArgs args)
			{
				//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
				string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status");
				switch (text)
				{
				case "on":
					Clear();
					PortalPreviewPlugin.Cfg.Enabled.Value = true;
					_status = "enabled; waiting for a loaded wooden or stone portal";
					break;
				case "off":
					PortalPreviewPlugin.Cfg.Enabled.Value = false;
					Clear();
					break;
				case "clear":
					Clear();
					_status = "RAM cache cleared; saved snapshots retained on disk";
					break;
				case "backdropcheck":
				{
					if (RefreshNearby() == 0)
					{
						args.Context.AddString("No connected portal within 24 m to capture.");
						return;
					}
					string text3 = Path.Combine(Paths.BepInExRootPath, "cache", "PortalPreview", "diagnostics", "backdrop-" + DateTime.Now.ToString("yyyyMMdd-HHmmss"));
					Directory.CreateDirectory(text3);
					PortalSnapshot.BackdropDiagnostics = text3;
					_viewDiagnostics = text3;
					_diagnosticsUntil = Time.realtimeSinceStartup + 60f;
					args.Context.AddString("The next capture of a nearby portal writes a panorama and view comparison to " + text3);
					break;
				}
				case "refresh":
					RefreshNearby();
					break;
				case "log":
					if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
					{
						args.Context.AddString("Usage: frozenportal log on|off");
						return;
					}
					PortalPreviewPlugin.Verbose = args[2] == "on";
					args.Context.AddString("Portal diagnostics " + (PortalPreviewPlugin.Verbose ? "enabled" : "disabled") + " for this session.");
					return;
				case "lighting":
				{
					if ((Object)(object)Player.m_localPlayer == (Object)null)
					{
						args.Context.AddString("Enter a world first.");
						return;
					}
					string text2 = "Lighting computed/live here: " + DestinationLighting.CompareWithLive(((Component)Player.m_localPlayer).transform.position);
					args.Context.AddString(text2);
					PortalPreviewPlugin.Log.LogInfo((object)("[portal preview] " + text2));
					return;
				}
				case "cleanup":
					if ((Object)(object)ZNet.instance == (Object)null)
					{
						args.Context.AddString("Enter a world first.");
						return;
					}
					PortalCleanup.Start(ArchiveDirectory);
					_status = "cleanup of stale saved scenes started; 'frozenportal status' shows the result";
					break;
				case "mode":
					if (args.Length != 3 || (args[2] != "always" && args[2] != "approach"))
					{
						args.Context.AddString("Usage: frozenportal mode always|approach");
						return;
					}
					PortalPreviewPlugin.Cfg.FrozenPortalAlwaysVisible.Value = args[2] == "always";
					break;
				case "profile":
				{
					if (args.Length != 3 || !Enum.TryParse<HardwareProfileSetting>(args[2], ignoreCase: true, out var result))
					{
						args.Context.AddString("Usage: frozenportal profile auto|low|medium|high|ultra|custom");
						return;
					}
					PortalPreviewPlugin.Cfg.HardwareProfile.Value = result;
					PreviewConfig cfg = PortalPreviewPlugin.Cfg;
					if (cfg.FrozenPortalResolution.Value > 0 || cfg.FrozenPortalMaxVisible.Value > 0 || cfg.FrozenPortalMaxResident.Value > 0)
					{
						cfg.FrozenPortalResolution.Value = 0;
						cfg.FrozenPortalMaxVisible.Value = 0;
						cfg.FrozenPortalMaxResident.Value = 0;
						args.Context.AddString("Resolution, Max visible portals and Max resident snapshots follow the profile again (set to 0).");
					}
					break;
				}
				case "count":
				case "spiral":
				case "radius":
				case "memory":
				{
					if (args.Length != 3 || !float.TryParse(args[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || float.IsNaN(result2) || float.IsInfinity(result2))
					{
						args.Context.AddString("Usage: frozenportal " + text + " <number>");
						return;
					}
					if (text == "count")
					{
						PortalPreviewPlugin.Cfg.FrozenPortalMaxVisible.Value = ((!(result2 <= 0f)) ? Mathf.Clamp(Mathf.RoundToInt(result2), 1, 16) : 0);
					}
					if (text == "memory")
					{
						PortalPreviewPlugin.Cfg.FrozenPortalMaxResident.Value = ((!(result2 <= 0f)) ? Mathf.Clamp(Mathf.RoundToInt(result2), 2, 128) : 0);
					}
					if (text == "radius")
					{
						PortalPreviewPlugin.Cfg.FrozenPortalApproachRadius.Value = Mathf.Clamp(result2, 1f, 100f);
					}
					if (text == "spiral")
					{
						PortalPreviewPlugin.Cfg.FrozenPortalRevealSeconds.Value = Mathf.Clamp(result2, 0f, 5f);
					}
					break;
				}
				case "recapture":
				{
					if (args.Length != 3 || !float.TryParse(args[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) || result4 < 0.02f || result4 > 1f)
					{
						args.Context.AddString("Usage: frozenportal recapture <metres, 0.02-1; 0.3 by default>");
						return;
					}
					PortalView.EyeRecaptureDistance = result4;
					_status = $"eye recapture every {result4:F2} m (diagnostic, not saved)";
					break;
				}
				case "pair":
					if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
					{
						args.Context.AddString("Usage: frozenportal pair on|off");
						return;
					}
					PortalView.EyePrediction = args[2] == "on";
					_status = "eye capture pair " + args[2] + " (diagnostic, not saved)";
					break;
				case "mainview":
					if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
					{
						args.Context.AddString("Usage: frozenportal mainview on|off");
						return;
					}
					PortalView.MainViewOnly = args[2] == "on";
					_status = "main view only " + args[2] + " (diagnostic, not saved)";
					break;
				case "load":
				{
					if (args.Length != 3 || (!Enum.TryParse<MeasuredLoad>(args[2], ignoreCase: true, out var result3) && args[2] != "auto"))
					{
						args.Context.AddString("Usage: frozenportal load normal|reduced|minimal|auto");
						return;
					}
					HardwareProfile.ForcedLoad = ((args[2] == "auto") ? ((MeasuredLoad?)null) : new MeasuredLoad?(result3));
					_status = "load " + args[2] + " (diagnostic, not saved)";
					break;
				}
				case "buffers":
					if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
					{
						args.Context.AddString("Usage: frozenportal buffers on|off");
						return;
					}
					PortalView.WriteVirtualBuffers = args[2] == "on";
					_status = "portal depth and motion vectors " + (PortalView.WriteVirtualBuffers ? "written" : "not written (diagnostic)");
					break;
				default:
					args.Context.AddString("Usage: frozenportal on|off|status|clear|refresh|backdropcheck|cleanup|lighting|buffers on|off|pair on|off|mainview on|off|load normal|reduced|minimal|auto|recapture <m>; profile auto|low|medium|high|ultra|custom; mode always|approach; count|radius|spiral|memory <number> (count and memory 0: the profile's)");
					return;
				case "status":
					break;
				}
				args.Context.AddString($"Frozen portals (scene-v20): {_status}; {CountSnapshots()}/{MaxSnapshots} resident snapshots; " + string.Format("up to {0} views; {1}. ", MaxVisible, PortalPreviewPlugin.Cfg.FrozenPortalAlwaysVisible.Value ? "always" : ("approach " + PortalPreviewPlugin.Cfg.FrozenPortalApproachRadius.Value + " m")) + $"Snapshot updates: {PortalPreviewPlugin.Cfg.SnapshotUpdates.Value}; " + "Scene, grass, particles, clouds and world tree frozen; sun and fog " + (PortalPreviewPlugin.Cfg.FollowTimeOfDay.Value ? "follow the time of day" : "frozen") + "; disk snapshots retained.");
				args.Context.AddString(HardwareProfile.Describe());
				if (PortalCleanup.LastReport != null)
				{
					args.Context.AddString("Last cleanup: " + PortalCleanup.LastReport);
				}
				string text4 = DescribeNearest(Utils.GetMainCamera());
				args.Context.AddString(text4);
				PortalPreviewPlugin.Diag("[portal preview] status: " + text4);
			}

			internal int <CaptureOne>b__154_0(Endpoint a, Endpoint b)
			{
				//IL_000a: 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_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				//IL_0049: Unknown result type (might be due to invalid IL or missing references)
				//IL_004e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0053: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = ((Component)Player.m_localPlayer).transform.position - ((Component)a.Portal).transform.position;
				float sqrMagnitude = ((Vector3)(ref val)).sqrMagnitude;
				val = ((Component)Player.m_localPlayer).transform.position - ((Component)b.Portal).transform.position;
				return sqrMagnitude.CompareTo(((Vector3)(ref val)).sqrMagnitude);
			}

			internal int <.cctor>b__193_0(Endpoint a, Endpoint b)
			{
				return DistanceSquaredToPlayer(a).CompareTo(DistanceSquaredToPlayer(b));
			}
		}

		internal const double PlayCaptureBudgetMs = 4.0;

		private static SceneCaptureJob _sceneCapture;

		private static readonly Dictionary<ZDOID, Endpoint> Endpoints = new Dictionary<ZDOID, Endpoint>();

		private static readonly List<ZDOID> Stale = new List<ZDOID>();

		private static readonly Dictionary<ZDOID, PortalView> Views = new Dictionary<ZDOID, PortalView>();

		private static readonly HashSet<ZDOID> VisibleTargets = new HashSet<ZDOID>();

		private static readonly List<ArchivedScene> ArchivedScenes = new List<ArchivedScene>();

		private static string _indexedArchiveDirectory;

		private static ZNet _session;

		private static int _layer = -1;

		private static bool _failed;

		private static float _maintenanceAt;

		private static string _status = "off";

		private static string _lastViewDiagnostic;

		private static float _viewDiagnosticAt;

		private static float _statusAt;

		private static bool _renderAllowed;

		private static int _renderedFrame = -1;

		private const float ViewRetentionSeconds = 20f;

		private static readonly List<Endpoint> CandidateBuffer = new List<Endpoint>();

		private static readonly List<ViewCandidate> Renderable = new List<ViewCandidate>();

		private static readonly Plane[] FrustumPlanes = (Plane[])(object)new Plane[6];

		private static readonly FieldInfo LoadedHeightmaps = AccessTools.Field(typeof(Heightmap), "s_heightmaps");

		private static float _resumePreviewAt;

		private static IEnumerator<PortalSnapshot> _loadSteps;

		private static Endpoint _loading;

		private static long _loadStarted;

		private static double _loadWorkMs;

		private static ZDOID _arrival;

		private static ZDOID _teleportCapture;

		private static readonly TeleportCaptureGate TeleportGate = new TeleportCaptureGate();

		private static Vector3 _departurePlayerPosition;

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

		private static bool _wasTeleporting;

		private static bool _screenlessTeleports;

		private static bool _screenSeenThisTeleport;

		private static double _frameCostMaxMs;

		private static readonly CaptureCostSamples CaptureCost = new CaptureCostSamples();

		private static int _lastEyeCaptureFrame = -1000;

		private static readonly float[] FrameRing = new float[120];

		private static readonly float[] FrameSorted = new float[120];

		private static int _frameRingCount;

		private static int _frameRingNext;

		private static float _frameMedianMs;

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

		private static float _nextSlowFrameLogAt;

		private static int _frameCostSlow;

		private static int _frameCostCaptures;

		private static double _frameCostMs;

		private static int _frameCostFrames;

		private static float _frameCostAt;

		private static readonly Comparison<Endpoint> NearestFirst = (Endpoint a, Endpoint b) => DistanceSquaredToPlayer(a).CompareTo(DistanceSquaredToPlayer(b));

		private static readonly List<PortalRefreshScheduler.Request> RefreshRequests = new List<PortalRefreshScheduler.Request>();

		private static readonly List<float> ScreenHeights = new List<float>();

		private static ZDOID _mainView;

		private const float EyeCaptureRetentionSeconds = 2f;

		private static readonly Dictionary<ZDOID, AtmosphereState> Atmospheres = new Dictionary<ZDOID, AtmosphereState>();

		private static readonly List<ZDOID> EndedAtmospheres = new List<ZDOID>();

		private static readonly List<PortalSnapshot> RenderedScenes = new List<PortalSnapshot>();

		private static readonly List<PortalViewBudget.Entry<Endpoint>> ViewBudget = new List<PortalViewBudget.Entry<Endpoint>>();

		private static readonly RaycastHit[] OcclusionHits = (RaycastHit[])(object)new RaycastHit[8];

		private static int _occlusionMask = -1;

		private static IEnumerator<PortalCapture> _captureSteps;

		private static PortalSnapshot _capturing;

		private const float RelightSpacing = 10f;

		private static bool _lightingFailed;

		private const string BuildModeWait = "waiting for build mode to end (put the hammer, hoe or cultivator away)";

		private static float _stayStartedAt = float.NegativeInfinity;

		internal const float RecentCaptureSeconds = 120f;

		private static bool _missedDepartureReported;

		private static (bool Refresh, CaptureWhen When, bool FirstLink) _departurePending;

		internal const float ArrivalHoldSeconds = 4f;

		private const float ArrivalSettleSeconds = 0.5f;

		private static float _arrivalHoldUntil;

		private static bool _arrivalHoldDone;

		private static int _arrivalInstances = -1;

		private static float _arrivalInstancesSince;

		private static string _viewDiagnostics;

		private static float _diagnosticsUntil;

		private static ZDOID _instantArrival;

		private static ZDOID _arrivalPreparationFailed;

		private static float _instantArrivalUntil;

		private const float InstantArrivalSeconds = 60f;

		private const float InstantArrivalGrace = 3f;

		private static string ArchiveDirectory => ArchiveDirectoryFor(SnapshotRoot, ZNet.instance.GetWorldUID().ToString());

		private static int MaxVisible => Mathf.Clamp(HardwareProfile.MaxVisiblePortals, 1, 16);

		private static int MaxSnapshots => Mathf.Max(MaxVisible + 1, Mathf.Clamp(HardwareProfile.MaxResidentSnapshots, 2, 128));

		internal static string SnapshotRoot => Path.Combine(Paths.BepInExRootPath, "cache", "PortalPreview", "PortalSnapshots");

		internal static string LegacySnapshotRoot => Path.Combine(Paths.BepInExRootPath, "cache", "FastLoading", "PortalSnapshots");

		public static bool Enabled
		{
			get
			{
				if (PortalPreviewPlugin.Cfg != null && PortalPreviewPlugin.Cfg.Enabled.Value)
				{
					return !_failed;
				}
				return false;
			}
		}

		private static int CaptureInterval => CaptureBudgetPolicy.MinFramesBetween(HardwareProfile.MeasuredValid, HardwareProfile.MeasuredCostMs, HardwareProfile.MeasuredBaseMs);

		private static float CaptureIdleSeconds => HardwareProfile.CaptureIdleSeconds;

		private static float FingerprintRadius => Mathf.Clamp(PortalPreviewPlugin.Cfg.FrozenPortalRadius.Value, 32f, 128f);

		private static bool ArrivalOnly
		{
			get
			{
				//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)
				if (_instantArrival != ZDOID.None)
				{
					return Time.realtimeSinceStartup < _resumePreviewAt;
				}
				return false;
			}
		}

		private static bool Ready(Endpoint endpoint)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: 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_00ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)endpoint.Portal == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				endpoint.WaitReason = "portal object is not loaded";
				return false;
			}
			if (!ZNetScene.instance.IsAreaReady(((Component)endpoint.Portal).transform.position))
			{
				endpoint.WaitReason = "waiting for the zone to finish loading";
				return false;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			Vector3 position = ((Component)endpoint.Portal).transform.position;
			float num = Mathf.Clamp(PortalPreviewPlugin.Cfg.FrozenPortalRadius.Value, 32f, 128f);
			IEnumerable<Heightmap> enumerable = LoadedHeightmaps?.GetValue(null) as List<Heightmap>;
			foreach (Heightmap item in enumerable ?? Object.FindObjectsByType<Heightmap>((FindObjectsSortMode)0))
			{
				if (!((Object)(object)item == (Object)null) && !item.IsDistantLod && item.m_doLateUpdate != 0)
				{
					Renderer component = (Renderer)(object)((Component)item).GetComponent<MeshRenderer>();
					Bounds val = (Bounds)(((Object)(object)component != (Object)null) ? component.bounds : new Bounds(((Component)item).transform.position, Vector3.zero));
					if (((Bounds)(ref val)).SqrDistance(position) <= num * num)
					{
						endpoint.StableAt = Mathf.Max(endpoint.StableAt, realtimeSinceStartup + 2f);
						endpoint.WaitReason = "waiting for modified terrain to finish rebuilding";
						return false;
					}
				}
			}
			if (realtimeSinceStartup < endpoint.StableAt)
			{
				endpoint.WaitReason = $"settling loaded scene ({Mathf.CeilToInt(endpoint.StableAt - realtimeSinceStartup)} s)";
				return false;
			}
			endpoint.WaitReason = null;
			return true;
		}

		private static void RecordFingerprint(Endpoint capture, ulong? measured)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			capture.FingerprintKnown = true;
			capture.Fingerprint = measured;
			if (!capture.Fingerprint.HasValue)
			{
				return;
			}
			try
			{
				PortalSceneChanges.Store(PortalSnapshot.ArchivePath(ArchiveDirectory, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString()), capture.Fingerprint.Value);
			}
			catch (Exception ex)
			{
				PortalPreviewPlugin.Log.LogWarning((object)($"[portal preview] What stands around {capture.Id} could not be saved ({ex.Message}); " + "it is captured again on the next trip."));
			}
		}

		private static float CaptureRadiusFor(Endpoint capture)
		{
			float num = Mathf.Clamp(PortalPreviewPlugin.Cfg.FrozenPortalRadius.Value, 32f, 128f);
			if (!(capture.CaptureRadius > 0f))
			{
				return num;
			}
			return Mathf.Min(num, capture.CaptureRadius);
		}

		private static void CaptureEndpoint(Endpoint capture)
		{
			SceneCaptureJob job = NewJob(capture, CaptureBudget.Unlimited);
			while (!Advance(job))
			{
			}
		}

		private static void StartSceneCapture(Endpoint capture)
		{
			AbortSceneCapture(null);
			_sceneCapture = NewJob(capture, new CaptureBudget(4.0));
			StepSceneCapture();
		}

		private static SceneCaptureJob NewJob(Endpoint capture, CaptureBudget budget)
		{
			SceneCaptureJob obj = new SceneCaptureJob
			{
				Capture = capture,
				Budget = budget,
				Started = Stopwatch.GetTimestamp()
			};
			obj.Steps = CaptureSteps(obj);
			return obj;
		}

		private static void StepSceneCapture()
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			SceneCaptureJob sceneCapture = _sceneCapture;
			if (sceneCapture != null)
			{
				if (!Endpoints.TryGetValue(sceneCapture.Capture.Id, out var value) || value != sceneCapture.Capture || (!sceneCapture.SceneTaken && (Object)(object)sceneCapture.Capture.Portal == (Object)null))
				{
					AbortSceneCapture("its portal was unloaded");
				}
				else if (!sceneCapture.SceneTaken && InBuildMode())
				{
					sceneCapture.Capture.WaitReason = "waiting for build mode to end (put the hammer, hoe or cultivator away)";
					AbortSceneCapture("build mode began");
				}
				else if (Advance(sceneCapture))
				{
					_sceneCapture = null;
				}
			}
		}

		private static void AbortSceneCapture(string reason)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			SceneCaptureJob sceneCapture = _sceneCapture;
			if (sceneCapture != null && (reason == null || !sceneCapture.SceneTaken))
			{
				_sceneCapture = null;
				sceneCapture.Steps.Dispose();
				if (reason != null)
				{
					PortalPreviewPlugin.Diag($"[portal preview] Capture of {sceneCapture.Capture.Id} stopped after {sceneCapture.Frames} frames ({reason}); it is captured later.");
				}
			}
		}

		private static bool Advance(SceneCaptureJob job)
		{
			job.Budget.Restart();
			long timestamp = Stopwatch.GetTimestamp();
			bool flag;
			try
			{
				flag = job.Steps.MoveNext();
			}
			catch (Exception exception)
			{
				job.Steps.Dispose();
				CaptureFailed(job, exception);
				return true;
			}
			finally
			{
				double num = (double)(Stopwatch.GetTimestamp() - timestamp) * 1000.0 / (double)Stopwatch.Frequency;
				if (num > job.LongestStepMs)
				{
					job.LongestStepMs = num;
					if (!job.Budget.IsUnlimited)
					{
						job.LongestStepDetail = StallProfile.Describe();
					}
				}
			}
			if (!flag)
			{
				job.Steps.Dispose();
				return true;
			}
			job.Frames++;
			return false;
		}

		private static double SinceMs(long started)
		{
			return (double)(Stopwatch.GetTimestamp() - started) * 1000.0 / (double)Stopwatch.Frequency;
		}

		private static IEnumerator<bool> CaptureSteps(SceneCaptureJob job)
		{
			Endpoint capture = job.Capture;
			CaptureBudget budget = job.Budget;
			EnsureLayer();
			float radius = CaptureRadiusFor(capture);
			ulong? fingerprint = PortalSceneChanges.Measure(((Component)capture.Portal).transform.position, FingerprintRadius);
			PortalSnapshot fresh = null;
			try
			{
				using (IEnumerator<PortalSnapshot> steps = PortalSnapshot.CaptureSteps(((Component)capture.Portal).transform.position, ((Component)capture.Portal).transform.rotation, radius, _layer, budget, null, null, PortalShape.For(((Component)capture.Portal).transform)))
				{
					while (steps.MoveNext())
					{
						if (steps.Current != null)
						{
							fresh = steps.Current;
							break;
						}
						yield return true;
					}
				}
				if (fresh == null)
				{
					throw new InvalidOperationException("Portal capture ended without a scene.");
				}
				job.SceneTaken = true;
				double captureMs = SinceMs(job.Started);
				string text = PortalSnapshot.ArchivePath(ArchiveDirectory, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString());
				if (File.Exists(text))
				{
					Vector3 position;
					Quaternion rotation;
					int version;
					bool flag = PortalSnapshot.TryReadArchiveHeader(text, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString(), out position, out rotation, out version);
					int num;
					if (flag)
					{
						Vector3 val = position - fresh.Position;
						num = ((((Vector3)(ref val)).sqrMagnitude > 0.25f || Quaternion.Angle(rotation, fresh.Rotation) > 1f) ? 1 : 0);
					}
					else
					{
						num = 0;
					}
					if (num != 0)
					{
						string text2 = text + ".reassigned-" + Guid.NewGuid().ToString("N");
						File.Copy(text, text2);
						if (_indexedArchiveDirectory == ArchiveDirectory)
						{
							IndexArchivedScene(text2);
						}
					}
					else if (!flag || version < 24)
					{
						string text3 = text + ".legacy";
						if (!File.Exists(text3))
						{
							File.Copy(text, text3);
						}
						if (_indexedArchiveDirectory == ArchiveDirectory)
						{
							IndexArchivedScene(text3);
						}
					}
				}
				StallProfile.Mark("capture: archive bookkeeping");
				if (budget.Spent)
				{
					yield return true;
				}
				string saveError = null;
				using (IEnumerator<bool> save = fresh.SaveSteps(ArchiveDirectory, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString(), ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Target)/*cast due to .constrained prefix*/).ToString(), budget))
				{
					while (true)
					{
						bool flag2;
						try
						{
							flag2 = save.MoveNext();
							StallProfile.Mark("save");
						}
						catch (ArchiveWriteException ex)
						{
							saveError = ex.Message;
							break;
						}
						if (!flag2)
						{
							break;
						}
						yield return true;
					}
				}
				if (saveError == null && _indexedArchiveDirectory == ArchiveDirectory)
				{
					IndexArchivedScene(PortalSnapshot.ArchivePath(ArchiveDirectory, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString()));
				}
				capture.CopiesPending = saveError == null && fresh.DeferredTextureCopies > 0;
				if (saveError != null)
				{
					capture.FingerprintKnown = true;
					capture.Fingerprint = null;
					PortalSceneChanges.Clear(PortalSnapshot.ArchivePath(ArchiveDirectory, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString()));
					if (capture.LastError != saveError)
					{
						PortalPreviewPlugin.Log.LogWarning((object)($"[portal preview] Portal {capture.Id} is shown but its scene was not saved " + "(" + saveError + "); it is captured again on the next trip."));
					}
				}
				else if (capture.CopiesPending)
				{
					capture.FingerprintKnown = true;
					capture.Fingerprint = null;
					PortalSceneChanges.Clear(PortalSnapshot.ArchivePath(ArchiveDirectory, ((object)Unsafe.As<ZDOID, ZDOID>(ref capture.Id)/*cast due to .constrained prefix*/).ToString()));
					PortalPreviewPlugin.Log.LogInfo((object)($"[portal preview] {capture.Id}: {fresh.DeferredTextureCopies} texture copies are " + "saved on its next captures (large texture pack); it is captured again on the next trip."));
				}
				else
				{
					RecordFingerprint(capture, fingerprint);
				}
				double savedMs = SinceMs(job.Started);
				StallProfile.Mark("save: fingerprint, index");
				if (budget.Spent)
				{
					yield return true;
				}
				using (IEnumerator<bool> save = fresh.PrepareSteps(budget))
				{
					while (save.MoveNext())
					{
						yield return true;
					}
				}
				StallProfile.Mark("prepare for render");
				double num2 = SinceMs(job.Started);
				if (_viewDiagnostics != null)
				{
					string viewDiagnostics = _viewDiagnostics;
					ForgetDiagnostics();
					try
					{
						if ((Object)(object)capture.Portal != (Object)null)
						{
							PortalDiagnostics.CompareView(viewDiagnostics, fresh, ((Component)capture.Portal).transform, _layer);
						}
					}
					catch (Exception ex2)
					{
						PortalPreviewPlugin.Log.LogWarning((object)("[portal preview] View comparison failed: " + ex2.Message));
					}
				}
				if (_loading == capture)
				{
					StopLoad();
				}
				MakeRoom(capture);
				capture.Snapshot?.Dispose();
				capture.Snapshot = fresh;
				capture.SnapshotVerified = true;
				fresh = null;
				FollowTimeOfDay(capture, fresh: true);
				capture.LastUsed = Time.realtimeSinceStartup;
				capture.Refresh = false;
				capture.Reported = false;
				capture.CapturedAt = Time.realtimeSinceStartup;
				capture.When = CaptureWhen.Now;
				capture.SkipArchiveLoad = false;
				if (saveError == null)
				{
					capture.LoadRetryAt = 0f;
				}
				capture.FirstLinkCapture = false;
				capture.LastError = saveError;
				capture.WaitReason = null;
				capture.Failures = 0;
				string text4 = ((job.Frames > 0) ? $" over {job.Frames + 1} frames (longest step {job.LongestStepMs:F0} ms: {job.LongestStepDetail})" : "");
				PortalPreviewPlugin.Diag($"[portal preview] Captured loaded exit {capture.Id} -> {capture.Target}: " + $"{capture.Snapshot.MeshCount} meshes, {capture.Snapshot.UniqueVertices} unique / {capture.Snapshot.Vertices} placed vertices, " + $"{capture.Snapshot.GrassCount} grass, {capture.Snapshot.TerrainCount} terrain tiles, " + $"{capture.Snapshot.ParticleCount} frozen particle systems, " + $"{capture.Snapshot.WeatherParticleCount} camera-centred weather systems, " + $"{capture.Snapshot.SkyBackdropCount} cloud/world-tree renderers in the backdrop, " + $"{capture.Snapshot.WaterBackdropCount} water renderers in the backdrop, " + $"{capture.Snapshot.SkippedVisuals} invalid visuals skipped. Saved with frozen lighting. " + $"Capture/save/prepare {captureMs:F0}/{savedMs - captureMs:F0}/{num2 - savedMs:F0} ms{text4}; save {capture.Snapshot.LastSaveTimings}.");
				StallProfile.Note($"captured {capture.Id} ({capture.Snapshot.MeshCount} meshes, {capture.Snapshot.UniqueVertices} unique vertices): " + $"capture/save/prepare {captureMs:F0}/{savedMs - captureMs:F0}/{num2 - savedMs:F0} ms{text4}, save {capture.Snapshot.LastSaveTimings}");
			}
			finally
			{
				fresh?.Dispose();
			}
		}

		private static void CaptureFailed(SceneCaptureJob job, Exception exception)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			Endpoint capture = job.Capture;
			float num = CaptureRadiusFor(capture);
			StallProfile.Note($"capture of {capture.Id} failed after {SinceMs(job.Started):F0} ms" + ((job.Frames > 0) ? $" over {job.Frames + 1} frames" : ""));
			capture.Failures++;
			float num2 = CaptureRetry.SmallerRadius(num);
			bool flag = exception is SnapshotLimitException && num2 < num;
			if (flag)
			{
				capture.CaptureRadius = num2;
			}
			float num3 = (flag ? 2f : CaptureRetry.Delay(capture.Failures));
			capture.RetryAt = Time.realtimeSinceStartup + num3;
			if (capture.LastError != exception.Message)
			{
				PortalPreviewPlugin.Log.LogError((object)($"[portal preview] Portal {capture.Id} could not be captured: {exception.Message} " + (flag ? $"Trying again with a {num2:F0} m radius." : $"Trying again in {num3:F0} s.") + ((exception is SnapshotLimitException || exception.Message.StartsWith("There is no", StringComparison.Ordinal)) ? "" : (Environment.NewLine + exception.StackTrace))));
			}
			capture.LastError = exception.Message;
			capture.Reported = true;
		}

		public static void Initialize()
		{
			//IL_00c1: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Expected O, but got Unknown
			PortalPreviewPlugin.Diag(HardwareProfile.Describe());
			PortalView.BindWaterReflection(null);
			PortalPreviewPlugin.Cfg.FrozenPortalRadius.SettingChanged += ResetCaptureRadii;
			PortalPreviewPlugin.Cfg.FrozenPortalMaxRenderers.SettingChanged += ResetCaptureRadii;
			PortalPreviewPlugin.Cfg.FrozenPortalMaxUniqueVertices.SettingChanged += ResetCaptureRadii;
			object obj = <>c.<>9__15_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					//IL_04c3: Unknown result type (might be due to invalid IL or missing references)
					string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status");
					switch (text)
					{
					case "on":
						Clear();
						PortalPreviewPlugin.Cfg.Enabled.Value = true;
						_status = "enabled; waiting for a loaded wooden or stone portal";
						break;
					case "off":
						PortalPreviewPlugin.Cfg.Enabled.Value = false;
						Clear();
						break;
					case "clear":
						Clear();
						_status = "RAM cache cleared; saved snapshots retained on disk";
						break;
					case "backdropcheck":
					{
						if (RefreshNearby() == 0)
						{
							args.Context.AddString("No connected portal within 24 m to capture.");
							return;
						}
						string text3 = Path.Combine(Paths.BepInExRootPath, "cache", "PortalPreview", "diagnostics", "backdrop-" + DateTime.Now.ToString("yyyyMMdd-HHmmss"));
						Directory.CreateDirectory(text3);
						PortalSnapshot.BackdropDiagnostics = text3;
						_viewDiagnostics = text3;
						_diagnosticsUntil = Time.realtimeSinceStartup + 60f;
						args.Context.AddString("The next capture of a nearby portal writes a panorama and view comparison to " + text3);
						break;
					}
					case "refresh":
						RefreshNearby();
						break;
					case "log":
						if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
						{
							args.Context.AddString("Usage: frozenportal log on|off");
						}
						else
						{
							PortalPreviewPlugin.Verbose = args[2] == "on";
							args.Context.AddString("Portal diagnostics " + (PortalPreviewPlugin.Verbose ? "enabled" : "disabled") + " for this session.");
						}
						return;
					case "lighting":
						if ((Object)(object)Player.m_localPlayer == (Object)null)
						{
							args.Context.AddString("Enter a world first.");
						}
						else
						{
							string text2 = "Lighting computed/live here: " + DestinationLighting.CompareWithLive(((Component)Player.m_localPlayer).transform.position);
							args.Context.AddString(text2);
							PortalPreviewPlugin.Log.LogInfo((object)("[portal preview] " + text2));
						}
						return;
					case "cleanup":
						if ((Object)(object)ZNet.instance == (Object)null)
						{
							args.Context.AddString("Enter a world first.");
							return;
						}
						PortalCleanup.Start(ArchiveDirectory);
						_status = "cleanup of stale saved scenes started; 'frozenportal status' shows the result";
						break;
					case "mode":
						if (args.Length != 3 || (args[2] != "always" && args[2] != "approach"))
						{
							args.Context.AddString("Usage: frozenportal mode always|approach");
							return;
						}
						PortalPreviewPlugin.Cfg.FrozenPortalAlwaysVisible.Value = args[2] == "always";
						break;
					case "profile":
					{
						if (args.Length != 3 || !Enum.TryParse<HardwareProfileSetting>(args[2], ignoreCase: true, out var result))
						{
							args.Context.AddString("Usage: frozenportal profile auto|low|medium|high|ultra|custom");
							return;
						}
						PortalPreviewPlugin.Cfg.HardwareProfile.Value = result;
						PreviewConfig cfg = PortalPreviewPlugin.Cfg;
						if (cfg.FrozenPortalResolution.Value > 0 || cfg.FrozenPortalMaxVisible.Value > 0 || cfg.FrozenPortalMaxResident.Value > 0)
						{
							cfg.FrozenPortalResolution.Value = 0;
							cfg.FrozenPortalMaxVisible.Value = 0;
							cfg.FrozenPortalMaxResident.Value = 0;
							args.Context.AddString("Resolution, Max visible portals and Max resident snapshots follow the profile again (set to 0).");
						}
						break;
					}
					case "count":
					case "spiral":
					case "radius":
					case "memory":
					{
						if (args.Length != 3 || !float.TryParse(args[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || float.IsNaN(result2) || float.IsInfinity(result2))
						{
							args.Context.AddString("Usage: frozenportal " + text + " <number>");
							return;
						}
						if (text == "count")
						{
							PortalPreviewPlugin.Cfg.FrozenPortalMaxVisible.Value = ((!(result2 <= 0f)) ? Mathf.Clamp(Mathf.RoundToInt(result2), 1, 16) : 0);
						}
						if (text == "memory")
						{
							PortalPreviewPlugin.Cfg.FrozenPortalMaxResident.Value = ((!(result2 <= 0f)) ? Mathf.Clamp(Mathf.RoundToInt(result2), 2, 128) : 0);
						}
						if (text == "radius")
						{
							PortalPreviewPlugin.Cfg.FrozenPortalApproachRadius.Value = Mathf.Clamp(result2, 1f, 100f);
						}
						if (text == "spiral")
						{
							PortalPreviewPlugin.Cfg.FrozenPortalRevealSeconds.Value = Mathf.Clamp(result2, 0f, 5f);
						}
						break;
					}
					case "recapture":
					{
						if (args.Length != 3 || !float.TryParse(args[2].Replace(',', '.'), NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) || result4 < 0.02f || result4 > 1f)
						{
							args.Context.AddString("Usage: frozenportal recapture <metres, 0.02-1; 0.3 by default>");
							return;
						}
						PortalView.EyeRecaptureDistance = result4;
						_status = $"eye recapture every {result4:F2} m (diagnostic, not saved)";
						break;
					}
					case "pair":
						if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
						{
							args.Context.AddString("Usage: frozenportal pair on|off");
							return;
						}
						PortalView.EyePrediction = args[2] == "on";
						_status = "eye capture pair " + args[2] + " (diagnostic, not saved)";
						break;
					case "mainview":
						if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
						{
							args.Context.AddString("Usage: frozenportal mainview on|off");
							return;
						}
						PortalView.MainViewOnly = args[2] == "on";
						_status = "main view only " + args[2] + " (diagnostic, not saved)";
						break;
					case "load":
					{
						if (args.Length != 3 || (!Enum.TryParse<MeasuredLoad>(args[2], ignoreCase: true, out var result3) && args[2] != "auto"))
						{
							args.Context.AddString("Usage: frozenportal load normal|reduced|minimal|auto");
							return;
						}
						HardwareProfile.ForcedLoad = ((args[2] == "auto") ? ((MeasuredLoad?)null) : new MeasuredLoad?(result3));
						_status = "load " + args[2] + " (diagnostic, not saved)";
						break;
					}
					case "buffers":
						if (args.Length != 3 || (args[2] != "on" && args[2] != "off"))
						{
							args.Context.AddString("Usage: frozenportal buffers on|off");
							return;
						}
						PortalView.WriteVirtualBuffers = args[2] == "on";
						_status = "portal depth and motion vectors " + (PortalView.WriteVirtualBuffers ? "written" : "not written (diagnostic)");
						break;
					default:
						args.Context.AddString("Usage: frozenportal on|off|status|clear|refresh|backdropcheck|cleanup|lighting|buffers on|off|pair on|off|mainview on|off|load normal|reduced|minimal|auto|recapture <m>; profile auto|low|medium|high|ultra|custom; mode always|approach; count|radius|spiral|memory <number> (count and memory 0: the profile's)");
						return;
					case "status":
						break;
					}
					args.Context.AddString($"Frozen portals (scene-v20): {_status}; {CountSnapshots()}/{MaxSnapshots} resident snapshots; " + string.Format("up to {0} views; {1}. ", MaxVisible, PortalPreviewPlugin.Cfg.FrozenPortalAlwaysVisible.Value ? "always" : ("approach " + PortalPreviewPlugin.Cfg.FrozenPortalApproachRadius.Value + " m")) + $"Snapshot updates: {PortalPreviewPlugin.Cfg.SnapshotUpdates.Value}; " + "Scene, grass, particles, clouds and world tree frozen; sun and fog " + (PortalPreviewPlugin.Cfg.FollowTimeOfDay.Value ? "follow the time of day" : "frozen") + "; disk snapshots retained.");
					args.Context.AddString(HardwareProfile.Describe());
					if (PortalCleanup.LastReport != null)
					{
						args.Context.AddString("Last cleanup: " + PortalCleanup.LastReport);
					}
					string text4 = DescribeNearest(Utils.GetMainCamera());
					args.Context.AddString(text4);
					PortalPreviewPlugin.Diag("[portal preview] status: " + text4);
				};
				<>c.<>9__15_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("frozenportal", "Frozen 3D portals: on, off, status, clear (RAM), refresh (nearby)", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
		}

		internal static string ArchiveDirectoryFor(string root, string world)
		{
			string key = root + "|" + world;
			if (ArchiveDirectories.TryGetValue(key, out var value))
			{
				return value;
			}
			string text = Path.Combine(root, world, PortalSnapshot.RevisionFolder);
			string text2 = Path.Combine(root, world, PortalSnapshot.GameRevision);
			if (!Directory.Exists(text) && Directory.Exists(text2))
			{
				try
				{
					Directory.Move(text2, text);
				}
				catch (Exception ex)
				{
					PortalPreviewPlugin.Log.LogError((object)("[portal preview] Could not shorten the snapshot folder " + text2 + " (" + ex.Message + "); it stays in use."));
					text = text2;
				}
			}
			ArchiveDirectories[key] = text;
			return text;
		}

		internal static void AdoptLegacySnapshots()
		{
			try
			{
				if (Directory.Exists(LegacySnapshotRoot) && !Directory.Exists(SnapshotRoot))
				{
					Directory.CreateDirectory(Path.GetDirectoryName(SnapshotRoot));
					Directory.Move(LegacySnapshotRoot, SnapshotRoot);
					PortalPreviewPlugin.Diag("[portal preview] Snapshots saved by FastLoading moved to " + SnapshotRoot);
				}
			}
			catch (Exception ex)
			{
				PortalPreviewPlugin.Log.LogError((object)("[portal preview] Could not move FastLoading's snapshots (" + ex.Message + "); portals are captured again."));
			}
		}

		private static string DescribeNearest(Camera viewer)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//I