Decompiled source of SagaCapture v1.0.1

Landoria.SagaCapture.dll

Decompiled 2 weeks ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.IO.Pipes;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AmplifyOcclusion;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CameraOperator;
using FFmpegMediaWriter;
using HarmonyLib;
using Landoria.Shared;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.PostProcessing;
using UnityEngine.Rendering;
using UnityRuntimeCameraRecorder;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Core.ObjectPool;
using YamlDotNet.Core.Tokens;
using YamlDotNet.Helpers;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.BufferedDeserialization;
using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators;
using YamlDotNet.Serialization.Callbacks;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Landoria.SagaCapture")]
[assembly: AssemblyDescription("A configurable video recorder for Valheim.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Landoria")]
[assembly: AssemblyProduct("Landoria.SagaCapture")]
[assembly: AssemblyCopyright("Copyright © 2026 End3rbyte")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("3A8D7D61-C013-4D86-9962-95E57F7F6685")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: AssemblyInformationalVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("1.0.1.21577")]
namespace Landoria.SagaCapture
{
	internal static class Preference
	{
		private static ConfigEntry<KeyboardShortcut> captureModeShortcut;

		private static ConfigEntry<string> cameraFrameRate;

		private static ConfigEntry<bool> showFrameRate;

		private static ConfigEntry<RecordingQualityPreset> recordingQuality;

		private static ConfigEntry<bool> gameplayIncludeUi;

		private static ConfigEntry<float> minimumShotDuration;

		private static ConfigEntry<float> maximumShotDuration;

		internal const float TreeScanRadius = 10f;

		internal const float TreeScanIntervalSeconds = 0.5f;

		internal const int MinimumTreeCount = 2;

		internal static KeyboardShortcut CaptureModeShortcut => captureModeShortcut.Value;

		internal static RecordingQualityPreset RecordingQuality => recordingQuality.Value;

		internal static int CameraMaximumFrameRate
		{
			get
			{
				if (!(cameraFrameRate.Value == "30"))
				{
					return 60;
				}
				return 30;
			}
		}

		internal static bool LimitGameFrameRate
		{
			get
			{
				if (!(cameraFrameRate.Value != "SameAsGame"))
				{
					return QualitySettings.vSyncCount == 0;
				}
				return true;
			}
		}

		internal static bool ShowFrameRate => showFrameRate.Value;

		internal static bool GameplayIncludeUi => gameplayIncludeUi.Value;

		internal static float MinimumShotDuration => minimumShotDuration.Value;

		internal static float MaximumShotDuration => Mathf.Max(MinimumShotDuration, maximumShotDuration.Value);

		internal static string CameraOperatorConfigPath => Path.Combine(Paths.ConfigPath, "SagaCapture", "config.yaml");

		internal static void Initialize(ConfigFile config)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Expected O, but got Unknown
			captureModeShortcut = config.Bind<KeyboardShortcut>("Controls", "CaptureModeShortcut", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Shortcut used to enter or leave CaptureMode.\n\nhttps://docs.unity3d.com/ScriptReference/KeyCode.html");
			cameraFrameRate = config.Bind<string>("CinematicCameraRendering", "MaximumFrameRate", "SameAsGame", new ConfigDescription("Capture frame rate: 30, 60, or SameAsGame. SameAsGame preserves active VSync; when VSync is off, the game and video are limited to 60 FPS.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "30", "60", "SameAsGame" }), Array.Empty<object>()));
			showFrameRate = config.Bind<bool>("CinematicCameraRendering", "ShowFrameRate", false, "Show the cinematic-camera frame rate in the top-left corner.");
			recordingQuality = config.Bind<RecordingQualityPreset>("Recording", "Quality", RecordingQualityPreset.Medium, "Recording quality: Low, Medium, High, or Highest. Lower quality reduces file size and encoding load.");
			gameplayIncludeUi = config.Bind<bool>("GameplayCamera", "IncludeUI", true, "Include Valheim's interface in gameplay-camera shots. When false, capture occurs before overlay UI is rendered.");
			InitializeDirector(config);
		}

		private static void InitializeDirector(ConfigFile config)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			minimumShotDuration = config.Bind<float>("Director", "MinimumShotDuration", 8f, new ConfigDescription("Minimum shot duration in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>()));
			maximumShotDuration = config.Bind<float>("Director", "MaximumShotDuration", 15f, new ConfigDescription("Maximum shot duration in seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 120f), Array.Empty<object>()));
		}

		internal static void RestoreDefaults(ConfigFile config)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			captureModeShortcut.Value = new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>());
			cameraFrameRate.Value = "SameAsGame";
			showFrameRate.Value = false;
			recordingQuality.Value = RecordingQualityPreset.Medium;
			gameplayIncludeUi.Value = true;
			minimumShotDuration.Value = 8f;
			maximumShotDuration.Value = 15f;
			config.Save();
			Landoria.Shared.ConfigWatcher.IgnoreCurrentFileVersion();
		}
	}
	[BepInPlugin("Landoria.SagaCapture", "Landoria.SagaCapture", "1.0.1")]
	public sealed class SagaCapturePlugin : BaseUnityPlugin
	{
		private const string PluginGuid = "Landoria.SagaCapture";

		private const string PluginName = "Landoria.SagaCapture";

		private const string PluginVersion = "1.0.1";

		private ShotDirector _shotDirector;

		private Harmony _harmony;

		private static SagaCapturePlugin _instance;

		internal static ManualLogSource Log { get; private set; }

		internal static bool IsCinematicCameraActive
		{
			get
			{
				SagaCapturePlugin instance = _instance;
				if (instance == null)
				{
					return false;
				}
				return instance._shotDirector?.IsCameraActive == true;
			}
		}

		internal static bool IsCinematicImageActive
		{
			get
			{
				SagaCapturePlugin instance = _instance;
				if (instance == null)
				{
					return false;
				}
				return instance._shotDirector?.IsCinematicImageActive == true;
			}
		}

		internal static void StopRecordingForMenuExit()
		{
			SagaCapturePlugin instance = _instance;
			if (instance != null && instance._shotDirector?.IsActive == true)
			{
				Log.LogInfo((object)"Stopping recording before leaving the game.");
				_instance._shotDirector.StopRecording();
			}
		}

		private void Awake()
		{
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			_instance = this;
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"AssemblyVersion: {((object)this).GetType().Assembly.GetName().Version}.");
			Preference.Initialize(((BaseUnityPlugin)this).Config);
			Landoria.Shared.ConfigWatcher.Initialize(((BaseUnityPlugin)this).Config, ((BaseUnityPlugin)this).Logger, "Saga Capture", delegate
			{
				Preference.RestoreDefaults(((BaseUnityPlugin)this).Config);
			});
			_shotDirector = ((Component)this).gameObject.AddComponent<ShotDirector>();
			_harmony = new Harmony("Landoria.SagaCapture");
			_harmony.PatchAll();
			Log.LogInfo((object)"Landoria.SagaCapture 1.0.1 is loaded.");
		}

		private void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			Landoria.Shared.ConfigWatcher.Update();
			bool flag = IsMainKeyDown(Preference.CaptureModeShortcut);
			if (_shotDirector.IsActive)
			{
				if (flag)
				{
					_shotDirector.StopRecording();
				}
			}
			else if (IsShortcutDown(Preference.CaptureModeShortcut))
			{
				_shotDirector.StartRecording();
			}
		}

		private static bool IsShortcutDown(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey == 0 || !ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, true))
			{
				return false;
			}
			foreach (KeyCode modifier in ((KeyboardShortcut)(ref shortcut)).Modifiers)
			{
				if (!IsModifierDown(modifier))
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsModifierDown(KeyCode modifier)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			if ((int)modifier == 304 || (int)modifier == 303)
			{
				if (!ZInput.GetKey((KeyCode)304, true))
				{
					return ZInput.GetKey((KeyCode)303, true);
				}
				return true;
			}
			return ZInput.GetKey(modifier, true);
		}

		private static bool IsMainKeyDown(KeyboardShortcut shortcut)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			if ((int)((KeyboardShortcut)(ref shortcut)).MainKey != 0)
			{
				return ZInput.GetKeyDown(((KeyboardShortcut)(ref shortcut)).MainKey, true);
			}
			return false;
		}

