Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SagaCapture v1.0.1
Landoria.SagaCapture.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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) {