Decompiled source of Discord GIFs v0.2.1

BepInEx/plugins/Hugo-DiscordClips/DiscordClips.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ValheimDiscordGifs
{
	internal static class CodecInstaller
	{
		internal const string DownloadUrl = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip";

		internal static async Task Install(HttpClient client, string directory, Action<string> status, CancellationToken token)
		{
			string destination = Path.Combine(directory, "ffmpeg.exe");
			if (File.Exists(destination))
			{
				return;
			}
			string staging = Path.Combine(directory, "codec-download-" + Guid.NewGuid().ToString("N"));
			Directory.CreateDirectory(staging);
			try
			{
				status("Preparando o download do codificador...");
				string expected;
				using (HttpResponseMessage response = await client.GetAsync("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip.sha256", token).ConfigureAwait(continueOnCapturedContext: false))
				{
					response.EnsureSuccessStatusCode();
					expected = Regex.Match(await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false), "\\b[0-9a-fA-F]{64}\\b").Value;
					if (expected.Length != 64)
					{
						throw new InvalidDataException("Checksum indisponivel.");
					}
				}
				string zipPath = Path.Combine(staging, "ffmpeg.zip");
				using (HttpResponseMessage response = await client.GetAsync("https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip", HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(continueOnCapturedContext: false))
				{
					response.EnsureSuccessStatusCode();
					long total = response.Content.Headers.ContentLength.GetValueOrDefault();
					if (total > 300000000)
					{
						throw new InvalidDataException("Download inesperadamente grande.");
					}
					using Stream input = await response.Content.ReadAsStreamAsync().ConfigureAwait(continueOnCapturedContext: false);
					using FileStream output = File.Create(zipPath);
					byte[] buffer = new byte[81920];
					long received = 0L;
					DateTime next = DateTime.MinValue;
					int num;
					while ((num = await input.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(continueOnCapturedContext: false)) > 0)
					{
						received += num;
						if (received > 300000000)
						{
							throw new InvalidDataException("Download inesperadamente grande.");
						}
						await output.WriteAsync(buffer, 0, num, token).ConfigureAwait(continueOnCapturedContext: false);
						if (DateTime.UtcNow >= next)
						{
							status("Baixando codificador: " + received / 1000000 + ((total > 0) ? (" / " + total / 1000000) : "") + " MB. Pode continuar no menu.");
							next = DateTime.UtcNow.AddSeconds(1.0);
						}
					}
				}
				token.ThrowIfCancellationRequested();
				status("Verificando e preparando o codificador...");
				using (FileStream inputStream = File.OpenRead(zipPath))
				{
					using SHA256 sHA = SHA256.Create();
					if (!BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").Equals(expected, StringComparison.OrdinalIgnoreCase))
					{
						throw new InvalidDataException("SHA256 nao confere. Tente novamente.");
					}
				}
				using ZipArchive archive = ZipFile.OpenRead(zipPath);
				ZipArchiveEntry zipArchiveEntry = archive.Entries.SingleOrDefault((ZipArchiveEntry e) => e.FullName.Replace('\\', '/').EndsWith("/bin/ffmpeg.exe", StringComparison.OrdinalIgnoreCase));
				if (zipArchiveEntry == null || zipArchiveEntry.Length < 2 || zipArchiveEntry.Length > 300000000)
				{
					throw new InvalidDataException("Codificador ausente no ZIP.");
				}
				string exe = Path.Combine(staging, "ffmpeg.exe");
				await Extract(zipArchiveEntry, exe, 300000000L, token).ConfigureAwait(continueOnCapturedContext: false);
				using (FileStream fileStream = File.OpenRead(exe))
				{
					if (fileStream.ReadByte() != 77 || fileStream.ReadByte() != 90)
					{
						throw new InvalidDataException("Executavel invalido.");
					}
				}
				foreach (ZipArchiveEntry entry in archive.Entries)
				{
					string text = entry.FullName.Replace('\\', '/').Split('/').Last();
					if (text.Equals("LICENSE", StringComparison.OrdinalIgnoreCase) || text.Equals("LICENSE.txt", StringComparison.OrdinalIgnoreCase) || text.Equals("COPYING", StringComparison.OrdinalIgnoreCase) || text.Equals("README.txt", StringComparison.OrdinalIgnoreCase))
					{
						string path = Path.Combine(directory, "FFmpeg-" + text);
						if (!File.Exists(path))
						{
							await Extract(entry, path, 1000000L, token).ConfigureAwait(continueOnCapturedContext: false);
						}
					}
				}
				File.WriteAllText(Path.Combine(directory, "FFmpeg-origem.txt"), "Download: https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip\r\nSHA256: " + expected + "\r\nFornecedor e licencas: https://www.gyan.dev/ffmpeg/builds/\r\nCodigo-fonte: https://ffmpeg.org/download.html\r\n");
				token.ThrowIfCancellationRequested();
				if (!File.Exists(destination))
				{
					File.Move(exe, destination);
				}
			}
			finally
			{
				string value = Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
				if (Path.GetFullPath(staging).StartsWith(value, StringComparison.OrdinalIgnoreCase) && Path.GetFileName(staging).StartsWith("codec-download-"))
				{
					try
					{
						Directory.Delete(staging, recursive: true);
					}
					catch
					{
					}
				}
			}
		}

		private static async Task Extract(ZipArchiveEntry entry, string path, long limit, CancellationToken token)
		{
			if (entry.Length > limit)
			{
				throw new InvalidDataException("Entrada muito grande.");
			}
			using Stream source = entry.Open();
			using FileStream target = File.Create(path);
			byte[] buffer = new byte[81920];
			long count = 0L;
			int num;
			while ((num = await source.ReadAsync(buffer, 0, buffer.Length, token).ConfigureAwait(continueOnCapturedContext: false)) > 0)
			{
				count += num;
				if (count > limit)
				{
					throw new InvalidDataException("Entrada muito grande.");
				}
				await target.WriteAsync(buffer, 0, num, token).ConfigureAwait(continueOnCapturedContext: false);
			}
		}
	}
	internal static class ConfigMigration
	{
		public static string Clean(string text)
		{
			if (!text.Contains("[02 - Video]") && !text.Contains("[03 - Audio]") && !text.Contains("[04 - Discord]") && !text.Contains("[05 - Arquivos]") && !text.Contains("[06 - Atualizacoes]"))
			{
				return text;
			}
			Dictionary<string, string> dictionary = new Dictionary<string, string>
			{
				{ "Enabled", "01 - Geral" },
				{ "CaptureOnDeath", "01 - Geral" },
				{ "CaptureKey", "01 - Geral" },
				{ "FlipVertically", "02 - GIF" },
				{ "OnlyWhenFocused", "02 - GIF" },
				{ "FramesPerSecond", "02 - GIF" },
				{ "CaptureHeight", "02 - GIF" },
				{ "BufferJpegQuality", "02 - GIF" },
				{ "BufferMemoryMB", "02 - GIF" },
				{ "WebhookUrl", "03 - Discord" },
				{ "MaxUploadMB", "03 - Discord" },
				{ "CooldownSeconds", "03 - Discord" },
				{ "WebhookName", "03 - Discord" },
				{ "DeathMessage", "03 - Discord" },
				{ "ManualMessage", "03 - Discord" },
				{ "ManualName", "05 - Discord manual" },
				{ "ManualAvatarUrl", "05 - Discord manual" },
				{ "ManualFooter", "05 - Discord manual" },
				{ "DeathName", "06 - Discord morte" },
				{ "DeathAvatarUrl", "06 - Discord morte" },
				{ "DeathFooter", "06 - Discord morte" },
				{ "FfmpegPath", "04 - Arquivos" },
				{ "AutoDownloadEncoder", "04 - Arquivos" },
				{ "ClipsDirectory", "04 - Arquivos" }
			};
			Dictionary<string, string> dictionary2 = new Dictionary<string, string>();
			string text2 = "";
			string[] array = text.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = array[i].Trim();
				if (text3.StartsWith("[") && text3.EndsWith("]"))
				{
					text2 = text3.Substring(1, text3.Length - 2);
				}
				else
				{
					if (text3.StartsWith("#") || text3.StartsWith(";"))
					{
						continue;
					}
					int num = text3.IndexOf('=');
					if (num >= 0)
					{
						string text4 = text3.Substring(0, num).Trim();
						if (dictionary.ContainsKey(text4) && (!(text2 == "02 - Video") || !(text4 != "FlipVertically") || !(text4 != "OnlyWhenFocused")) && !(text2 == "03 - Audio") && !(text2 == "06 - Atualizacoes"))
						{
							dictionary2[text4] = text3.Substring(num + 1).Trim();
						}
					}
				}
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (IGrouping<string, KeyValuePair<string, string>> item in from x in dictionary
				group x by x.Value)
			{
				stringBuilder.AppendLine("[" + item.Key + "]");
				foreach (KeyValuePair<string, string> item2 in item)
				{
					if (dictionary2.ContainsKey(item2.Key))
					{
						stringBuilder.AppendLine(item2.Key + " = " + dictionary2[item2.Key]);
					}
				}
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString();
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public string DispName { get; set; }

		public int Order { get; set; }

		public bool? IsAdvanced { get; set; }

		public bool? ShowRangeAsPercent { get; set; }

		public Action<ConfigEntryBase> CustomDrawer { get; set; }
	}
	public sealed class GifFrame
	{
		public double Time;

		public byte[] Jpeg;

		public int Width;

		public int Height;
	}
	public sealed class FrameRing
	{
		private readonly object gate = new object();

		private readonly LinkedList<GifFrame> frames = new LinkedList<GifFrame>();

		private long bytes;

		public void Add(GifFrame frame, double seconds, long byteLimit)
		{
			lock (gate)
			{
				if (frames.Last != null && (frame.Time <= frames.Last.Value.Time || frame.Width != frames.Last.Value.Width || frame.Height != frames.Last.Value.Height))
				{
					frames.Clear();
					bytes = 0L;
				}
				frames.AddLast(frame);
				bytes += frame.Jpeg.Length;
				while (frames.Count > 1 && (frames.First.Next.Value.Time < frame.Time - seconds || bytes > byteLimit))
				{
					bytes -= frames.First.Value.Jpeg.Length;
					frames.RemoveFirst();
				}
			}
		}

		public GifFrame[] Snapshot()
		{
			lock (gate)
			{
				return frames.ToArray();
			}
		}

		public void Clear()
		{
			lock (gate)
			{
				frames.Clear();
				bytes = 0L;
			}
		}
	}
	public static class GifMath
	{
		public static int[] Resample(GifFrame[] frames, double start, double duration, int fps)
		{
			if (frames.Length == 0 || fps < 1 || duration <= 0.0)
			{
				throw new ArgumentException("Empty clip");
			}
			int[] array = new int[(int)Math.Ceiling(duration * (double)fps)];
			int i = 0;
			for (int j = 0; j < array.Length; j++)
			{
				for (double num = start + (double)j / (double)fps; i + 1 < frames.Length && frames[i + 1].Time <= num; i++)
				{
				}
				array[j] = i;
			}
			return array;
		}

		public static string Number(double value)
		{
			return value.ToString("0.######", CultureInfo.InvariantCulture);
		}

		public static string Json(string text)
		{
			StringBuilder stringBuilder = new StringBuilder("\"");
			string text2 = text ?? "";
			foreach (char c in text2)
			{
				if (c == '"' || c == '\\')
				{
					stringBuilder.Append('\\');
					stringBuilder.Append(c);
				}
				else if (c < ' ')
				{
					int num = c;
					stringBuilder.Append("\\u" + num.ToString("x4"));
				}
				else
				{
					stringBuilder.Append(c);
				}
			}
			return stringBuilder.Append('"').ToString();
		}

		public static Uri Webhook(string text)
		{
			if (!Uri.TryCreate(text, UriKind.Absolute, out Uri result) || result.Scheme != "https" || (!(result.Host == "discord.com") && !(result.Host == "canary.discord.com") && !(result.Host == "ptb.discord.com")) || !result.IsDefaultPort || !string.IsNullOrEmpty(result.UserInfo) || !string.IsNullOrEmpty(result.Fragment))
			{
				throw new ArgumentException("Webhook invalido: use um webhook HTTPS do Discord.");
			}
			string[] array = result.AbsolutePath.Trim('/').Split('/');
			int num = 1;
			if (array.Length > 1 && (array[1] == "v10" || array[1] == "v9"))
			{
				num++;
			}
			if (array.Length != num + 3 || array[0] != "api" || array[num] != "webhooks" || !ulong.TryParse(array[num + 1], out var _) || array[num + 2].Length < 20)
			{
				throw new ArgumentException("URL de webhook incompleta.");
			}
			IEnumerable<string> first = from x in result.Query.TrimStart('?').Split('&')
				where x.Length > 0 && !x.StartsWith("wait=", StringComparison.OrdinalIgnoreCase)
				select x;
			UriBuilder uriBuilder = new UriBuilder(result);
			uriBuilder.Query = string.Join("&", first.Concat(new string[1] { "wait=true" }));
			return uriBuilder.Uri;
		}
	}
	internal sealed class GifExportJob
	{
		public GifFrame[] Frames;

		public double Start;

		public double Duration;

		public int Fps;

		public long Limit;

		public bool Flip;

		public string Ffmpeg;

		public string OutputDirectory;

		public string Webhook;

		public string Player;

		public string Reason;

		public string WebhookName;

		public string MessageTemplate;

		public string AvatarUrl;

		public string Footer;
	}
	internal static class DiscordUpload
	{
		public static string SelectName(string specific, string fallback)
		{
			if (!string.IsNullOrWhiteSpace(specific))
			{
				return specific;
			}
			return fallback;
		}

		private static string Shorten(string value, int limit)
		{
			if (value.Length <= limit)
			{
				return value;
			}
			int num = limit;
			if (char.IsHighSurrogate(value[num - 1]))
			{
				num--;
			}
			return value.Substring(0, num);
		}

		private static string BoldPlayer(string name)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (char c in name)
			{
				if (c == '\r' || c == '\n')
				{
					stringBuilder.Append(' ');
					continue;
				}
				if ("\\*_~`|>".IndexOf(c) >= 0)
				{
					stringBuilder.Append('\\');
				}
				stringBuilder.Append(c);
			}
			return "**" + stringBuilder?.ToString() + "**";
		}

		internal static string Payload(string file, string player, string reason, string webhookName, string messageTemplate, string avatarUrl, string footer)
		{
			string text = Shorten(string.IsNullOrWhiteSpace(player) ? "Viking" : player, 80);
			string text2 = Shorten((messageTemplate ?? ((reason == "morte") ? "{player} morreu em Valheim." : "{player} salvou um GIF de Valheim.")).Replace("**{player}**", "{player}").Replace("{player}", BoldPlayer(text)), 1900);
			string text3 = Shorten(string.IsNullOrWhiteSpace(webhookName) ? "Valheim GIFs" : webhookName.Trim(), 80);
			StringBuilder stringBuilder = new StringBuilder("{\"username\":" + GifMath.Json(text3) + ",\"content\":" + GifMath.Json(text2) + ",\"allowed_mentions\":{\"parse\":[]}");
			if (Uri.TryCreate((avatarUrl ?? "").Trim(), UriKind.Absolute, out Uri result) && (result.Scheme == "https" || result.Scheme == "http") && string.IsNullOrEmpty(result.UserInfo))
			{
				stringBuilder.Append(",\"avatar_url\":").Append(GifMath.Json(result.AbsoluteUri));
			}
			if (!string.IsNullOrWhiteSpace(footer))
			{
				string text4 = Shorten(footer.Replace("{player}", text), 2048);
				stringBuilder.Append(",\"embeds\":[{\"image\":{\"url\":").Append(GifMath.Json("attachment://" + Path.GetFileName(file))).Append("},\"footer\":{\"text\":")
					.Append(GifMath.Json(text4))
					.Append("}}]");
			}
			return stringBuilder.Append('}').ToString();
		}

		public static async Task<int> Send(HttpClient client, Uri webhook, string file, long limit, string player, string reason, CancellationToken token, string webhookName = "Valheim GIFs", string messageTemplate = null, string avatarUrl = null, string footer = null)
		{
			if (new FileInfo(file).Length > limit)
			{
				return 413;
			}
			for (int attempt = 0; attempt < 3; attempt++)
			{
				using MultipartFormDataContent form = new MultipartFormDataContent();
				using FileStream stream = File.OpenRead(file);
				string content = Payload(file, player, reason, webhookName, messageTemplate, avatarUrl, footer);
				form.Add(new StringContent(content, Encoding.UTF8, "application/json"), "payload_json");
				StreamContent streamContent = new StreamContent(stream);
				streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/gif");
				form.Add(streamContent, "files[0]", Path.GetFileName(file));
				using HttpResponseMessage response = await client.PostAsync(webhook, form, token).ConfigureAwait(continueOnCapturedContext: false);
				int statusCode = (int)response.StatusCode;
				if (response.IsSuccessStatusCode)
				{
					return statusCode;
				}
				if (statusCode != 429 || attempt == 2)
				{
					return statusCode;
				}
				RetryConditionHeaderValue retryAfter = response.Headers.RetryAfter;
				double num = ((retryAfter != null && retryAfter.Delta.HasValue) ? retryAfter.Delta.Value.TotalSeconds : 5.0);
				if (num > 60.0)
				{
					return statusCode;
				}
				await Task.Delay(TimeSpan.FromSeconds(Math.Max(1.0, num)), token).ConfigureAwait(continueOnCapturedContext: false);
			}
			return 429;
		}
	}
	internal static class GifExporter
	{
		public static async Task Run(GifExportJob job, Action<string> notify, CancellationToken token)
		{
			Directory.CreateDirectory(job.OutputDirectory);
			string stem = DateTime.Now.ToString("yyyyMMdd-HHmmss-fff") + "-" + Guid.NewGuid().ToString("N").Substring(0, 6);
			string work = Path.Combine(job.OutputDirectory, "pending-" + stem);
			Directory.CreateDirectory(work);
			string output = Path.Combine(job.OutputDirectory, stem + ".gif");
			bool success = false;
			try
			{
				File.WriteAllText(Path.Combine(work, "RECUPERAR.txt"), "Captura nao finalizada. Quadros JPEG preservados.\nFPS=" + job.Fps + "\nDuracao=" + GifMath.Number(job.Duration));
				int[] array = GifMath.Resample(job.Frames, job.Start, job.Duration, job.Fps);
				for (int i = 0; i < array.Length; i++)
				{
					token.ThrowIfCancellationRequested();
					File.WriteAllBytes(Path.Combine(work, "frame" + i.ToString("D5") + ".jpg"), job.Frames[array[i]].Jpeg);
				}
				if (!File.Exists(job.Ffmpeg))
				{
					notify("FFmpeg ausente. Captura preservada em " + work + ". Reinicie o jogo para preparar o codificador automaticamente.");
					return;
				}
				notify("Montando GIF de " + job.Duration.ToString("0") + " segundos...");
				File.Copy(await MakeGif(job, work, job.Limit, token).ConfigureAwait(continueOnCapturedContext: false), output, overwrite: false);
				success = true;
				if (string.IsNullOrWhiteSpace(job.Webhook))
				{
					notify("GIF salvo localmente. Configure WebhookUrl para enviar: " + output);
					return;
				}
				Uri uri;
				try
				{
					uri = GifMath.Webhook(job.Webhook);
				}
				catch (ArgumentException)
				{
					notify("Webhook invalido. GIF salvo: " + output);
					return;
				}
				using HttpClientHandler handler = new HttpClientHandler
				{
					AllowAutoRedirect = false
				};
				using HttpClient client = new HttpClient(handler)
				{
					Timeout = TimeSpan.FromSeconds(90.0)
				};
				int num = await DiscordUpload.Send(client, uri, output, job.Limit, job.Player, job.Reason, token, job.WebhookName, job.MessageTemplate, job.AvatarUrl, job.Footer).ConfigureAwait(continueOnCapturedContext: false);
				if (num == 413)
				{
					notify("Discord recusou o tamanho. Tentando uma versao menor...");
					long reduced = Math.Max(500000L, Math.Min(job.Limit / 2, new FileInfo(output).Length / 2));
					string sourceFileName = await MakeGif(job, work, reduced, token).ConfigureAwait(continueOnCapturedContext: false);
					string text = Path.Combine(job.OutputDirectory, stem + "-menor.gif");
					File.Copy(sourceFileName, text, overwrite: false);
					num = await DiscordUpload.Send(client, uri, text, reduced, job.Player, job.Reason, token, job.WebhookName, job.MessageTemplate, job.AvatarUrl, job.Footer).ConfigureAwait(continueOnCapturedContext: false);
				}
				notify((num >= 200 && num < 300) ? "GIF enviado ao Discord e salvo no PC." : ("Discord retornou HTTP " + num + ". GIF salvo: " + output));
			}
			catch (OperationCanceledException)
			{
				notify(success ? "Envio cancelado; GIF salvo no PC." : ("Exportacao interrompida; captura preservada em " + work));
			}
			catch (Exception ex3)
			{
				notify("Nao foi possivel finalizar (" + ex3.GetType().Name + "). " + (success ? ("GIF salvo: " + output) : ("Captura preservada: " + work)));
			}
			finally
			{
				if (success)
				{
					try
					{
						string value = Path.GetFullPath(job.OutputDirectory).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
						if (Path.GetFullPath(work).StartsWith(value, StringComparison.OrdinalIgnoreCase) && Path.GetFileName(work) == "pending-" + stem)
						{
							Directory.Delete(work, recursive: true);
						}
					}
					catch
					{
					}
				}
			}
		}

		private static async Task<string> MakeGif(GifExportJob job, string work, long limit, CancellationToken token)
		{
			int[] widths = new int[4] { 480, 360, 240, 160 };
			string target = Path.Combine(work, "preview.gif");
			for (int i = 0; i < widths.Length; i++)
			{
				int num = Math.Min(widths[i], job.Frames[0].Width);
				string text = (job.Flip ? "vflip," : "") + "fps=10,scale=" + num + ":-2:flags=lanczos,split[a][b];[a]palettegen=max_colors=96[p];[b][p]paletteuse=dither=bayer";
				string args = "-hide_banner -nostdin -y -loglevel warning -framerate " + job.Fps + " -i frame%05d.jpg -filter_complex \"" + text + "\" -an -t " + GifMath.Number(job.Duration) + " -loop 0 preview.gif";
				if (await Process(work, job.Ffmpeg, args, token).ConfigureAwait(continueOnCapturedContext: false) != 0)
				{
					throw new InvalidOperationException("Falha ao gerar GIF.");
				}
				long length = new FileInfo(target).Length;
				if (length > 0 && length <= limit)
				{
					return target;
				}
			}
			throw new InvalidOperationException("GIF excede limite.");
		}

		private static async Task<int> Process(string directory, string exe, string args, CancellationToken token)
		{
			using Process p = new Process();
			p.StartInfo = new ProcessStartInfo(exe, args)
			{
				WorkingDirectory = directory,
				UseShellExecute = false,
				CreateNoWindow = true,
				WindowStyle = ProcessWindowStyle.Hidden,
				RedirectStandardError = true
			};
			p.Start();
			Task<string> stderr = p.StandardError.ReadToEndAsync();
			Stopwatch watch = Stopwatch.StartNew();
			try
			{
				while (!p.WaitForExit(0))
				{
					token.ThrowIfCancellationRequested();
					if (watch.Elapsed.TotalSeconds > 120.0)
					{
						throw new TimeoutException();
					}
					await Task.Delay(100, token).ConfigureAwait(continueOnCapturedContext: false);
				}
				string path = Path.Combine(directory, "encoder.log");
				File.AppendAllText(path, await stderr.ConfigureAwait(continueOnCapturedContext: false));
				return p.ExitCode;
			}
			finally
			{
				if (!p.HasExited)
				{
					try
					{
						p.Kill();
					}
					catch
					{
					}
				}
			}
		}
	}
	[BepInPlugin("hugo.valheim.discordclips", "Discord GIFs", "0.2.1")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Player), "OnDeath")]
		private static class DeathHook
		{
			private static void Prefix(Player __instance)
			{
				if ((Object)(object)instance != (Object)null && instance.death.Value && instance.recording && !instance.deathPending && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && !((Character)__instance).IsDead())
				{
					((MonoBehaviour)instance).StartCoroutine(instance.CaptureAfterDeath(__instance));
				}
			}
		}

		public const string Id = "hugo.valheim.discordclips";

		public const string Version = "0.2.1";

		private static Plugin instance;

		private readonly FrameRing ring = new FrameRing();

		private readonly object captureGate = new object();

		private readonly ConcurrentQueue<string> messages = new ConcurrentQueue<string>();

		private readonly CancellationTokenSource lifetime = new CancellationTokenSource();

		private Harmony harmony;

		private ConfigEntry<bool> enabledCapture;

		private ConfigEntry<bool> death;

		private ConfigEntry<bool> flip;

		private ConfigEntry<bool> focusOnly;

		private ConfigEntry<bool> autoDownload;

		private volatile string codecStatus = "";

		private int installing;

		private bool installAttempted;

		private ConfigEntry<int> fps;

		private ConfigEntry<int> height;

		private ConfigEntry<int> memory;

		private ConfigEntry<int> jpeg;

		private ConfigEntry<float> maxMb;

		private ConfigEntry<float> cooldown;

		private ConfigEntry<string> webhook;

		private ConfigEntry<string> ffmpeg;

		private ConfigEntry<string> output;

		private ConfigEntry<string> webhookName;

		private ConfigEntry<string> deathMessage;

		private ConfigEntry<string> manualMessage;

		private ConfigEntry<string> manualName;

		private ConfigEntry<string> deathName;

		private ConfigEntry<string> manualAvatar;

		private ConfigEntry<string> deathAvatar;

		private ConfigEntry<string> manualFooter;

		private ConfigEntry<string> deathFooter;

		private ConfigEntry<KeyboardShortcut> hotkey;

		private RenderTexture screen;

		private RenderTexture scaled;

		private int pending;

		private int exporting;

		private int generation;

		private bool recording;

		private bool closed;

		private bool warnedMissing;

		private bool deathPending;

		private double nextFrame;

		private double lastTrigger = -1000.0;

		private string directory;

		private void MigrateConfiguration()
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			if (!File.Exists(configFilePath))
			{
				return;
			}
			string text = File.ReadAllText(configFilePath);
			string text2 = ConfigMigration.Clean(text);
			if (!(text2 == text))
			{
				string text3 = configFilePath + ".before-gif-menu.bak";
				if (!File.Exists(text3))
				{
					File.Copy(configFilePath, text3);
				}
				File.WriteAllText(configFilePath, text2);
				((BaseUnityPlugin)this).Config.Reload();
			}
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T value, string description)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			return Bind(section, key, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T value, ConfigDescription description)
		{
			//IL_0252: Unknown result type (might be due to invalid IL or missing references)
			//IL_025c: Expected O, but got Unknown
			string[] array = new string[24]
			{
				"Enabled", "CaptureOnDeath", "CaptureKey", "FramesPerSecond", "CaptureHeight", "BufferJpegQuality", "BufferMemoryMB", "FlipVertically", "OnlyWhenFocused", "WebhookUrl",
				"MaxUploadMB", "CooldownSeconds", "FfmpegPath", "AutoDownloadEncoder", "ClipsDirectory", "WebhookName", "DeathMessage", "ManualMessage", "ManualName", "DeathName",
				"ManualAvatarUrl", "DeathAvatarUrl", "ManualFooter", "DeathFooter"
			};
			string[] array2 = new string[24]
			{
				"Ativar captura", "Capturar ao morrer (espera 2 s)", "Tecla para salvar GIF", "FPS da captura", "Altura da captura", "Qualidade do buffer", "Memoria do buffer (MB)", "Inverter imagem verticalmente", "Capturar apenas com jogo em foco", "Webhook do Discord",
				"Tamanho maximo do GIF (MB)", "Intervalo entre GIFs (segundos)", "Caminho do FFmpeg", "Baixar FFmpeg automaticamente", "Pasta dos GIFs", "Nome do remetente", "Mensagem ao morrer", "Mensagem da captura manual", "Nome do remetente (manual)", "Nome do remetente (morte)",
				"Foto/avatar (manual)", "Foto/avatar (morte)", "Rodape (manual)", "Rodape (morte)"
			};
			int num = Array.IndexOf(array, key);
			ConfigurationManagerAttributes configurationManagerAttributes = new ConfigurationManagerAttributes
			{
				DispName = ((num < 0) ? key : array2[num]),
				Order = num,
				ShowRangeAsPercent = false,
				IsAdvanced = (key == "BufferJpegQuality" || key == "BufferMemoryMB" || key == "FfmpegPath")
			};
			if (key == "WebhookUrl")
			{
				configurationManagerAttributes.CustomDrawer = DrawWebhook;
			}
			return ((BaseUnityPlugin)this).Config.Bind<T>(section, key, value, new ConfigDescription(description.Description, description.AcceptableValues, new object[1] { configurationManagerAttributes }));
		}

		private void DrawWebhook(ConfigEntryBase entry)
		{
			string text = (string)entry.BoxedValue;
			string text2 = GUILayout.PasswordField(text, '*', (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(180f) });
			if (text2 != text)
			{
				entry.BoxedValue = text2;
			}
			if (!GUILayout.Button("Colar", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }))
			{
				return;
			}
			string text3 = (GUIUtility.systemCopyBuffer ?? "").Trim();
			try
			{
				if (text3.Length > 0)
				{
					GifMath.Webhook(text3);
				}
				entry.BoxedValue = text3;
			}
			catch (ArgumentException)
			{
				Notify("Link invalido. Copie o webhook do canal do Discord.");
			}
		}

		private static bool ConfigurationOpen()
		{
			if (!Chainloader.PluginInfos.TryGetValue("shudnal.ConfigurationManager", out var value) || (Object)(object)value.Instance == (Object)null)
			{
				return false;
			}
			PropertyInfo property = ((object)value.Instance).GetType().GetProperty("DisplayingWindow", BindingFlags.Instance | BindingFlags.Public);
			if (property != null && property.GetValue(value.Instance, null) is bool)
			{
				return (bool)property.GetValue(value.Instance, null);
			}
			return false;
		}

		private void Awake()
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Expected O, but got Unknown
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			//IL_0114: Unknown result type (might be due to invalid IL or missing references)
			//IL_011e: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Expected O, but got Unknown
			//IL_02fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0305: Expected O, but got Unknown
			//IL_0334: Unknown result type (might be due to invalid IL or missing references)
			//IL_033e: Expected O, but got Unknown
			//IL_03a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_03af: Expected O, but got Unknown
			instance = this;
			directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			MigrateConfiguration();
			enabledCapture = Bind("01 - Geral", "Enabled", value: true, "Ativa a captura durante a partida.");
			death = Bind("01 - Geral", "CaptureOnDeath", value: true, "Gera um GIF de 4 segundos, finalizado 2 segundos apos a morte.");
			hotkey = Bind<KeyboardShortcut>("01 - Geral", "CaptureKey", new KeyboardShortcut((KeyCode)289, Array.Empty<KeyCode>()), "Salva e envia os segundos anteriores.");
			fps = Bind("02 - GIF", "FramesPerSecond", 10, new ConfigDescription("FPS alvo; o jogo precisa produzir os quadros.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(10, 20), Array.Empty<object>()));
			height = Bind("02 - GIF", "CaptureHeight", 480, new ConfigDescription("Altura maxima; preserva a proporcao. 480 e suficiente para o GIF.", (AcceptableValueBase)(object)new AcceptableValueList<int>(new int[3] { 480, 720, 1080 }), Array.Empty<object>()));
			jpeg = Bind("02 - GIF", "BufferJpegQuality", 75, new ConfigDescription("Qualidade dos quadros temporarios.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(40, 95), Array.Empty<object>()));
			memory = Bind("02 - GIF", "BufferMemoryMB", 192, new ConfigDescription("Limite dos JPEG no buffer; exportacao pode usar memoria adicional.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(32, 512), Array.Empty<object>()));
			flip = Bind("02 - GIF", "FlipVertically", value: true, "Correcao de orientacao para o renderizador deste PC. Desative se necessario em outro renderizador.");
			focusOnly = Bind("02 - GIF", "OnlyWhenFocused", value: true, "Pausa e limpa o buffer ao alternar para outro aplicativo.");
			webhook = Bind("03 - Discord", "WebhookUrl", "", "Webhook do canal. Vazio salva apenas no PC. Nao compartilhe este valor.");
			webhookName = Bind("03 - Discord", "WebhookName", "Valheim GIFs", "Nome exibido nas mensagens enviadas pelo mod. Maximo de 80 caracteres.");
			deathMessage = Bind("03 - Discord", "DeathMessage", "{player} foi para Valhalla! Ultimos momentos registrados...", "Mensagem para morte. Use {player} para inserir o nome do jogador.");
			manualMessage = Bind("03 - Discord", "ManualMessage", "{player} registrou esta aventura!", "Mensagem para captura por tecla. Use {player} para inserir o nome do jogador.");
			manualName = Bind("05 - Discord manual", "ManualName", "", "Nome para capturas manuais. Vazio usa Nome do remetente do grupo Discord.");
			manualAvatar = Bind("05 - Discord manual", "ManualAvatarUrl", "", "URL HTTP/HTTPS direta de uma imagem. Vazio usa o avatar do webhook.");
			manualFooter = Bind("05 - Discord manual", "ManualFooter", "", "Rodape opcional abaixo do GIF. Use {player} para o nome do jogador; o rodape nao aceita negrito.");
			deathName = Bind("06 - Discord morte", "DeathName", "", "Nome para registros de morte. Vazio usa Nome do remetente do grupo Discord.");
			deathAvatar = Bind("06 - Discord morte", "DeathAvatarUrl", "", "URL HTTP/HTTPS direta de uma imagem. Vazio usa o avatar do webhook.");
			deathFooter = Bind("06 - Discord morte", "DeathFooter", "", "Rodape opcional abaixo do GIF. Use {player} para o nome do jogador; o rodape nao aceita negrito.");
			maxMb = Bind("03 - Discord", "MaxUploadMB", 20f, new ConfigDescription("Limite em MB decimais. Ajuste ao limite do canal; nao e detectado automaticamente.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			cooldown = Bind("03 - Discord", "CooldownSeconds", 15f, new ConfigDescription("Intervalo minimo entre clipes; apenas uma exportacao por vez.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(3f, 120f), Array.Empty<object>()));
			ffmpeg = Bind("04 - Arquivos", "FfmpegPath", "ffmpeg.exe", "Caminho absoluto ou relativo a pasta deste mod.");
			autoDownload = Bind("04 - Arquivos", "AutoDownloadEncoder", value: true, "Baixa o FFmpeg automaticamente uma vez, quando ausente. Requer internet.");
			output = Bind("04 - Arquivos", "ClipsDirectory", "clips", "Pasta de clipes, relativa ao mod ou absoluta. Clipes salvos nao sao apagados automaticamente.");
			harmony = new Harmony("hugo.valheim.discordclips");
			harmony.PatchAll(typeof(Plugin).Assembly);
			if (!SystemInfo.supportsAsyncGPUReadback)
			{
				Notify("GPU/renderizador sem captura assincrona. Mod desativado nesta sessao.");
				((Behaviour)this).enabled = false;
			}
			else
			{
				((MonoBehaviour)this).StartCoroutine(CaptureLoop());
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Discord GIFs 0.2.1 experimental. Somente GIF de 4 segundos, sem captura de audio.");
			}
		}

		private string Resolve(string path)
		{
			return Path.GetFullPath(Path.IsPathRooted(path) ? path : Path.Combine(directory, path));
		}

		private void Notify(string message)
		{
			if (!closed)
			{
				messages.Enqueue(message);
			}
		}

		private void StartCodecInstall()
		{
			if (Interlocked.CompareExchange(ref installing, 1, 0) != 0)
			{
				return;
			}
			installAttempted = true;
			codecStatus = "Baixando componente de GIF automaticamente...";
			Notify(codecStatus);
			Task.Run(async delegate
			{
				try
				{
					using (HttpClient client = new HttpClient
					{
						Timeout = TimeSpan.FromMinutes(15.0)
					})
					{
						await CodecInstaller.Install(client, directory, delegate(string text)
						{
							codecStatus = text;
						}, lifetime.Token).ConfigureAwait(continueOnCapturedContext: false);
					}
					codecStatus = "";
					Notify("Codificador pronto. Entre no mundo e aguarde 5 segundos antes de apertar F8.");
				}
				catch (OperationCanceledException)
				{
					codecStatus = "Download interrompido. Tente novamente.";
				}
				catch (Exception ex2)
				{
					codecStatus = "Falha ao baixar codificador (" + ex2.GetType().Name + "). Confira a internet e tente novamente.";
					Notify(codecStatus);
				}
				finally
				{
					Interlocked.Exchange(ref installing, 0);
				}
			});
		}

		private void OnGUI()
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			if (!string.IsNullOrEmpty(codecStatus))
			{
				GUI.Box(new Rect(12f, 12f, (float)Math.Min(650, Screen.width - 24), 88f), "Discord GIFs\n" + codecStatus);
				if (Volatile.Read(in installing) == 0 && GUI.Button(new Rect(24f, 64f, 180f, 26f), "Tentar download novamente"))
				{
					StartCodecInstall();
				}
			}
		}

		private void Update()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			string result;
			while (messages.TryDequeue(out result))
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)result);
				if ((Object)(object)MessageHud.instance != (Object)null)
				{
					MessageHud.instance.ShowMessage((MessageType)1, result, 0, (Sprite)null, false, true);
				}
			}
			if (recording && !ConfigurationOpen())
			{
				KeyboardShortcut value = hotkey.Value;
				if (((KeyboardShortcut)(ref value)).IsDown())
				{
					SafeCapture("tecla");
				}
			}
		}

		private IEnumerator CaptureLoop()
		{
			WaitForEndOfFrame end = new WaitForEndOfFrame();
			while (!closed)
			{
				yield return end;
				bool flag = enabledCapture.Value && !ConfigurationOpen() && (Object)(object)Player.m_localPlayer != (Object)null && (!focusOnly.Value || Application.isFocused) && (!((Character)Player.m_localPlayer).IsDead() || deathPending);
				string path = "";
				try
				{
					path = Resolve(ffmpeg.Value);
				}
				catch
				{
				}
				if (!File.Exists(path))
				{
					flag = false;
					bool flag2 = string.Equals(ffmpeg.Value, "ffmpeg.exe", StringComparison.OrdinalIgnoreCase);
					if (enabledCapture.Value && autoDownload.Value && flag2 && !installAttempted)
					{
						StartCodecInstall();
					}
					else if ((!autoDownload.Value || !flag2) && !warnedMissing)
					{
						warnedMissing = true;
						Notify("Codificador ausente. Ative AutoDownloadEncoder e use FfmpegPath = ffmpeg.exe para baixar automaticamente.");
					}
				}
				if (!flag)
				{
					if (recording)
					{
						StopRecording();
					}
					continue;
				}
				if (!recording)
				{
					recording = true;
					nextFrame = 0.0;
				}
				double num = (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
				if (!(num < nextFrame) && Volatile.Read(in pending) == 0)
				{
					nextFrame = num + 1.0 / (double)fps.Value;
					try
					{
						RequestFrame(num);
					}
					catch (Exception ex)
					{
						Notify("Captura interrompida (" + ex.GetType().Name + ").");
						enabledCapture.Value = false;
						StopRecording();
					}
				}
			}
		}

		private void RequestFrame(double now)
		{
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Expected O, but got Unknown
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Expected O, but got Unknown
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			int h = Math.Max(2, Math.Min(height.Value, Screen.height) / 2 * 2);
			int w = Math.Max(2, (int)((double)(Screen.width * h) / (double)Math.Max(1, Screen.height)) / 2 * 2);
			if ((Object)(object)screen == (Object)null || ((Texture)screen).width != Screen.width || ((Texture)screen).height != Screen.height || ((Texture)scaled).width != w || ((Texture)scaled).height != h)
			{
				FreeTextures();
				lock (captureGate)
				{
					ring.Clear();
					generation++;
				}
				screen = new RenderTexture(Screen.width, Screen.height, 0, (RenderTextureFormat)0);
				scaled = new RenderTexture(w, h, 0, (RenderTextureFormat)0);
				screen.Create();
				scaled.Create();
			}
			ScreenCapture.CaptureScreenshotIntoRenderTexture(screen);
			Graphics.Blit((Texture)(object)screen, scaled);
			int epoch = generation;
			int quality = jpeg.Value;
			long limit = (long)memory.Value * 1000000L;
			int retain = 4;
			Interlocked.Exchange(ref pending, 1);
			try
			{
				AsyncGPUReadback.Request((Texture)(object)scaled, 0, (TextureFormat)4, (Action<AsyncGPUReadbackRequest>)delegate(AsyncGPUReadbackRequest request)
				{
					//IL_0067: Unknown result type (might be due to invalid IL or missing references)
					//IL_006c: Unknown result type (might be due to invalid IL or missing references)
					if (closed || ((AsyncGPUReadbackRequest)(ref request)).hasError || epoch != generation)
					{
						Interlocked.Exchange(ref pending, 0);
						if (((AsyncGPUReadbackRequest)(ref request)).hasError)
						{
							Notify("Falha na leitura de um quadro da GPU.");
						}
					}
					else
					{
						byte[] pixels;
						try
						{
							pixels = ((AsyncGPUReadbackRequest)(ref request)).GetData<byte>(0).ToArray();
						}
						catch
						{
							Interlocked.Exchange(ref pending, 0);
							return;
						}
						Task.Run(delegate
						{
							try
							{
								byte[] array = ImageConversion.EncodeArrayToJPG((Array)pixels, (GraphicsFormat)8, (uint)w, (uint)h, 0u, quality);
								lock (captureGate)
								{
									if (!closed && epoch == generation)
									{
										ring.Add(new GifFrame
										{
											Time = now,
											Jpeg = array,
											Width = w,
											Height = h
										}, (double)retain + 0.3, limit);
									}
								}
							}
							catch (Exception ex)
							{
								Notify("Falha ao comprimir quadro (" + ex.GetType().Name + ").");
							}
							finally
							{
								Interlocked.Exchange(ref pending, 0);
							}
						});
					}
				});
			}
			catch
			{
				Interlocked.Exchange(ref pending, 0);
				throw;
			}
		}

		private void SafeCapture(string reason)
		{
			try
			{
				Capture(reason);
			}
			catch (Exception ex)
			{
				Notify("Nao foi possivel iniciar o clipe (" + ex.GetType().Name + "). Confira os caminhos da configuracao.");
			}
		}

		private void Capture(string reason)
		{
			if (closed || !recording || !enabledCapture.Value || (!Application.isFocused && focusOnly.Value))
			{
				return;
			}
			if (deathPending && reason != "morte")
			{
				Notify("Aguardando a animacao da morte para finalizar o GIF.");
			}
			else
			{
				if (reason == "morte" && !death.Value)
				{
					return;
				}
				if (Volatile.Read(in exporting) != 0)
				{
					Notify("Um clipe ainda esta sendo processado.");
					return;
				}
				double num = (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
				if (num - lastTrigger < (double)cooldown.Value)
				{
					Notify("Aguarde o intervalo entre clipes.");
					return;
				}
				GifFrame[] array = ring.Snapshot();
				if (array.Length < 2 || num - array[^1].Time > 0.6 || array[^1].Time - array[0].Time < 4.0)
				{
					Notify("Buffer incompleto. Aguarde " + 4 + " segundos de jogo em primeiro plano; reduza qualidade se persistir.");
					return;
				}
				double time = array[^1].Time;
				GifExportJob job = new GifExportJob
				{
					Frames = array,
					Start = time - 4.0,
					Duration = 4.0,
					Fps = fps.Value,
					Flip = flip.Value,
					Limit = (long)(maxMb.Value * 1000000f),
					Ffmpeg = Resolve(ffmpeg.Value),
					OutputDirectory = Resolve(output.Value),
					Webhook = webhook.Value,
					Player = Player.m_localPlayer.GetPlayerName(),
					Reason = reason,
					WebhookName = DiscordUpload.SelectName((reason == "morte") ? deathName.Value : manualName.Value, webhookName.Value),
					AvatarUrl = ((reason == "morte") ? deathAvatar.Value : manualAvatar.Value),
					Footer = ((reason == "morte") ? deathFooter.Value : manualFooter.Value),
					MessageTemplate = ((reason == "morte") ? deathMessage.Value : manualMessage.Value)
				};
				lastTrigger = num;
				Interlocked.Exchange(ref exporting, 1);
				Task.Run(async delegate
				{
					try
					{
						await GifExporter.Run(job, Notify, lifetime.Token).ConfigureAwait(continueOnCapturedContext: false);
					}
					finally
					{
						Interlocked.Exchange(ref exporting, 0);
					}
				});
			}
		}

		private void StopRecording()
		{
			recording = false;
			lock (captureGate)
			{
				generation++;
				ring.Clear();
			}
		}

		private void FreeTextures()
		{
			if ((Object)(object)screen != (Object)null)
			{
				screen.Release();
				Object.Destroy((Object)(object)screen);
				screen = null;
			}
			if ((Object)(object)scaled != (Object)null)
			{
				scaled.Release();
				Object.Destroy((Object)(object)scaled);
				scaled = null;
			}
		}

		private void OnDestroy()
		{
			closed = true;
			lifetime.Cancel();
			StopRecording();
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			AsyncGPUReadback.WaitAllRequests();
			FreeTextures();
			if ((Object)(object)instance == (Object)(object)this)
			{
				instance = null;
			}
		}

		private IEnumerator CaptureAfterDeath(Player player)
		{
			deathPending = true;
			try
			{
				yield return (object)new WaitForSecondsRealtime(2f);
				if (!closed && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer)
				{
					SafeCapture("morte");
				}
			}
			finally
			{
				deathPending = false;
			}
		}
	}
}