		private void OnDestroy()
		{
			Landoria.Shared.ConfigWatcher.Dispose();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			_harmony = null;
			_shotDirector?.Shutdown();
			_shotDirector = null;
			ManualLogSource log = Log;
			if (log != null)
			{
				log.LogInfo((object)"Landoria.SagaCapture 1.0.1 is unloaded.");
			}
			Log = null;
			_instance = null;
		}
	}
	internal sealed class ShotDirector : MonoBehaviour
	{
		private const int AntiAliasingSamples = 1;

		private const int MinimumWarmupFrames = 8;

		private const float MinimumWarmupSeconds = 0.5f;

		private const float TargetLossGraceDuration = 1f;

		private SagaCaptureRig _cameraRig;

		private readonly CameraPlacementDirector _placementDirector = new CameraPlacementDirector();

		private global::UnityRuntimeCameraRecorder.UnityRuntimeCameraRecorder _recorder;

		private Coroutine _warmupRoutine;

		private AudioListener _gameplayListener;

		private SagaCaptureGameplayCapture _gameplayCapture;

		private SagaCaptureVideoSource _videoSource;

		private string _outputPath;

		private float _targetLostSince = -1f;

		private float _nextShotAt;

		private readonly SagaCaptureFrameRateLimit _frameRateLimit = new SagaCaptureFrameRateLimit();

		internal bool IsActive
		{
			get
			{
				if (!((Object)(object)_recorder != (Object)null))
				{
					return _warmupRoutine != null;
				}
				return true;
			}
		}

		internal bool IsCameraActive => _cameraRig?.IsMoving ?? false;

		internal bool IsCinematicImageActive
		{
			get
			{
				global::UnityRuntimeCameraRecorder.UnityRuntimeCameraRecorder recorder = _recorder;
				if (recorder != null && recorder.IsCapturing)
				{
					return _videoSource?.IsCinematic ?? false;
				}
				return false;
			}
		}

		private void Update()
		{
			global::UnityRuntimeCameraRecorder.UnityRuntimeCameraRecorder recorder = _recorder;
			if (recorder != null && recorder.IsCapturing)
			{
				if (Time.time >= _nextShotAt)
				{
					CutToNextSource();
				}
				CheckCinematicVisibility();
			}
		}

		private void CheckCinematicVisibility()
		{
			SagaCaptureVideoSource videoSource = _videoSource;
			if (videoSource == null || !videoSource.IsCinematic)
			{
				_targetLostSince = -1f;
				return;
			}
			SagaCaptureRig cameraRig = _cameraRig;
			if (cameraRig != null && cameraRig.HasTargetVisibility())
			{
				_targetLostSince = -1f;
			}
			else if (_targetLostSince < 0f)
			{
				_targetLostSince = Time.time;
			}
			else if (Time.time - _targetLostSince >= 1f)
			{
				ActivateGameplay("Cinematic target occluded; cut to gameplay.");
			}
		}

		internal void StartRecording()
		{
			if (IsActive)
			{
				return;
			}
			try
			{
				Camera main = Camera.main;
				ValidateGameplayCamera(main);
				_frameRateLimit.Apply("CaptureMode", Preference.CameraMaximumFrameRate, Preference.LimitGameFrameRate);
				PrepareCamera(main);
				_outputPath = CreateOutputPath();
				_warmupRoutine = ((MonoBehaviour)this).StartCoroutine(WarmupThenStart());
				Notify("Saga Capture is preparing CaptureMode.");
			}
			catch (Exception exception)
			{
				HandleRecordingFailed(exception);
			}
		}

		internal void StopRecording()
		{
			if (_warmupRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_warmupRoutine);
				_warmupRoutine = null;
				ReleaseCamera();
				Notify("Saga Capture CaptureMode cancelled during warmup.");
				return;
			}
			global::UnityRuntimeCameraRecorder.UnityRuntimeCameraRecorder recorder = _recorder;
			if (recorder == null || !recorder.IsCapturing)
			{
				Notify("Saga Capture is finalizing the video.");
				return;
			}
			_recorder.StopRecording();
			ReleaseCamera();
		}

		private IEnumerator WarmupThenStart()
		{
			float startedAt = Time.realtimeSinceStartup;
			int renderedFrames = 0;
			while (renderedFrames < 8 || Time.realtimeSinceStartup - startedAt < 0.5f)
			{
				renderedFrames++;
				yield return (object)new WaitForEndOfFrame();
			}
			_warmupRoutine = null;
			StartPreparedRecording();
		}

		private void StartPreparedRecording()
		{
			try
			{
				_recorder = ((Component)this).gameObject.AddComponent<global::UnityRuntimeCameraRecorder.UnityRuntimeCameraRecorder>();
				_targetLostSince = -1f;
				SubscribeRecorder();
				_cameraRig.BeginMovement();
				if (!_placementDirector.TryPlace(_cameraRig))
				{
					SagaCapturePlugin.Log.LogWarning((object)"Initial cinematic cut has no visible viewpoint.");
				}
				_videoSource.SetCinematic(enabled: true);
				ScheduleNextCut();
				_recorder.StartRecording(CreateSequence(), _gameplayListener, CreateSettings(_outputPath));
			}
			catch (Exception exception)
			{
				HandleRecordingFailed(exception);
			}
		}

		private void ValidateGameplayCamera(Camera gameplayCamera)
		{
			if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)gameplayCamera == (Object)null)
			{
				throw new InvalidOperationException("The local player camera is unavailable.");
			}
			_gameplayListener = SagaCaptureAudioListener.FindActive(gameplayCamera);
			if ((Object)(object)_gameplayListener == (Object)null)
			{
				throw new InvalidOperationException("The gameplay audio listener is unavailable.");
			}
		}

		private unsafe void PrepareCamera(Camera gameplayCamera)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			GraphicsSettingsState graphicsSettings = SagaCaptureRendering.GetGraphicsSettings();
			SagaCaptureRendering.GetResolution(graphicsSettings, out var width, out var height, out var filterMode);
			_cameraRig = ((Component)this).gameObject.AddComponent<SagaCaptureRig>();
			_cameraRig.Initialize(gameplayCamera, graphicsSettings);
			_cameraRig.BeginWarmup(width, height, 1, filterMode);
			_videoSource = ((Component)this).gameObject.AddComponent<SagaCaptureVideoSource>();
			_videoSource.Initialize(_cameraRig.Camera, _cameraRig.OutputTexture, Preference.GameplayIncludeUi);
			_gameplayCapture = ((Component)gameplayCamera).gameObject.AddComponent<SagaCaptureGameplayCapture>();
			_gameplayCapture.Initialize(_videoSource);
			SagaCaptureCameraLogger.LogEffectiveConfiguration("CaptureMode", gameplayCamera, _cameraRig.Camera, Unsafe.Read<GraphicsSettingsState>((void*)GraphicsSettingsManager.Instance.ActiveSettings), graphicsSettings);
		}

		private VideoSequenceSettings CreateSequence()
		{
			VideoSequenceSettings videoSequenceSettings = new VideoSequenceSettings();
			videoSequenceSettings.Sources = new VideoSequenceSource[1] { VideoSequenceSource.FromTexture(_videoSource.Texture) };
			return videoSequenceSettings;
		}

		private void CutToNextSource()
		{
			if (_videoSource.IsCinematic)
			{
				ActivateGameplay("Gameplay shot activated.");
				return;
			}
			if (!((Object)(object)_cameraRig != (Object)null) || !_placementDirector.TryPlace(_cameraRig))
			{
				SagaCapturePlugin.Log.LogDebug((object)"Cinematic cut postponed: no visible target viewpoint.");
			}
			else
			{
				_videoSource.SetCinematic(enabled: true);
				_targetLostSince = -1f;
				SagaCapturePlugin.Log.LogDebug((object)"Cinematic shot activated with a new viewpoint.");
			}
			ScheduleNextCut();
		}

		private void ActivateGameplay(string logMessage)
		{
			_videoSource.SetCinematic(enabled: false);
			_targetLostSince = -1f;
			ScheduleNextCut();
			SagaCapturePlugin.Log.LogDebug((object)logMessage);
		}

		private void ScheduleNextCut()
		{
			_nextShotAt = Time.time + Random.Range(Preference.MinimumShotDuration, Preference.MaximumShotDuration);
		}

		private RecordingSettings CreateSettings(string outputPath)
		{
			RenderTexture targetTexture = _cameraRig.Camera.targetTexture;
			return new RecordingSettings
			{
				FfmpegPath = ResolveFfmpegDirectory(),
				TemporaryContainerPath = outputPath + ".mkv.tmp",
				OutputPath = outputPath,
				Width = ((Texture)targetTexture).width,
				Height = ((Texture)targetTexture).height,
				MaximumFrameRate = Preference.CameraMaximumFrameRate,
				SourceAntiAliasingSamples = 1,
				QualityPreset = Preference.RecordingQuality
			};
		}

		private static string ResolveFfmpegDirectory()
		{
			string environmentVariable = Environment.GetEnvironmentVariable("FFMPEG_PATH");
			if (string.IsNullOrWhiteSpace(environmentVariable) || !File.Exists(Path.Combine(environmentVariable, "ffmpeg.exe")))
			{
				throw new FileNotFoundException("FFMPEG_PATH must point to a directory containing ffmpeg.exe.");
			}
			return environmentVariable;
		}

		private static string CreateOutputPath()
		{
			return Path.Combine(ResolveOutputDirectory(), $"SagaCapture_{DateTime.Now:yyyy-MM-dd_HH-mm-ss-fff}.mp4");
		}

		private static string ResolveOutputDirectory()
		{
			string text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyVideos), "SagaCapture");
			Directory.CreateDirectory(text);
			return text;
		}

		private void SubscribeRecorder()
		{
			_recorder.CaptureStarted += HandleCaptureStarted;
			_recorder.FinalizationStarted += HandleFinalizationStarted;
			_recorder.RecordingCompleted += HandleRecordingCompleted;
			_recorder.RecordingFailed += HandleRecordingFailed;
		}

		private void UnsubscribeRecorder()
		{
			_recorder.CaptureStarted -= HandleCaptureStarted;
			_recorder.FinalizationStarted -= HandleFinalizationStarted;
			_recorder.RecordingCompleted -= HandleRecordingCompleted;
			_recorder.RecordingFailed -= HandleRecordingFailed;
		}

		private void HandleCaptureStarted()
		{
			Notify("Saga Capture CaptureMode started.");
			SagaCapturePlugin.Log.LogInfo((object)("Recording started: " + _outputPath));
		}

		private void HandleFinalizationStarted()
		{
			Notify("Saga Capture is finalizing the video.");
		}

		private void HandleRecordingCompleted()
		{
			Notify("Saga Capture saved the video to Videos/SagaCapture.");
			SagaCapturePlugin.Log.LogInfo((object)("Recording saved: " + _outputPath));
			ReleaseSession();
		}

		private void HandleRecordingFailed(Exception exception)
		{
			SagaCapturePlugin.Log.LogError((object)exception);
			Notify("Saga Capture recording failed: " + exception.Message);
			ReleaseSession();
		}

		private void ReleaseSession()
		{
			if (_warmupRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_warmupRoutine);
				_warmupRoutine = null;
			}
			ReleaseCamera();
			if ((Object)(object)_recorder != (Object)null)
			{
				UnsubscribeRecorder();
				Object.Destroy((Object)(object)_recorder);
				_recorder = null;
			}
			_gameplayListener = null;
			_targetLostSince = -1f;
		}

		private void ReleaseCamera()
		{
			if ((Object)(object)_videoSource != (Object)null)
			{
				_videoSource.Dispose();
				Object.Destroy((Object)(object)_videoSource);
				_videoSource = null;
			}
			if ((Object)(object)_cameraRig != (Object)null)
			{
				_cameraRig.Dispose();
				Object.Destroy((Object)(object)_cameraRig);
				_cameraRig = null;
			}
			if ((Object)(object)_gameplayCapture != (Object)null)
			{
				_gameplayCapture.Dispose();
				Object.Destroy((Object)(object)_gameplayCapture);
				_gameplayCapture = null;
			}
			_frameRateLimit.Restore("CaptureMode");
		}

		internal void Shutdown()
		{
			global::UnityRuntimeCameraRecorder.UnityRuntimeCameraRecorder recorder = _recorder;
			if (recorder != null && recorder.IsCapturing)
			{
				_recorder.StopRecording();
			}
			ReleaseSession();
		}

		private static void Notify(string message)
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)1, message, 0, (Sprite)null, false);
			}
		}
	}
	internal sealed class CameraPlacementDirector
	{
		private const int MaximumPlacementAttempts = 4;

		private const float MinimumViewAngle = 45f;

		private static readonly CameraPlacement[] NormalPlacements = new CameraPlacement[3]
		{
			CameraPlacement.Front,
			CameraPlacement.Left,
			CameraPlacement.Right
		};

		private static readonly CameraPlacement[] OpenAreaPlacements = new CameraPlacement[9]
		{
			CameraPlacement.Front,
			CameraPlacement.Left,
			CameraPlacement.Right,
			CameraPlacement.Front,
			CameraPlacement.Left,
			CameraPlacement.Right,
			CameraPlacement.FrontTop,
			CameraPlacement.LeftTop,
			CameraPlacement.RightTop
		};

		private CameraPlacement? _lastPlacement;

		internal bool TryPlace(SagaCaptureRig rig)
		{
			CameraPlacement[] placements = (rig.IsTargetInForest() ? NormalPlacements : OpenAreaPlacements);
			for (int i = 0; i < 4; i++)
			{
				CameraPlacement cameraPlacement = SelectPlacement(placements);
				if (rig.TryCutViewpoint(cameraPlacement, 45f))
				{
					_lastPlacement = cameraPlacement;
					return true;
				}
			}
			return false;
		}

		private CameraPlacement SelectPlacement(CameraPlacement[] placements)
		{
			CameraPlacement[] array = new CameraPlacement[placements.Length];
			int num = 0;
			foreach (CameraPlacement cameraPlacement in placements)
			{
				if (!_lastPlacement.HasValue || cameraPlacement != _lastPlacement.Value)
				{
					array[num++] = cameraPlacement;
				}
			}
			return array[Random.Range(0, num)];
		}
	}
	internal static class SagaCaptureAudioListener
	{
		internal static AudioListener FindActive(Camera gameplayCamera)
		{
			AudioListener val = ((Component)gameplayCamera).GetComponent<AudioListener>() ?? ((Component)gameplayCamera).GetComponentInParent<AudioListener>();
			if ((Object)(object)val != (Object)null)
			{
				return val;
			}
			AudioListener[] array = Object.FindObjectsByType<AudioListener>((FindObjectsSortMode)0);
			AudioListener[] array2 = array;
			foreach (AudioListener val2 in array2)
			{
				if (((Behaviour)val2).enabled && ((Component)val2).gameObject.activeInHierarchy)
				{
					return val2;
				}
			}
			if (array.Length == 0)
			{
				return null;
			}
			return array[0];
		}
	}
	internal sealed class SagaCaptureFrameRateDisplay : MonoBehaviour
	{
		private GUIStyle _style;

		private int _renderedFrames;

		private float _measurementStartedAt;

		private float _framesPerSecond;

		private void Awake()
		{
			_measurementStartedAt = Time.realtimeSinceStartup;
		}

		private void OnPostRender()
		{
			_renderedFrames++;
			float num = Time.realtimeSinceStartup - _measurementStartedAt;
			if (!(num < 0.5f))
			{
				_framesPerSecond = (float)_renderedFrames / num;
				_renderedFrames = 0;
				_measurementStartedAt = Time.realtimeSinceStartup;
			}
		}

		private void OnGUI()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: 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_004f: Expected O, but got Unknown
			if (_style == null)
			{
				int fontSize = Mathf.Clamp(Mathf.RoundToInt((float)Screen.height / 65f), 22, 38);
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					fontSize = fontSize
				};
				val.normal.textColor = Color.white;
				_style = val;
			}
			GUI.Label(new Rect(8f, 6f, 300f, 55f), $"{Mathf.RoundToInt(_framesPerSecond)} FPS", _style);
		}
	}
	internal sealed class SagaCaptureFrameRateLimit
	{
		private int _originalTargetFrameRate;

		private int _originalVSyncCount;

		private bool _applied;

		internal void Apply(string mode, int maximumFrameRate, bool limitGameFrameRate)
		{
			if (!_applied && limitGameFrameRate)
			{
				_originalTargetFrameRate = Application.targetFrameRate;
				_originalVSyncCount = QualitySettings.vSyncCount;
				QualitySettings.vSyncCount = 0;
				Application.targetFrameRate = maximumFrameRate;
				_applied = true;
				SagaCapturePlugin.Log.LogDebug((object)(mode + " frame-rate limit applied: " + $"{maximumFrameRate} FPS " + $"(previous target={_originalTargetFrameRate}, " + $"vSyncCount={_originalVSyncCount})."));
			}
		}

		internal void Restore(string mode)
		{
			if (_applied)
			{
				Application.targetFrameRate = _originalTargetFrameRate;
				QualitySettings.vSyncCount = _originalVSyncCount;
				_applied = false;
				SagaCapturePlugin.Log.LogDebug((object)(mode + " restored game frame-rate state: " + $"target={_originalTargetFrameRate}, " + $"vSyncCount={_originalVSyncCount}."));
			}
		}
	}
	internal sealed class SagaCaptureGameplayCapture : MonoBehaviour
	{
		private SagaCaptureVideoSource _videoSource;

		internal void Initialize(SagaCaptureVideoSource videoSource)
		{
			_videoSource = videoSource;
		}

		private void OnRenderImage(RenderTexture source, RenderTexture destination)
		{
			_videoSource?.CopyGameplay(source);
			Graphics.Blit((Texture)(object)source, destination);
		}

		internal void Dispose()
		{
			_videoSource = null;
		}
	}
	internal sealed class SagaCaptureVideoSource : MonoBehaviour
	{
		private Camera _cinematicCamera;

		private RenderTexture _output;

		private bool _includeGameplayUi;

		private Coroutine _screenCaptureRoutine;

		internal Texture Texture => (Texture)(object)_output;

		internal bool IsCinematic { get; private set; }

		internal void Initialize(Camera cinematicCamera, RenderTexture output, bool includeGameplayUi)
		{
			_cinematicCamera = cinematicCamera;
			_output = output;
			_includeGameplayUi = includeGameplayUi;
			SetCinematic(enabled: true);
			if (includeGameplayUi)
			{
				_screenCaptureRoutine = ((MonoBehaviour)this).StartCoroutine(CaptureScreenFrames());
			}
		}

		internal void SetCinematic(bool enabled)
		{
			IsCinematic = enabled;
			if ((Object)(object)_cinematicCamera != (Object)null)
			{
				((Behaviour)_cinematicCamera).enabled = enabled;
				if (enabled)
				{
					_cinematicCamera.Render();
				}
			}
		}

		internal void CopyGameplay(RenderTexture source)
		{
			if (!IsCinematic && !_includeGameplayUi && (Object)(object)_output != (Object)null)
			{
				Graphics.Blit((Texture)(object)source, _output);
			}
		}

		private IEnumerator CaptureScreenFrames()
		{
			while (true)
			{
				yield return (object)new WaitForEndOfFrame();
				if (!IsCinematic && (Object)(object)_output != (Object)null)
				{
					CopyScreenWithVerticalCorrection();
				}
			}
		}

		private void CopyScreenWithVerticalCorrection()
		{
			//IL_001d: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			RenderTexture temporary = RenderTexture.GetTemporary(((Texture)_output).width, ((Texture)_output).height, 0, _output.format, (RenderTextureReadWrite)2);
			try
			{
				ScreenCapture.CaptureScreenshotIntoRenderTexture(temporary);
				Graphics.Blit((Texture)(object)temporary, _output, new Vector2(1f, -1f), new Vector2(0f, 1f));
			}
			finally
			{
				RenderTexture.ReleaseTemporary(temporary);
			}
		}

		internal void Dispose()
		{
			if (_screenCaptureRoutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_screenCaptureRoutine);
				_screenCaptureRoutine = null;
			}
			_cinematicCamera = null;
			_output = null;
		}
	}
	internal sealed class SagaCaptureRig : MonoBehaviour
	{
		private Camera _camera;

		private Camera _sourceCamera;

		private RenderTexture _offscreenTarget;

		private CameraOperatorController _cameraOperator;

		private ValheimCameraAdapter _adapter;

		private readonly SagaCaptureEffects _effects = new SagaCaptureEffects();

		private bool _movementEnabled;

		internal Camera Camera => _camera;

		internal RenderTexture OutputTexture => _offscreenTarget;

		internal bool IsMoving => _movementEnabled;

		internal void Initialize(Camera sourceCamera, GraphicsSettingsState? captureSettings = null)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			_sourceCamera = sourceCamera;
			GameObject val = new GameObject("SagaCaptureCamera");
			val.transform.SetParent(((Component)this).transform, false);
			_camera = val.AddComponent<Camera>();
			_camera.CopyFrom(sourceCamera);
			_camera.fieldOfView = sourceCamera.fieldOfView;
			_camera.depth = sourceCamera.depth + 1f;
			((Behaviour)_camera).enabled = false;
			_effects.Initialize(sourceCamera, val, captureSettings);
			SynchronizePose();
		}

		internal void BeginMovement()
		{
			SynchronizePose();
			if (Preference.ShowFrameRate)
			{
				((Component)_camera).gameObject.AddComponent<SagaCaptureFrameRateDisplay>();
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				throw new InvalidOperationException("The local player is unavailable.");
			}
			_adapter = new ValheimCameraAdapter();
			_cameraOperator = new CameraOperatorController(_camera, ((Component)localPlayer).gameObject, Preference.CameraOperatorConfigPath, _adapter.CreateWorld());
			_movementEnabled = true;
		}

		internal void BeginWarmup(int requestedWidth, int requestedHeight, int antiAliasingSamples, FilterMode filterMode)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			int num = Mathf.Max(2, requestedWidth & -2);
			int num2 = Mathf.Max(2, requestedHeight & -2);
			_offscreenTarget = new RenderTexture(num, num2, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2);
			_offscreenTarget.antiAliasing = antiAliasingSamples;
			((Texture)_offscreenTarget).filterMode = filterMode;
			_offscreenTarget.Create();
			_camera.targetTexture = _offscreenTarget;
			((Behaviour)_camera).enabled = true;
		}

		private void LateUpdate()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			_effects.Synchronize();
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)_camera == (Object)null))
			{
				_camera.fieldOfView = _sourceCamera.fieldOfView;
				if (!_movementEnabled)
				{
					SynchronizePose();
				}
				else
				{
					_cameraOperator?.Update(((Character)localPlayer).GetVelocity(), ((Character)localPlayer).m_runSpeed);
				}
			}
		}

		internal bool HasTargetVisibility()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!_movementEnabled || (Object)(object)localPlayer == (Object)null)
			{
				return false;
			}
			Vector3 val = PlayerFocus(localPlayer);
			Vector3 val2 = _camera.WorldToViewportPoint(val);
			if (val2.z > 0f && val2.x >= 0f && val2.x <= 1f && val2.y >= 0f && val2.y <= 1f)
			{
				return IsTargetVisible(((Component)_camera).transform.position, localPlayer);
			}
			return false;
		}

		internal bool TryCutViewpoint(CameraPlacement placement, float minimumViewAngle)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if (!_movementEnabled || (Object)(object)localPlayer == (Object)null)
			{
				return false;
			}
			Vector3 position = CreateViewpoint(PlayerFocus(localPlayer), localPlayer, placement);
			if (!HasDistinctDirection(position, localPlayer, minimumViewAngle) || !IsTargetVisible(position, localPlayer))
			{
				return false;
			}
			_cameraOperator?.Reposition(position, ((Character)localPlayer).GetVelocity());
			return true;
		}

		private bool HasDistinctDirection(Vector3 position, Player player, float minimumViewAngle)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			Vector3 directionFromTarget = _cameraOperator.DirectionFromTarget;
			Vector3 val = position - ((Component)player).transform.position;
			val.y = 0f;
			if (((Vector3)(ref directionFromTarget)).sqrMagnitude != 0f && ((Vector3)(ref val)).sqrMagnitude != 0f)
			{
				return Vector3.Angle(directionFromTarget, val) >= minimumViewAngle;
			}
			return true;
		}

		internal bool IsTargetInForest()
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				return _adapter?.IsForest(((Component)localPlayer).transform.position) ?? false;
			}
			return false;
		}

		private Vector3 CreateViewpoint(Vector3 focus, Player player, CameraPlacement placement)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = CreateViewDirection(player, placement);
			float num = Random.Range(PlacementSetting("minimum_distance"), PlacementSetting("maximum_distance"));
			Vector3 result = focus + val * num;
			result.y = focus.y + (IsTop(placement) ? PlacementSetting("top_height") : 0f);
			return result;
		}

		private float PlacementSetting(string path)
		{
			return _cameraOperator.Settings.Number("positioning." + path);
		}

		private Vector3 PlayerFocus(Player player)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			return ((Component)player).transform.position + Vector3.up * _cameraOperator.TargetHeight;
		}

		private Vector3 CreateViewDirection(Player player, CameraPlacement placement)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			Vector3 forward = ((Component)player).transform.forward;
			forward.y = 0f;
			if (((Vector3)(ref forward)).sqrMagnitude < 0.01f)
			{
				forward = Vector3.forward;
			}
			float num = CreateViewAngle(placement);
			return Quaternion.Euler(0f, num, 0f) * ((Vector3)(ref forward)).normalized;
		}

		private float CreateViewAngle(CameraPlacement placement)
		{
			float num = PlacementSetting("angle_variation");
			return PlacementAngle(placement) + Random.Range(0f - num, num);
		}

		private static float PlacementAngle(CameraPlacement placement)
		{
			switch (placement)
			{
			case CameraPlacement.Left:
			case CameraPlacement.LeftTop:
				return -90f;
			case CameraPlacement.Right:
			case CameraPlacement.RightTop:
				return 90f;
			default:
				return 0f;
			}
		}

		private static bool IsTop(CameraPlacement placement)
		{
			if (placement != CameraPlacement.FrontTop && placement != CameraPlacement.LeftTop)
			{
				return placement == CameraPlacement.RightTop;
			}
			return true;
		}

		private bool IsTargetVisible(Vector3 position, Player player)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = PlayerFocus(player) - position;
			RaycastHit[] array = Physics.RaycastAll(position, ((Vector3)(ref val)).normalized, ((Vector3)(ref val)).magnitude, -1, (QueryTriggerInteraction)1);
			Array.Sort(array, (RaycastHit left, RaycastHit right) => ((RaycastHit)(ref left)).distance.CompareTo(((RaycastHit)(ref right)).distance));
			RaycastHit[] array2 = array;
			int num = 0;
			if (num < array2.Length)
			{
				RaycastHit val2 = array2[num];
				return (Object)(object)((Component)((RaycastHit)(ref val2)).collider).GetComponentInParent<Player>() == (Object)(object)player;
			}
			return true;
		}

		internal void Dispose()
		{
			_cameraOperator?.Dispose();
			_cameraOperator = null;
			EndOffscreenRendering();
			if ((Object)(object)_camera != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_camera).gameObject);
				_camera = null;
				_effects.Clear();
			}
		}

		private void SynchronizePose()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_sourceCamera == (Object)null) && !((Object)(object)_camera == (Object)null))
			{
				((Component)_camera).transform.SetPositionAndRotation(((Component)_sourceCamera).transform.position, ((Component)_sourceCamera).transform.rotation);
			}
		}

		private void EndOffscreenRendering()
		{
			if ((Object)(object)_camera != (Object)null)
			{
				((Behaviour)_camera).enabled = false;
				_camera.targetTexture = null;
			}
			if ((Object)(object)_offscreenTarget != (Object)null)
			{
				_offscreenTarget.Release();
				Object.Destroy((Object)(object)_offscreenTarget);
				_offscreenTarget = null;
			}
		}
	}
	internal sealed class ValheimCameraAdapter
	{
		private static readonly string[] LightVegetationNames = new string[11]
		{
			"grass", "bush", "shrub", "fern", "heath", "berry", "raspberry", "blueberry", "cloudberry", "thistle",
			"dandelion"
		};

		private float _nextTreeScan;

		private bool _forest;

		internal CameraWorld CreateWorld()
		{
			return new CameraWorld
			{
				GroundHeight = GroundHeight,
				IgnoreObstacle = IgnoreObstacle,
				IsActor = (Collider collider) => (Object)(object)((Component)collider).GetComponentInParent<Character>() != (Object)null,
				LogDebug = delegate(string message)
				{
					SagaCapturePlugin.Log.LogDebug((object)message);
				},
				LogInfo = delegate(string message)
				{
					SagaCapturePlugin.Log.LogInfo((object)message);
				},
				LogWarning = delegate(string message)
				{
					SagaCapturePlugin.Log.LogWarning((object)message);
				}
			};
		}

		internal static float? GroundHeight(Vector3 position)
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			float value = default(float);
			if (!((Object)(object)ZoneSystem.instance != (Object)null) || !ZoneSystem.instance.GetGroundHeight(position, ref value))
			{
				return null;
			}
			return value;
		}

		private static bool IgnoreObstacle(Collider collider)
		{
			if ((Object)(object)collider == (Object)null || (Object)(object)((Component)collider).GetComponentInParent<Heightmap>() != (Object)null || (Object)(object)((Component)collider).GetComponentInParent<ItemDrop>() != (Object)null || (Object)(object)((Component)collider).GetComponentInParent<Pickable>() != (Object)null)
			{
				return true;
			}
			Transform val = ((Component)collider).transform;
			while ((Object)(object)val != (Object)null)
			{
				string text = ((Object)val).name.ToLowerInvariant();
				string[] lightVegetationNames = LightVegetationNames;
				foreach (string value in lightVegetationNames)
				{
					if (text.Contains(value))
					{
						return true;
					}
				}
				val = val.parent;
			}
			return false;
		}

		internal bool IsForest(Vector3 position)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			if (Time.time < _nextTreeScan)
			{
				return _forest;
			}
			_nextTreeScan = Time.time + 0.5f;
			Collider[] array = Physics.OverlapSphere(position, 10f, -1, (QueryTriggerInteraction)1);
			HashSet<TreeBase> hashSet = new HashSet<TreeBase>();
			Collider[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				TreeBase componentInParent = ((Component)array2[i]).GetComponentInParent<TreeBase>();
				if ((Object)(object)componentInParent != (Object)null)
				{
					hashSet.Add(componentInParent);
				}
			}
			_forest = hashSet.Count >= 2;
			return _forest;
		}
	}
	internal static class SagaCaptureCameraLogger
	{
		internal static void LogEffectiveConfiguration(string mode, Camera source, Camera cinematicCamera, GraphicsSettingsState sourceSettings, GraphicsSettingsState cinematicSettings)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)source == (Object)null) && !((Object)(object)cinematicCamera == (Object)null))
			{
				SagaCapturePlugin.Log.LogDebug((object)(mode + " effective camera configuration:"));
				LogCamera("Game", source, sourceSettings, Application.targetFrameRate, QualitySettings.vSyncCount > 0);
				LogCamera("Cinematic", cinematicCamera, cinematicSettings, Preference.CameraMaximumFrameRate, QualitySettings.vSyncCount > 0);
			}
		}

		private static void LogCamera(string label, Camera camera, GraphicsSettingsState settings, int maximumFrameRate, bool vSync)
		{
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			GetPixels(camera, out var width, out var height);
			SagaCapturePlugin.Log.LogDebug((object)string.Format(CultureInfo.InvariantCulture, "  {0}: pixels={1}x{2}, maximumFrameRate={3}, FOV={4:F1}, aspect={5:F3}, clip={6:F2}-{7:F1}m, target3DResolutionHeight={8}, upscaling={9}, vSync={10}.", label, width, height, maximumFrameRate, camera.fieldOfView, camera.aspect, camera.nearClipPlane, camera.farClipPlane, settings.m_target3DResolutionVertical, settings.m_upscalingAlgorithm, vSync));
			LogQualitySettings(label, settings);
		}

		private static void LogQualitySettings(string label, GraphicsSettingsState settings)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: 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_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0153: 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_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			SagaCapturePlugin.Log.LogDebug((object)($"  {label} quality: vegetation={settings.m_vegetation}, " + $"LOD={settings.m_lod}, particleLights={settings.m_lights}, " + $"shadowQuality={settings.m_shadowQuality}, " + $"pointLights={settings.m_pointLights}, " + $"pointLightShadows={settings.m_pointLightShadows}, " + $"SSAO={settings.m_ssao}, clothQuality={settings.m_clothQuality}, " + $"drawDistance={settings.m_simulationDistance}."));
			SagaCapturePlugin.Log.LogDebug((object)($"  {label} effects: distantShadows={settings.m_distantShadows}, " + $"tessellation={settings.m_tesselation}, bloom={settings.m_bloom}, " + $"depthOfField={settings.m_depthOfField}, " + $"motionBlur={settings.m_motionBlur}, " + $"chromaticAberration={settings.m_chromaticAberration}, " + $"sunShafts={settings.m_sunShafts}, " + $"softParticles={settings.m_softParticles}, " + $"antiAliasing={settings.m_antiAliasing}, " + $"anisotropicTextures={settings.m_anisotropicTextures}."));
		}

		private static void GetPixels(Camera camera, out int width, out int height)
		{
			if ((Object)(object)camera.targetTexture != (Object)null)
			{
				width = ((Texture)camera.targetTexture).width;
				height = ((Texture)camera.targetTexture).height;
			}
			else
			{
				width = camera.pixelWidth;
				height = camera.pixelHeight;
			}
		}
	}
	internal sealed class SagaCaptureEffectMirror
	{
		internal readonly Component Source;

		internal readonly Component Destination;

		internal SagaCaptureEffectMirror(Component source, Component destination)
		{
			Source = source;
			Destination = destination;
		}
	}
	internal sealed class SagaCaptureEffects
	{
		private readonly List<SagaCaptureEffectMirror> _mirrors = new List<SagaCaptureEffectMirror>();

		private static readonly Dictionary<Type, FieldInfo[]> SerializableFields = new Dictionary<Type, FieldInfo[]>();

		private static readonly HashSet<string> WarnedComponents = new HashSet<string>();

		private GraphicsSettingsState? _captureSettings;

		internal void Initialize(Camera sourceCamera, GameObject target, GraphicsSettingsState? captureSettings = null)
		{
			_captureSettings = captureSettings;
			Component[] components = ((Component)sourceCamera).gameObject.GetComponents<Component>();
			foreach (Component source in components)
			{
				CopyComponent(sourceCamera, target, source);
			}
		}

		internal void Synchronize()
		{
			foreach (SagaCaptureEffectMirror mirror in _mirrors)
			{
				if (!((Object)(object)mirror.Source == (Object)null) && !((Object)(object)mirror.Destination == (Object)null))
				{
					CopySerializedSettings(mirror.Source, mirror.Destination);
					SynchronizeEnabledState(mirror.Source, mirror.Destination);
					ApplyCaptureSettings(mirror.Destination);
				}
			}
		}

		internal void Clear()
		{
			_mirrors.Clear();
			_captureSettings = null;
		}

		private void CopyComponent(Camera sourceCamera, GameObject target, Component source)
		{
			if (source is FlareLayer)
			{
				target.AddComponent<FlareLayer>();
				return;
			}
			if (source is PostProcessingBehaviour)
			{
				CopyPostProcessing(sourceCamera, target);
				return;
			}
			string fullName = ((object)source).GetType().FullName;
			if (!IsMirroredVisualEffect(fullName))
			{
				WarnWhenUnclassified(fullName);
				return;
			}
			Component destination = target.AddComponent(((object)source).GetType());
			CopySerializedSettings(source, destination);
			SynchronizeEnabledState(source, destination);
			ApplyCaptureSettings(destination);
			_mirrors.Add(new SagaCaptureEffectMirror(source, destination));
		}

		private static void WarnWhenUnclassified(string typeName)
		{
			if (!IsExcludedCameraComponent(typeName) && WarnedComponents.Add(typeName))
			{
				SagaCapturePlugin.Log.LogWarning((object)("Camera component '" + typeName + "' is not mirrored or excluded; SagaCapture left it off the secondary camera."));
			}
		}

		private static bool IsExcludedCameraComponent(string typeName)
		{
			switch (typeName)
			{
			case "ShieldDomeImageEffect":
			case "UnityEngine.Transform":
			case "UnityEngine.Camera":
			case "UnityEngine.AudioListener":
			case "GameCamera":
			case "CameraEffects":
			case "UpscaledFrameBuffer":
				return true;
			default:
				return false;
			}
		}

		private static bool IsMirroredVisualEffect(string typeName)
		{
			switch (typeName)
			{
			case "GlobalBlueNoise":
			case "AmplifyOcclusionEffect":
			case "UnityStandardAssets.ImageEffects.SunShafts":
			case "UnityStandardAssets.ImageEffects.DepthOfField":
			case "HeatDistortImageEffect":
			case "DepthCopy":
				return true;
			default:
				return false;
			}
		}

		private static void CopySerializedSettings(Component source, Component destination)
		{
			FieldInfo[] serializableFields = GetSerializableFields(((object)source).GetType());
			foreach (FieldInfo fieldInfo in serializableFields)
			{
				fieldInfo.SetValue(destination, fieldInfo.GetValue(source));
			}
		}

		private static FieldInfo[] GetSerializableFields(Type type)
		{
			if (SerializableFields.TryGetValue(type, out var value))
			{
				return value;
			}
			List<FieldInfo> list = new List<FieldInfo>();
			Type type2 = type;
			while (type2 != null && type2 != typeof(MonoBehaviour))
			{
				AddSerializableFields(type2, list);
				type2 = type2.BaseType;
			}
			value = list.ToArray();
			SerializableFields[type] = value;
			return value;
		}

		private static void AddSerializableFields(Type type, List<FieldInfo> fields)
		{
			FieldInfo[] fields2 = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields2)
			{
				if ((fieldInfo.IsPublic || fieldInfo.IsDefined(typeof(SerializeField), inherit: true)) && !fieldInfo.IsStatic && !fieldInfo.IsInitOnly && !fieldInfo.IsNotSerialized)
				{
					fields.Add(fieldInfo);
				}
			}
		}

		private static void SynchronizeEnabledState(Component source, Component destination)
		{
			Behaviour val = (Behaviour)(object)((source is Behaviour) ? source : null);
			if (val != null)
			{
				Behaviour val2 = (Behaviour)(object)((destination is Behaviour) ? destination : null);
				if (val2 != null)
				{
					val2.enabled = val.enabled;
				}
			}
		}

		private void CopyPostProcessing(Camera sourceCamera, GameObject target)
		{
			//IL_0056: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			PostProcessingBehaviour component = ((Component)sourceCamera).GetComponent<PostProcessingBehaviour>();
			if (!((Object)(object)component == (Object)null) && !((Object)(object)component.profile == (Object)null))
			{
				PostProcessingBehaviour val = target.AddComponent<PostProcessingBehaviour>();
				val.profile = Object.Instantiate<PostProcessingProfile>(component.profile);
				((Behaviour)val).enabled = ((Behaviour)component).enabled;
				if (_captureSettings.HasValue)
				{
					GraphicsSettingsState value = _captureSettings.Value;
					((PostProcessingModel)val.profile.bloom).enabled = value.m_bloom;
					((PostProcessingModel)val.profile.motionBlur).enabled = value.m_motionBlur;
					((PostProcessingModel)val.profile.chromaticAberration).enabled = value.m_chromaticAberration;
					((PostProcessingModel)val.profile.antialiasing).enabled = value.m_antiAliasing;
				}
			}
		}

		private void ApplyCaptureSettings(Component destination)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			if (!_captureSettings.HasValue)
			{
				return;
			}
			GraphicsSettingsState value = _captureSettings.Value;
			AmplifyOcclusionEffect val = (AmplifyOcclusionEffect)(object)((destination is AmplifyOcclusionEffect) ? destination : null);
			if (val != null)
			{
				((Behaviour)val).enabled = value.m_ssao > 0;
				val.Downsample = true;
				val.SampleCount = (SampleCountLevel)(value.m_ssao > 1);
				return;
			}
			if (((object)destination).GetType().FullName == "UnityStandardAssets.ImageEffects.DepthOfField")
			{
				Behaviour val2 = (Behaviour)(object)((destination is Behaviour) ? destination : null);
				if (val2 != null)
				{
					val2.enabled = value.m_depthOfField;
					return;
				}
			}
			if (((object)destination).GetType().FullName == "UnityStandardAssets.ImageEffects.SunShafts")
			{
				Behaviour val3 = (Behaviour)(object)((destination is Behaviour) ? destination : null);
				if (val3 != null)
				{
					val3.enabled = value.m_sunShafts;
				}
			}
		}
	}
	internal static class SagaCaptureRendering
	{
		internal unsafe static GraphicsSettingsState GetGraphicsSettings()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			return Unsafe.Read<GraphicsSettingsState>((void*)GraphicsSettingsManager.Instance.ActiveSettings);
		}

		internal static void GetResolution(GraphicsSettingsState settings, out int width, out int height, out FilterMode filterMode)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			width = Screen.width;
			height = Screen.height;
			filterMode = (FilterMode)((int)settings.m_upscalingAlgorithm != 1);
		}
	}
	[HarmonyPatch(typeof(FootStep), "SetEffectCreator")]
	internal static class SagaCaptureFootstepVolumePatch
	{
		private const float VolumeMultiplier = 0.6f;

		private static void Postfix(GameObject go)
		{
			if (SagaCapturePlugin.IsCinematicImageActive)
			{
				ZSFX[] componentsInChildren = go.GetComponentsInChildren<ZSFX>();
				foreach (ZSFX obj in componentsInChildren)
				{
					obj.SetVolumeModifier(obj.GetVolumeModifier() * 0.6f);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Menu), "OnLogout")]
	internal static class SagaCaptureLogoutMenuPatch
	{
		private static void Prefix()
		{
			SagaCapturePlugin.StopRecordingForMenuExit();
		}
	}
	[HarmonyPatch(typeof(Character), "SetVisible")]
	internal static class SagaCapturePlayerVisibilityPatch
	{
		private static bool Prefix(Character __instance, bool visible)
		{
			if (!visible && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				return !SagaCapturePlugin.IsCinematicCameraActive;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(Menu), "OnQuit")]
	internal static class SagaCaptureQuitMenuPatch
	{
		private static void Prefix()
		{
			SagaCapturePlugin.StopRecordingForMenuExit();
		}
	}
}
namespace Landoria.Shared
{
	internal static class ConfigWatcher
	{
		private const int ReloadDelayMilliseconds = 250;

		private const int MaximumReloadAttempts = 5;

		private static ConfigFile config;

		private static string configPath;

		private static ManualLogSource logger;

		private static FileSystemWatcher watcher;

		private static long lastChangeTicks;

		private static int reloadPending;

		private static int reloadAttempts;

		private static long ignoredWriteTicks;

		private static string displayName;

		private static Action restoreDefaults;

		internal static void Initialize(ConfigFile configFile, ManualLogSource logSource, string configurationDisplayName, Action restoreDefaultsAction)
		{
			Dispose();
			config = configFile;
			logger = logSource;
			displayName = configurationDisplayName;
			restoreDefaults = restoreDefaultsAction;
			configPath = Path.GetFullPath(config.ConfigFilePath);
			watcher = new FileSystemWatcher(Path.GetDirectoryName(configPath), Path.GetFileName(configPath));
			watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime;
			watcher.Changed += OnChanged;
			watcher.Created += OnChanged;
			watcher.Deleted += OnChanged;
			watcher.Renamed += OnRenamed;
			watcher.EnableRaisingEvents = true;
		}

		internal static void IgnoreCurrentFileVersion()
		{
			if (string.IsNullOrEmpty(configPath) || !File.Exists(configPath))
			{
				return;
			}
			try
			{
				Interlocked.Exchange(ref ignoredWriteTicks, File.GetLastWriteTimeUtc(configPath).Ticks);
				Interlocked.Exchange(ref reloadPending, 0);
			}
			catch (IOException)
			{
				ManualLogSource obj = logger;
				if (obj != null)
				{
					obj.LogDebug((object)"Could not mark the internal configuration write.");
				}
			}
		}

		internal static void Update()
		{
			if (Volatile.Read(in reloadPending) == 0 || !ReloadDelayElapsed())
			{
				return;
			}
			Interlocked.Exchange(ref reloadPending, 0);
			try
			{
				bool flag = !File.Exists(configPath);
				if (flag)
				{
					restoreDefaults?.Invoke();
				}
				else
				{
					config.Reload();
				}
				reloadAttempts = 0;
				logger.LogInfo((object)(flag ? "Recreated the deleted configuration with default values." : "Reloaded configuration after a file change."));
				ShowReloadMessage(flag);
			}
			catch (Exception exception)
			{
				RetryReload(exception);
			}
		}

		internal static void Dispose()
		{
			if (watcher != null)
			{
				watcher.EnableRaisingEvents = false;
				watcher.Dispose();
				watcher = null;
			}
			Interlocked.Exchange(ref reloadPending, 0);
			Interlocked.Exchange(ref ignoredWriteTicks, 0L);
			reloadAttempts = 0;
			displayName = null;
			restoreDefaults = null;
		}

		private static void OnChanged(object sender, FileSystemEventArgs eventArgs)
		{
			QueueReload();
		}

		private static void OnRenamed(object sender, RenamedEventArgs eventArgs)
		{
			QueueReload();
		}

		private static void QueueReload()
		{
			if (!IsIgnoredWrite())
			{
				Interlocked.Exchange(ref lastChangeTicks, DateTime.UtcNow.Ticks);
				Interlocked.Exchange(ref reloadPending, 1);
				reloadAttempts = 0;
			}
		}

		private static bool IsIgnoredWrite()
		{
			try
			{
				long num = (File.Exists(configPath) ? File.GetLastWriteTimeUtc(configPath).Ticks : 0);
				return num != 0L && num == Interlocked.Read(in ignoredWriteTicks);
			}
			catch (IOException)
			{
				return false;
			}
		}

		private static bool ReloadDelayElapsed()
		{
			return DateTime.UtcNow.Ticks - Interlocked.Read(in lastChangeTicks) >= 2500000;
		}

		private static void RetryReload(Exception exception)
		{
			reloadAttempts++;
			logger.LogWarning((object)("Configuration reload failed: " + exception.Message));
			if (reloadAttempts < 5)
			{
				Interlocked.Exchange(ref lastChangeTicks, DateTime.UtcNow.Ticks);
				Interlocked.Exchange(ref reloadPending, 1);
			}
		}

		private static void ShowReloadMessage(bool recreated)
		{
			Player localPlayer = Player.m_localPlayer;
			if (localPlayer != null)
			{
				((Character)localPlayer).Message((MessageType)1, recreated ? (displayName + " configuration restored to defaults.") : (displayName + " configuration reloaded."), 0, (Sprite)null, false);
			}
		}
	}
}
[CompilerGenerated]
internal sealed class <9843cd67-5340-4dbb-ae39-62fa8acd33df><PrivateImplementationDetails>
{
	[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 36)]
	internal struct __StaticArrayInitTypeSize=36
	{
	}

	internal static readonly __StaticArrayInitTypeSize=36 1B5C4113D97C9FAF8A59F759908B702F3A107D428F0801B16079D56F3F9A5C88/* Not supported: data(00 00 00 00 01 00 00 00 02 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00) */;
}
namespace UnityRuntimeCameraRecorder
{
	internal sealed class ImageSequenceCapture : MonoBehaviour
	{
		private sealed class ImageFrame
		{
			public int Number { get; }

			public byte[] Pixels { get; }

			public ImageFrame(int number, byte[] pixels)
			{
				Number = number;
				Pixels = pixels;
			}
		}

		private Camera _camera;

		private ImageSequenceSettings _settings;

		private RenderTexture _source;

		private RenderTexture _readbackTarget;

		private RenderTexture _previousTarget;

		private Coroutine _captureRoutine;

		private BlockingCollection<ImageFrame> _frames;

		private Task[] _writerTasks;

		private Exception _writerException;

		private long _captureIntervalTicks;

		private long _nextCaptureTimestamp;

		private bool _previousEnabled;

		private bool _ownsSource;

		private bool _ownsReadbackTarget;

		private bool _active;

		private int _pendingReadbacks;

		private int _capturedFrameCount;

		private int _droppedFrameCount;

		private int _nextFrameNumber;

		public int CapturedFrameCount => _capturedFrameCount;

		public void StartCapture(Camera camera, ImageSequenceSettings settings, RenderTexture preparedTarget)
		{
			_camera = camera;
			_settings = settings;
			Directory.CreateDirectory(settings.OutputDirectory);
			ValidatePreparedTarget(preparedTarget, settings.Width, settings.Height);
			_source = preparedTarget ?? CreateTarget(settings.Width, settings.Height, settings.AntiAliasingSamples);
			_ownsSource = (Object)(object)preparedTarget == (Object)null;
			bool flag = _source.antiAliasing > 1 || settings.FlipVertically;
			_readbackTarget = (flag ? CreateTarget(settings.Width, settings.Height, 1) : _source);
			_ownsReadbackTarget = flag;
			_frames = new BlockingCollection<ImageFrame>(settings.MaximumQueuedFrames);
			_writerTasks = new Task[settings.EncoderThreadCount];
			for (int i = 0; i < _writerTasks.Length; i++)
			{
				_writerTasks[i] = Task.Run((Action)WriteFrames);
			}
			_captureIntervalTicks = Math.Max(1L, (long)((double)Stopwatch.Frequency / settings.CapturesPerSecond));
			_nextCaptureTimestamp = Stopwatch.GetTimestamp() + (long)((double)Stopwatch.Frequency * settings.InitialDelaySeconds);
			_previousTarget = camera.targetTexture;
			_previousEnabled = ((Behaviour)camera).enabled;
			camera.targetTexture = _source;
			((Behaviour)camera).enabled = true;
			_active = true;
			_captureRoutine = ((MonoBehaviour)this).StartCoroutine(CaptureLoop());
			RecorderLog.WriteInfo($"Asynchronous image sequence capture started at {settings.CapturesPerSecond:0.###} image(s) per second: {settings.OutputDirectory}");
		}

		public void StopCapture()
		{
			if (_active || !((Object)(object)_source == (Object)null))
			{
				_active = false;
				if (_captureRoutine != null)
				{
					((MonoBehaviour)this).StopCoroutine(_captureRoutine);
					_captureRoutine = null;
				}
				AsyncGPUReadback.WaitAllRequests();
				_frames?.CompleteAdding();
				if (_writerTasks != null)
				{
					Task.WaitAll(_writerTasks);
				}
				RestoreCameraAndReleaseTargets();
				_frames?.Dispose();
				_frames = null;
				_writerTasks = null;
				RecorderLog.WriteInfo($"Image sequence capture stopped: written={_capturedFrameCount}, dropped={_droppedFrameCount}.");
				if (_writerException != null)
				{
					throw new IOException("The image sequence writer failed.", _writerException);
				}
			}
		}

		private void OnDestroy()
		{
			try
			{
				StopCapture();
			}
			catch (Exception exception)
			{
				RecorderLog.WriteError(exception);
			}
		}

		private IEnumerator CaptureLoop()
		{
			WaitForEndOfFrame endOfFrame = new WaitForEndOfFrame();
			while (_active)
			{
				yield return endOfFrame;
				long timestamp = Stopwatch.GetTimestamp();
				if (timestamp >= _nextCaptureTimestamp && (_settings.MaximumFrameCount <= 0 || _nextFrameNumber < _settings.MaximumFrameCount) && ScheduleReadback(_nextFrameNumber))
				{
					_nextFrameNumber++;
					_nextCaptureTimestamp += _captureIntervalTicks;
					if (_nextCaptureTimestamp < timestamp)
					{
						_nextCaptureTimestamp = timestamp + _captureIntervalTicks;
					}
				}
			}
		}

		private bool ScheduleReadback(int frameNumber)
		{
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			int num = Math.Min(16, Math.Max(2, _settings.EncoderThreadCount * 2));
			if (_pendingReadbacks >= num || _frames.Count >= _settings.MaximumQueuedFrames)
			{
				_droppedFrameCount++;
				return false;
			}
			if ((Object)(object)_readbackTarget != (Object)(object)_source)
			{
				if (_settings.FlipVertically)
				{
					Graphics.Blit((Texture)(object)_source, _readbackTarget, new Vector2(1f, -1f), new Vector2(0f, 1f));
				}
				else
				{
					Graphics.Blit((Texture)(object)_source, _readbackTarget);
				}
			}
			_pendingReadbacks++;
			AsyncGPUReadback.Request((Texture)(object)_readbackTarget, 0, (TextureFormat)4, (Action<AsyncGPUReadbackRequest>)delegate(AsyncGPUReadbackRequest request)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				CompleteReadback(request, frameNumber);
			});
			return true;
		}

		private void CompleteReadback(AsyncGPUReadbackRequest request, int frameNumber)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			_pendingReadbacks--;
			if (((AsyncGPUReadbackRequest)(ref request)).hasError)
			{
				_droppedFrameCount++;
				RecorderLog.WriteWarning("GPU readback failed for an image sequence frame.");
				return;
			}
			ImageFrame item = new ImageFrame(frameNumber, ((AsyncGPUReadbackRequest)(ref request)).GetData<byte>(0).ToArray());
			if (_frames.IsAddingCompleted || !_frames.TryAdd(item))
			{
				_droppedFrameCount++;
			}
		}

		private void WriteFrames()
		{
			try
			{
				foreach (ImageFrame item in _frames.GetConsumingEnumerable())
				{
					WriteFrame(item);
					Interlocked.Increment(ref _capturedFrameCount);
				}
			}
			catch (Exception ex)
			{
				RecorderLog.WriteError(ex);
				Interlocked.CompareExchange(ref _writerException, ex, null);
			}
		}

		private void WriteFrame(ImageFrame frame)
		{
			string arg = ((_settings.FileFormat == ImageSequenceFormat.Jpeg) ? "jpg" : "png");
			string path = Path.Combine(_settings.OutputDirectory, $"{_settings.FileNamePrefix}{frame.Number:D6}.{arg}");
			if (_settings.FileFormat == ImageSequenceFormat.Png)
			{
				FastPngEncoder.WriteRgba32(path, frame.Pixels, _settings.Width, _settings.Height);
				return;
			}
			byte[] bytes = ImageConversion.EncodeArrayToJPG((Array)frame.Pixels, (GraphicsFormat)4, (uint)_settings.Width, (uint)_settings.Height, 0u, _settings.JpegQuality);
			File.WriteAllBytes(path, bytes);
		}

		private void RestoreCameraAndReleaseTargets()
		{
			if ((Object)(object)_camera != (Object)null)
			{
				((Behaviour)_camera).enabled = _previousEnabled;
				_camera.targetTexture = _previousTarget;
			}
			if (_ownsReadbackTarget && (Object)(object)_readbackTarget != (Object)null)
			{
				_readbackTarget.Release();
				Object.Destroy((Object)(object)_readbackTarget);
			}
			if (_ownsSource && (Object)(object)_source != (Object)null)
			{
				_source.Release();
				Object.Destroy((Object)(object)_source);
			}
			_readbackTarget = null;
			_source = null;
			_camera = null;
		}

		private static RenderTexture CreateTarget(int width, int height, int antiAliasingSamples)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			RenderTexture val = new RenderTexture(width, height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2)
			{
				antiAliasing = antiAliasingSamples
			};
			val.Create();
			return val;
		}

		private static void ValidatePreparedTarget(RenderTexture target, int width, int height)
		{
			if ((Object)(object)target != (Object)null && (((Texture)target).width != width || ((Texture)target).height != height))
			{
				throw new ArgumentException("The prepared target does not match the image sequence dimensions.");
			}
		}
	}
	internal static class FastPngEncoder
	{
		private static readonly byte[] Signature = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };

		private static readonly uint[] CrcTable = CreateCrcTable();

		public static void WriteRgba32(string path, byte[] pixels, int width, int height)
		{
			int num;
			checked
			{
				num = width * 4;
				if (pixels == null || pixels.Length != num * height)
				{
					throw new ArgumentException("The RGBA buffer size does not match the PNG dimensions.", "pixels");
				}
			}
			using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read);
			fileStream.Write(Signature, 0, Signature.Length);
			byte[] array = new byte[13];
			WriteUInt32BigEndian(array, 0, (uint)width);
			WriteUInt32BigEndian(array, 4, (uint)height);
			array[8] = 8;
			array[9] = 6;
			WriteChunk(fileStream, "IHDR", array);
			WriteChunk(fileStream, "IDAT", CompressScanlines(pixels, num, height));
			WriteChunk(fileStream, "IEND", Array.Empty<byte>());
		}

		private static byte[] CompressScanlines(byte[] pixels, int rowBytes, int height)
		{
			using MemoryStream memoryStream = new MemoryStream();
			memoryStream.WriteByte(120);
			memoryStream.WriteByte(1);
			ulong num = 1uL;
			ulong num2 = 0uL;
			using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Fastest, leaveOpen: true))
			{
				for (int i = 0; i < height; i++)
				{
					deflateStream.WriteByte(0);
					num2 += num;
					int num3 = (height - 1 - i) * rowBytes;
					deflateStream.Write(pixels, num3, rowBytes);
					for (int j = 0; j < rowBytes; j++)
					{
						num += pixels[num3 + j];
						num2 += num;
					}
					num %= 65521;
					num2 %= 65521;
				}
			}
			WriteUInt32BigEndian(memoryStream, (uint)((num2 << 16) | num));
			return memoryStream.ToArray();
		}

		private static void WriteChunk(Stream output, string type, byte[] data)
		{
			byte[] bytes = Encoding.ASCII.GetBytes(type);
			WriteUInt32BigEndian(output, (uint)data.Length);
			output.Write(bytes, 0, bytes.Length);
			output.Write(data, 0, data.Length);
			uint crc = UpdateCrc(uint.MaxValue, bytes);
			crc = UpdateCrc(crc, data);
			WriteUInt32BigEndian(output, crc ^ 0xFFFFFFFFu);
		}

		private static uint UpdateCrc(uint crc, byte[] data)
		{
			foreach (byte b in data)
			{
				crc = CrcTable[(crc ^ b) & 0xFF] ^ (crc >> 8);
			}
			return crc;
		}

		private static uint[] CreateCrcTable()
		{
			uint[] array = new uint[256];
			for (uint num = 0u; num < array.Length; num++)
			{
				uint num2 = num;
				for (int i = 0; i < 8; i++)
				{
					num2 = (((num2 & 1) != 0) ? (0xEDB88320u ^ (num2 >> 1)) : (num2 >> 1));
				}
				array[num] = num2;
			}
			return array;
		}

		private static void WriteUInt32BigEndian(Stream output, uint value)
		{
			output.WriteByte((byte)(value >> 24));
			output.WriteByte((byte)(value >> 16));
			output.WriteByte((byte)(value >> 8));
			output.WriteByte((byte)value);
		}

		private static void WriteUInt32BigEndian(byte[] target, int offset, uint value)
		{
			target[offset] = (byte)(value >> 24);
			target[offset + 1] = (byte)(value >> 16);
			target[offset + 2] = (byte)(value >> 8);
			target[offset + 3] = (byte)value;
		}
	}
	internal sealed class NativeVideoCapture : VideoCaptureBackend
	{
		[UnmanagedFunctionPointer(CallingConvention.StdCall)]
		private delegate void PacketCallback(IntPtr data, int length, long timestampMicroseconds);

		private VideoCaptureContext _context;

		private RenderTexture _renderTarget;

		private RenderTexture _target;

		private PacketCallback _packetCallback;

		private IntPtr _renderEventFunction;

		private int _sessionId;

		private long _captureIntervalTicks;

		private long _nextCaptureTimestamp;

		private bool _active;

		private int _rejectedPackets;

		private Coroutine _captureCoroutine;

		private RenderTexture _screenTarget;

		private ScreenCursorOverlay _screenCursorOverlay;

		private VideoSequenceCompositor _videoSequenceCompositor;

		private string _diagnosticsJson;

		private VideoStreamFormat _streamFormat = VideoStreamFormat.H264;

		public override string DiagnosticsJson => _diagnosticsJson;

		public override string Name => "D3D11 NVENC";

		public override VideoStreamFormat StreamFormat => _streamFormat;

		public override void ConfigureStreamFormat(VideoStreamFormat format)
		{
			if (_sessionId != 0)
			{
				throw new InvalidOperationException("The video codec cannot change during capture.");
			}
			if (format != VideoStreamFormat.H264 && format != VideoStreamFormat.Hevc)
			{
				throw new NotSupportedException("The native backend supports only H.264 and HEVC.");
			}
			_streamFormat = format;
		}

		internal static bool IsAvailable()
		{
			try
			{
				return Direct3DVideoEncoderGetRenderEventFunction() != IntPtr.Zero;
			}
			catch (DllNotFoundException ex)
			{
				RecorderLog.WriteWarning("The native video encoder library was not found: " + ex.Message);
				return false;
			}
			catch (EntryPointNotFoundException ex2)
			{
				RecorderLog.WriteWarning("The native video encoder entry point was not found: " + ex2.Message);
				return false;
			}
			catch (BadImageFormatException ex3)
			{
				RecorderLog.WriteWarning("The native video encoder library has an incompatible format: " + ex3.Message);
				return false;
			}
		}

		public override void StartCapture(VideoCaptureContext context)
		{
			_diagnosticsJson = null;
			_context = context;
			if (context.FlipVertically)
			{
				_renderTarget = CreateTarget(context.Width, context.Height);
			}
			_target = CreateTarget(context.Width, context.Height);
			_packetCallback = ReceivePacket;
			_renderEventFunction = Direct3DVideoEncoderGetRenderEventFunction();
			int preset = (context.OptimizeForConcurrentEncoding ? 4 : context.QualityProfile.NativeEncodingPreset);
			_sessionId = Direct3DVideoEncoderStartWithConstantQPOptions(((Texture)_target).GetNativeTexturePtr(), context.Width, context.Height, context.MaximumFrameRate, preset, (int)_streamFormat, context.QualityProfile.QuantizationParameter, context.OptimizeForConcurrentEncoding ? 1 : 0, _packetCallback);
			if (_sessionId == 0)
			{
				throw new InvalidOperationException(GetNativeError(0));
			}
			_captureIntervalTicks = Math.Max(1L, Stopwatch.Frequency / context.MaximumFrameRate);
			_nextCaptureTimestamp = 0L;
			_videoSequenceCompositor = new VideoSequenceCompositor(context.VideoSequence, context.Width, context.Height, context.FlipVertically, Time.realtimeSinceStartup);
			_active = true;
			_captureCoroutine = ((MonoBehaviour)this).StartCoroutine(CaptureFramesAtEndOfFrame());
		}

		public override void StopCapture()
		{
			_active = false;
			if (_captureCoroutine != null)
			{
				((MonoBehaviour)this).StopCoroutine(_captureCoroutine);
				_captureCoroutine = null;
			}
			Direct3DVideoEncoderStop(_sessionId);
			_diagnosticsJson = Marshal.PtrToStringAnsi(Direct3DVideoEncoderGetTelemetry(_sessionId));
			RecorderLog.WriteInfo("NATIVE_PIPELINE " + _diagnosticsJson);
			string nativeError = GetNativeError(_sessionId);
			if (!string.IsNullOrEmpty(nativeError))
			{
				RecorderLog.WriteWarning("Native pipeline error: " + nativeError);
			}
			RecorderLog.WriteInfo($"Native capture frames: queued={Direct3DVideoEncoderGetQueuedFrameCount(_sessionId)}, " + $"encoded={Direct3DVideoEncoderGetEncodedFrameCount(_sessionId)}, " + $"dropped={Direct3DVideoEncoderGetDroppedFrameCount(_sessionId)}.");
			Direct3DVideoEncoderDestroy(_sessionId);
			_sessionId = 0;
			if (_rejectedPackets > 0)
			{
				RecorderLog.WriteWarning($"Native capture rejected {_rejectedPackets} encoded packets during shutdown.");
			}
			_packetCallback = null;
			if ((Object)(object)_renderTarget != (Object)null)
			{
				ReleaseTarget(_renderTarget);
				_renderTarget = null;
			}
			if ((Object)(object)_target != (Object)null)
			{
				ReleaseTarget(_target);
			}
			_target = null;
			if ((Object)(object)_screenTarget != (Object)null)
			{
				ReleaseTarget(_screenTarget);
				_screenTarget = null;
			}
			_screenCursorOverlay?.Dispose();
			_screenCursorOverlay = null;
			_videoSequenceCompositor?.Dispose();
			_videoSequenceCompositor = null;
		}

		private static RenderTexture CreateTarget(int width, int height)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			RenderTexture val = new RenderTexture(width, height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2);
			val.Create();
			return val;
		}

		private static void ReleaseTarget(RenderTexture target)
		{
			if ((Object)(object)RenderTexture.active == (Object)(object)target)
			{
				RenderTexture.active = null;
			}
			target.Release();
			Object.Destroy((Object)(object)target);
		}

		private IEnumerator CaptureFramesAtEndOfFrame()
		{
			while (_active)
			{
				yield return (object)new WaitForEndOfFrame();
				CaptureCompletedFrame();
			}
		}

		private void CaptureCompletedFrame()
		{
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			if (!_active)
			{
				return;
			}
			long timestamp = Stopwatch.GetTimestamp();
			if (_nextCaptureTimestamp != 0L && timestamp < _nextCaptureTimestamp)
			{
				return;
			}
			if (_nextCaptureTimestamp == 0L)
			{
				_nextCaptureTimestamp = timestamp;
			}
			if (_videoSequenceCompositor.RequiresScreen)
			{
				CaptureScreenFrame();
			}
			_videoSequenceCompositor.Render(_renderTarget ?? _target, _screenTarget, Time.realtimeSinceStartup);
			if ((Object)(object)_renderTarget != (Object)null)
			{
				if (_context.FlipVertically)
				{
					Graphics.Blit((Texture)(object)_renderTarget, _target, new Vector2(1f, -1f), new Vector2(0f, 1f));
				}
				else
				{
					Graphics.Blit((Texture)(object)_renderTarget, _target);
				}
			}
			long timestampMicroseconds = timestamp * 1000000 / Stopwatch.Frequency;
			Direct3DVideoEncoderQueueTexture(_sessionId, ((Texture)_target).GetNativeTexturePtr(), timestampMicroseconds);
			GL.IssuePluginEvent(_renderEventFunction, _sessionId);
			_nextCaptureTimestamp += _captureIntervalTicks;
			if (_nextCaptureTimestamp < timestamp - _captureIntervalTicks)
			{
				_nextCaptureTimestamp = timestamp + _captureIntervalTicks;
			}
		}

		private void CaptureScreenFrame()
		{
			if ((Object)(object)_screenTarget == (Object)null || ((Texture)_screenTarget).width != Screen.width || ((Texture)_screenTarget).height != Screen.height)
			{
				if ((Object)(object)_screenTarget != (Object)null)
				{
					_screenTarget.Release();
					Object.Destroy((Object)(object)_screenTarget);
				}
				_screenTarget = CreateTarget(Screen.width, Screen.height);
			}
			RenderTexture active = RenderTexture.active;
			RenderTexture.active = null;
			ScreenCapture.CaptureScreenshotIntoRenderTexture(_screenTarget);
			if (_videoSequenceCompositor.CapturesScreenCursor)
			{
				if (_screenCursorOverlay == null)
				{
					_screenCursorOverlay = new ScreenCursorOverlay();
				}
				_screenCursorOverlay.Draw(_screenTarget);
			}
			RenderTexture.active = active;
		}

		private void ReceivePacket(IntPtr data, int length, long timestampMicroseconds)
		{
			byte[] array = new byte[length];
			Marshal.Copy(data, array, 0, length);
			if (!_context.WritePacket(array, timestampMicroseconds))
			{
				_rejectedPackets++;
			}
		}

		private static string GetNativeError(int sessionId)
		{
			return Marshal.PtrToStringAnsi(Direct3DVideoEncoderGetLastError(sessionId)) ?? "Unknown native NVENC error.";
		}

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern int Direct3DVideoEncoderStartWithConstantQP(IntPtr texture, int width, int height, int frameRate, int codec, int quantizationParameter, PacketCallback callback);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern int Direct3DVideoEncoderStartWithConstantQPOptions(IntPtr texture, int width, int height, int frameRate, int preset, int codec, int quantizationParameter, int concurrentEncoding, PacketCallback callback);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern void Direct3DVideoEncoderQueueTexture(int sessionId, IntPtr texture, long timestampMicroseconds);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern IntPtr Direct3DVideoEncoderGetRenderEventFunction();

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern void Direct3DVideoEncoderStop(int sessionId);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern void Direct3DVideoEncoderDestroy(int sessionId);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern IntPtr Direct3DVideoEncoderGetLastError(int sessionId);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern ulong Direct3DVideoEncoderGetQueuedFrameCount(int sessionId);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern ulong Direct3DVideoEncoderGetEncodedFrameCount(int sessionId);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern ulong Direct3DVideoEncoderGetDroppedFrameCount(int sessionId);

		[DllImport("Direct3DVideoEncoder", CallingConvention = CallingConvention.StdCall)]
		private static extern IntPtr Direct3DVideoEncoderGetTelemetry(int sessionId);
	}
	internal sealed class ScreenCursorOverlay : IDisposable
	{
		private struct NativePoint
		{
			internal int X;

			internal int Y;
		}

		private struct NativeRectangle
		{
			internal int Left;

			internal int Top;

			internal int Right;

			internal int Bottom;
		}

		private struct NativeCursorInfo
		{
			internal int Size;

			internal int Flags;

			internal IntPtr Handle;

			internal NativePoint Position;
		}

		private const int CursorWidth = 32;

		private const int CursorHeight = 44;

		private const int CursorShowing = 1;

		private static readonly Vector2[] CursorShape = (Vector2[])(object)new Vector2[7]
		{
			new Vector2(1f, 1f),
			new Vector2(1f, 34f),
			new Vector2(10f, 26f),
			new Vector2(17f, 42f),
			new Vector2(24f, 39f),
			new Vector2(17f, 24f),
			new Vector2(30f, 24f)
		};

		private readonly Texture2D _texture;

		internal ScreenCursorOverlay()
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: 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_0026: Expected O, but got Unknown
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			_texture = new Texture2D(32, 44, (TextureFormat)4, false, true)
			{
				filterMode = (FilterMode)0,
				wrapMode = (TextureWrapMode)1
			};
			Color32[] array = (Color32[])(object)new Color32[1408];
			for (int i = 0; i < 44; i++)
			{
				for (int j = 0; j < 32; j++)
				{
					array[i * 32 + j] = ResolvePixel(j, i);
				}
			}
			_texture.SetPixels32(array);
			_texture.Apply(false, true);
		}

		internal void Draw(RenderTexture target)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			if (!TryGetCursorPosition(target, out var position, out var scale))
			{
				return;
			}
			RenderTexture active = RenderTexture.active;
			try
			{
				Graphics.SetRenderTarget(target);
				GL.PushMatrix();
				GL.LoadPixelMatrix(0f, (float)((Texture)target).width, (float)((Texture)target).height, 0f);
				float num = 44f * scale;
				Graphics.DrawTexture(new Rect(position.x, (float)((Texture)target).height - position.y - num, 32f * scale, num), (Texture)(object)_texture);
				GL.PopMatrix();
			}
			finally
			{
				RenderTexture.active = active;
			}
		}

		public void Dispose()
		{
			Object.Destroy((Object)(object)_texture);
		}

		private static Color32 ResolvePixel(int x, int y)
		{
			//IL_000d: 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_0059: Unknown result type (might be due to invalid IL or missing references)
			if (!IsInsideCursor(x, y))
			{
				return new Color32((byte)0, (byte)0, (byte)0, (byte)0);
			}
			if (IsInsideCursor(x - 1, y) && IsInsideCursor(x + 1, y) && IsInsideCursor(x, y - 1) && IsInsideCursor(x, y + 1))
			{
				return new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, byte.MaxValue);
			}
			return new Color32((byte)0, (byte)0, (byte)0, byte.MaxValue);
		}

		private static bool TryGetCursorPosition(RenderTexture target, out Vector2 position, out float scale)
		{
			//IL_0001: 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)
			position = default(Vector2);
			scale = 1f;
			NativeCursorInfo cursorInfo = new NativeCursorInfo
			{
				Size = Marshal.SizeOf<NativeCursorInfo>()
			};
			IntPtr activeWindow = GetActiveWindow();
			if (activeWindow == IntPtr.Zero || !GetCursorInfo(ref cursorInfo) || (cursorInfo.Flags & 1) == 0 || !ScreenToClient(activeWindow, ref cursorInfo.Position) || !GetClientRect(activeWindow, out var rectangle))
			{
				return false;
			}
			int num = rectangle.Right - rectangle.Left;
			int num2 = rectangle.Bottom - rectangle.Top;
			if (num <= 0 || num2 <= 0 || cursorInfo.Position.X < 0 || cursorInfo.Position.Y < 0 || cursorInfo.Position.X >= num || cursorInfo.Position.Y >= num2)
			{
				return false;
			}
			position = new Vector2((float)(cursorInfo.Position.X * ((Texture)target).width) / (float)num, (float)(cursorInfo.Position.Y * ((Texture)target).height) / (float)num2);
			scale = Mathf.Max(new float[3]
			{
				1f,
				(float)((Texture)target).width / (float)num,
				(float)((Texture)target).height / (float)num2
			});
			return true;
		}

		private static bool IsInsideCursor(int x, int y)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			bool flag = false;
			int num = 0;
			int num2 = CursorShape.Length - 1;
			while (num < CursorShape.Length)
			{
				Vector2 val = CursorShape[num];
				Vector2 val2 = CursorShape[num2];
				if (val.y > (float)y != val2.y > (float)y && (float)x < (val2.x - val.x) * ((float)y - val.y) / (val2.y - val.y) + val.x)
				{
					flag = !flag;
				}
				num2 = num++;
			}
			return flag;
		}

		[DllImport("user32.dll")]
		private static extern IntPtr GetActiveWindow();

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool GetCursorInfo(ref NativeCursorInfo cursorInfo);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool ScreenToClient(IntPtr window, ref NativePoint point);

		[DllImport("user32.dll")]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool GetClientRect(IntPtr window, out NativeRectangle rectangle);
	}
	internal sealed class UnityAudioCapture : MonoBehaviour
	{
		private Func<byte[], bool> _writeAudio;

		private volatile int _channels;

		internal int Channels => _channels;

		internal int SampleRate => AudioSettings.outputSampleRate;

		internal bool IsReady => _channels > 0;

		internal void Initialize(Func<byte[], bool> writeAudio)
		{
			_writeAudio = writeAudio;
		}

		private void OnAudioFilterRead(float[] data, int channels)
		{
			_channels = channels;
			if (_writeAudio == null)
			{
				return;
			}
			try
			{
				byte[] array = new byte[data.Length * 4];
				Buffer.BlockCopy(data, 0, array, 0, array.Length);
				_writeAudio(array);
			}
			catch (Exception exception)
			{
				RecorderLog.WriteError(exception);
			}
		}
	}
	internal abstract class VideoCaptureBackend : MonoBehaviour
	{
		public abstract string Name { get; }

		public abstract VideoStreamFormat StreamFormat { get; }

		public virtual string DiagnosticsJson => null;

		public virtual void ConfigureStreamFormat(VideoStreamFormat format)
		{
			if (format != StreamFormat)
			{
				throw new NotSupportedException("This video backend does not support the requested codec.");
			}
		}

		public abstract void StartCapture(VideoCaptureContext context);

		public abstract void StopCapture();
	}
	internal static class VideoCaptureBackendRegistry
	{
		private sealed class Registration
		{
			internal int Priority;

			internal Func<GameObject, VideoCaptureBackend> Factory;
		}

		private static readonly List<Registration> Registrations;

		static VideoCaptureBackendRegistry()
		{
			Registrations = new List<Registration>();
			Register(CreateNativeBackend, 100);
		}

		public static void Register(Func<GameObject, VideoCaptureBackend> factory, int priority = 0)
		{
			if (factory == null)
			{
				throw new ArgumentNullException("factory");
			}
			Registrations.Add(new Registration
			{
				Priority = priority,
				Factory = factory
			});
			Registrations.Sort((Registration left, Registration right) => right.Priority.CompareTo(left.Priority));
		}

		internal static VideoCaptureBackend Create(GameObject host)
		{
			foreach (Registration registration in Registrations)
			{
				VideoCaptureBackend videoCaptureBackend = registration.Factory(host);
				if ((Object)(object)videoCaptureBackend != (Object)null)
				{
					return videoCaptureBackend;
				}
			}
			throw new NotSupportedException("No compatible video encoder is available. The built-in encoder requires Windows, Direct3D 11, an NVIDIA GPU and Direct3DVideoEncoder.dll. Image-sequence capture remains available.");
		}

		private static VideoCaptureBackend CreateNativeBackend(GameObject host)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Invalid comparison between Unknown and I4
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Invalid comparison between Unknown and I4
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			if (((int)Application.platform != 2 && (int)Application.platform != 7) || (int)SystemInfo.graphicsDeviceType != 2 || SystemInfo.graphicsDeviceVendor.IndexOf("NVIDIA", StringComparison.OrdinalIgnoreCase) < 0 || !NativeVideoCapture.IsAvailable())
			{
				return null;
			}
			return host.AddComponent<NativeVideoCapture>();
		}
	}
	internal sealed class VideoCaptureContext
	{
		private readonly Func<byte[], long, bool> _writePacket;

		public int Width { get; }

		public int Height { get; }

		public int MaximumFrameRate { get; }

		public RecordingQualityProfile QualityProfile { get; }

		public VideoSequenceSettings VideoSequence { get; }

		public bool OptimizeForConcurrentEncoding { get; }

		public bool FlipVertically { get; }

		internal VideoCaptureContext(int width, int height, int maximumFrameRate, RecordingQualityProfile qualityProfile, VideoSequenceSettings videoSequence, bool optimizeForConcurrentEncoding, bool flipVertically, Func<byte[], long, bool> writePacket)
		{
			Width = width;
			Height = height;
			MaximumFrameRate = maximumFrameRate;
			QualityProfile = qualityProfile;
			VideoSequence = videoSequence;
			OptimizeForConcurrentEncoding = optimizeForConcurrentEncoding;
			FlipVertically = flipVertically;
			_writePacket = writePacket;
		}

		public bool WritePacket(byte[] data, long timestampMicroseconds)
		{
			return _writePacket(data, timestampMicroseconds);
		}
	}
	internal sealed class VideoSequenceCompositor : IDisposable
	{
		private readonly VideoSequenceSettings _settings;

		private readonly IReadOnlyList<VideoSequenceSource> _sources;

		private readonly Random _random;

		private readonly List<int> _randomOrder = new List<int>();

		private readonly RenderTexture _firstResolved;

		private readonly RenderTexture _secondResolved;

		private readonly Material _crossFadeMaterial;

		private readonly bool _preFlipScreen;

		private int _currentIndex;

		private int _nextIndex = -1;

		private float _shotEndTime;

		private float _transitionStartTime;

		private VideoSequenceTransition _activeTransition;

		internal bool RequiresScreen { get; private set; }

		internal bool CapturesScreenCursor { get; private set; }

		private int SourceCount => _sources.Count;

		internal VideoSequenceCompositor(VideoSequenceSettings settings, int width, int height, bool flipVertically, float startTime)
		{
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			_settings = settings;
			_sources = settings.Sources;
			RequiresScreen = ContainsScreenSource(_sources);
			CapturesScreenCursor = ResolveScreenCursorSetting(_sources);
			ValidateCameraSources();
			_preFlipScreen = flipVertically;
			_random = (settings.RandomSeed.HasValue ? new Random(settings.RandomSeed.Value) : new Random());
			if (SourceCount > 1 && ContainsCrossFade(settings.Transitions))
			{
				Shader val = Shader.Find("UnityRuntimeCameraRecorder/CrossFade");
				if ((Object)(object)val == (Object)null)
				{