Decompiled source of Valheim Moments v0.8.1

BepInEx/plugins/ValheimEventClips/ValheimEventClips.dll

Decompiled 4 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using ValheimEventClips.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ValheimEventClips")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.8.1.0")]
[assembly: AssemblyInformationalVersion("0.8.1")]
[assembly: AssemblyProduct("ValheimEventClips")]
[assembly: AssemblyTitle("ValheimEventClips")]
[assembly: AssemblyVersion("0.8.1.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 ValheimEventClips
{
	internal enum BossNameMode
	{
		KillCredit,
		FinalBlow,
		Both
	}
	internal static class BossAttribution
	{
		private sealed class Context
		{
			internal string Enemy;

			internal string Name;
		}

		private const string RpcName = "ValheimMoments_BossFinalBlow_v1";

		private static readonly FieldInfo LastHit = typeof(Character).GetField("m_lastHit", BindingFlags.Instance | BindingFlags.NonPublic);

		[ThreadStatic]
		private static Context current;

		private static ZRoutedRpc registered;

		private static readonly AttributionInbox inbox = new AttributionInbox();

		private static readonly Stopwatch clock = Stopwatch.StartNew();

		internal static Action<string> OnDiagnostic;

		internal static string Resolve(HitData hit, out string reason)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: 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)
			//IL_0075: 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)
			if (hit == null)
			{
				reason = "no recorded damage";
				return null;
			}
			Character attacker = hit.GetAttacker();
			Player val = (Player)(object)((attacker is Player) ? attacker : null);
			if ((Object)(object)val != (Object)null)
			{
				reason = "resolved player object";
				return val.GetPlayerName();
			}
			if ((Object)(object)attacker == (Object)null && !((ZDOID)(ref hit.m_attacker)).IsNone() && (Object)(object)ZNet.instance != (Object)null)
			{
				foreach (PlayerInfo player in ZNet.instance.GetPlayerList())
				{
					if (player.m_characterID == hit.m_attacker)
					{
						reason = "resolved exact player network ID";
						return player.m_name;
					}
				}
			}
			reason = "hit=" + ((object)Unsafe.As<HitType, HitType>(ref hit.m_hitType)/*cast due to .constrained prefix*/).ToString() + ", " + (((Object)(object)attacker != (Object)null) ? "non-player attacker" : (((ZDOID)(ref hit.m_attacker)).IsNone() ? "no attacker ID" : "attacker ID not in player list"));
			return null;
		}

		internal static void Install(Harmony harmony)
		{
			//IL_002b: 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_004d: Expected O, but got Unknown
			//IL_004d: Expected O, but got Unknown
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			//IL_0112: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Expected O, but got Unknown
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Character), "OnDeath", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(BossAttribution), "BeforeDeath", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(BossAttribution), "AfterDeath", (Type[])null), (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "RegisterKill", new Type[6]
			{
				typeof(long),
				typeof(string),
				typeof(int),
				typeof(KillModifiers),
				typeof(int),
				typeof(bool)
			}, (Type[])null), new HarmonyMethod(typeof(BossAttribution), "BeforeSendCredit", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			foreach (ConstructorInfo declaredConstructor in AccessTools.GetDeclaredConstructors(typeof(ZRoutedRpc), (bool?)null))
			{
				harmony.Patch((MethodBase)declaredConstructor, (HarmonyMethod)null, new HarmonyMethod(typeof(BossAttribution), "AfterRouterCreated", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			EnsureRegistered(ZRoutedRpc.instance);
		}

		private static void AfterRouterCreated(ZRoutedRpc __instance)
		{
			EnsureRegistered(__instance);
		}

		private static void EnsureRegistered(ZRoutedRpc router)
		{
			try
			{
				if (router != null && registered != router)
				{
					router.Register<string, string>("ValheimMoments_BossFinalBlow_v1", (Action<long, string, string>)Receive);
					registered = router;
					inbox.Clear();
				}
			}
			catch
			{
			}
		}

		private static void Receive(long sender, string enemy, string name)
		{
			inbox.Add(sender, enemy, name, clock.Elapsed.TotalSeconds);
		}

		private static void BeforeDeath(Character __instance, out Context __state)
		{
			__state = current;
			current = null;
			try
			{
				if (!__instance.IsBoss() || !__instance.IsOwner())
				{
					return;
				}
				object? obj = LastHit?.GetValue(__instance);
				string reason;
				string name = Resolve((HitData)((obj is HitData) ? obj : null), out reason);
				current = new Context
				{
					Enemy = __instance.m_name,
					Name = name
				};
				try
				{
					OnDiagnostic?.Invoke(reason);
				}
				catch
				{
				}
			}
			catch
			{
			}
		}

		private static void AfterDeath(Context __state)
		{
			current = __state;
		}

		private static void BeforeSendCredit(long playerPeerID, string enemyName, int bossNumber)
		{
			try
			{
				if (bossNumber > 0 && current != null && !(current.Enemy != enemyName))
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(playerPeerID, "ValheimMoments_BossFinalBlow_v1", new object[2]
					{
						enemyName,
						current.Name ?? ""
					});
				}
			}
			catch
			{
			}
		}

		internal static string Take(long sender, string enemy)
		{
			if (sender == 0L && current != null && current.Enemy == enemy)
			{
				return current.Name;
			}
			string text = inbox.Take(sender, enemy, clock.Elapsed.TotalSeconds);
			try
			{
				OnDiagnostic?.Invoke((text == null) ? "owner metadata missing or expired" : ((text.Length == 0) ? "owner reported no player attacker" : "received owner attribution"));
			}
			catch
			{
			}
			return text;
		}

		internal static void Clear()
		{
			current = null;
			inbox.Clear();
			OnDiagnostic = null;
		}
	}
	internal sealed class AttributionInbox
	{
		private sealed class Entry
		{
			internal long Sender;

			internal string Enemy;

			internal string Name;

			internal double Time;
		}

		private readonly List<Entry> entries = new List<Entry>();

		internal void Add(long sender, string enemy, string name, double now)
		{
			if (!string.IsNullOrEmpty(enemy) && enemy.Length <= 256 && name != null && name.Length <= 256)
			{
				entries.RemoveAll((Entry e) => now - e.Time > 5.0 || (e.Sender == sender && e.Enemy == enemy));
				if (entries.Count >= 64)
				{
					entries.RemoveAt(0);
				}
				entries.Add(new Entry
				{
					Sender = sender,
					Enemy = enemy,
					Name = name,
					Time = now
				});
			}
		}

		internal string Take(long sender, string enemy, double now)
		{
			entries.RemoveAll((Entry e) => now - e.Time > 5.0);
			int num = entries.FindIndex((Entry e) => e.Sender == sender && e.Enemy == enemy);
			if (num < 0)
			{
				return null;
			}
			string name = entries[num].Name;
			entries.RemoveAt(num);
			return name;
		}

		internal void Clear()
		{
			entries.Clear();
		}
	}
	internal sealed class BossKill
	{
		internal string EnemyKey;

		internal string PlayerName;

		internal string FinalBlowName;

		internal int BossNumber;

		internal bool FirstKill;

		internal BossLoot Loot;
	}
	internal static class BossKillDetector
	{
		private sealed class State
		{
			internal PlayerProfile Profile;

			internal string EnemyKey;

			internal int BossNumber;

			internal float Count;

			internal string FinalBlowName;

			internal BossLoot Loot;
		}

		internal static Action<BossKill> OnKill;

		internal static Action<BossKill> OnLootKill;

		internal static Func<bool> ObserveOrdinary;

		internal static Action OnError;

		private const BindingFlags Fields = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly FieldInfo Stats = typeof(PlayerProfile).GetField("m_playerStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

		internal static bool TryCount(PlayerProfile profile, string key, out float count)
		{
			count = 0f;
			try
			{
				if (!(Stats?.GetValue(profile) is Array { Length: not 0 } array))
				{
					return false;
				}
				object value = array.GetValue(0);
				if (!((value?.GetType().GetField("m_enemyStats", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(value) is Dictionary<string, float>[] array2) || array2.Length == 0 || array2[0] == null || string.IsNullOrEmpty(key))
				{
					return false;
				}
				array2[0].TryGetValue(key, out count);
				return !float.IsNaN(count) && !float.IsInfinity(count) && count >= 0f;
			}
			catch
			{
				return false;
			}
		}

		internal static void Install(Harmony harmony)
		{
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Expected O, but got Unknown
			//IL_00cc: Expected O, but got Unknown
			MethodInfo method = typeof(Game).GetMethod("RPC_RegisterKill", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[6]
			{
				typeof(long),
				typeof(string),
				typeof(int),
				typeof(int),
				typeof(int),
				typeof(bool)
			}, null);
			if (method == null || method.ReturnType != typeof(void))
			{
				throw new MissingMethodException("Expected Game.RPC_RegisterKill signature not found.");
			}
			harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(BossKillDetector), "Prefix", (Type[])null), new HarmonyMethod(typeof(BossKillDetector), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void Prefix(Game __instance, long sender, string enemyName, int bossNumber, out State __state)
		{
			__state = null;
			try
			{
				if (bossNumber <= 0)
				{
					Func<bool> observeOrdinary = ObserveOrdinary;
					if (observeOrdinary == null || !observeOrdinary())
					{
						return;
					}
				}
				string finalBlowName = ((bossNumber > 0) ? BossAttribution.Take(sender, enemyName) : null);
				BossLoot loot = BossLootDetector.Take(sender, enemyName);
				PlayerProfile playerProfile = __instance.GetPlayerProfile();
				if (!TryCount(playerProfile, enemyName, out var count))
				{
					ReportError();
					return;
				}
				__state = new State
				{
					Profile = playerProfile,
					EnemyKey = enemyName,
					BossNumber = bossNumber,
					Count = count,
					FinalBlowName = finalBlowName,
					Loot = loot
				};
			}
			catch
			{
				ReportError();
			}
		}

		private static void Postfix(State __state)
		{
			if (__state == null)
			{
				return;
			}
			try
			{
				if (!TryCount(__state.Profile, __state.EnemyKey, out var count))
				{
					ReportError();
				}
				else if (!(count <= __state.Count))
				{
					((__state.BossNumber > 0) ? OnKill : OnLootKill)?.Invoke(new BossKill
					{
						EnemyKey = __state.EnemyKey,
						BossNumber = __state.BossNumber,
						PlayerName = __state.Profile.GetName(),
						FirstKill = (__state.Count == 0f),
						FinalBlowName = __state.FinalBlowName,
						Loot = __state.Loot
					});
				}
			}
			catch
			{
				ReportError();
			}
		}

		private static void ReportError()
		{
			try
			{
				OnError?.Invoke();
			}
			catch
			{
			}
		}
	}
	internal sealed class LootItem
	{
		internal string Id;

		internal string Name;

		internal int Quantity;

		internal int Rank = -1;

		internal int Sockets;

		internal string Rarity = "";

		internal string Color = "";

		internal string Modifiers = "";

		internal bool Unidentified;
	}
	internal sealed class BossLoot
	{
		internal readonly string Id = Guid.NewGuid().ToString("N");

		internal readonly List<LootItem> Items = new List<LootItem>();

		internal bool Observed;

		internal bool Incomplete;

		internal bool Pending;

		internal int Revision;

		internal void AddEpic(LootItem item)
		{
			if (item == null || string.IsNullOrEmpty(item.Name) || string.IsNullOrEmpty(item.Id) || item.Quantity <= 0 || Items.Count >= 64)
			{
				Incomplete = true;
				return;
			}
			Items.Add(item);
			Observed = true;
		}

		internal void Add(string id, string name, long quantity)
		{
			if (string.IsNullOrEmpty(id) || string.IsNullOrEmpty(name) || quantity <= 0)
			{
				Incomplete = true;
				return;
			}
			if (id.Length > 128 || name.Length > 256 || quantity > int.MaxValue)
			{
				Incomplete = true;
				return;
			}
			LootItem lootItem = Items.Find((LootItem i) => i.Id == id && i.Name == name);
			if (lootItem != null)
			{
				if (quantity > int.MaxValue - lootItem.Quantity)
				{
					Incomplete = true;
				}
				else
				{
					lootItem.Quantity += (int)quantity;
				}
			}
			else if (Items.Count < 64)
			{
				Items.Add(new LootItem
				{
					Id = id,
					Name = name,
					Quantity = (int)quantity
				});
			}
			else
			{
				Incomplete = true;
			}
		}

		internal string Display(int maximum, bool quantity, Func<string, string> localize, bool showRarity = true, bool showModifiers = true, bool showSockets = true, bool showUnidentified = true)
		{
			if (!Observed)
			{
				return "unavailable";
			}
			if (Items.Count == 0)
			{
				if (!Incomplete)
				{
					return "No items generated.";
				}
				return "unavailable";
			}
			List<LootItem> list = new List<LootItem>();
			foreach (LootItem item in Items)
			{
				list.Add(new LootItem
				{
					Id = item.Id,
					Name = localize(item.Name),
					Quantity = item.Quantity,
					Rank = item.Rank,
					Rarity = localize(item.Rarity),
					Color = item.Color,
					Modifiers = localize(item.Modifiers),
					Sockets = item.Sockets,
					Unidentified = item.Unidentified
				});
			}
			list.Sort(delegate(LootItem a, LootItem b)
			{
				int num2 = b.Rank.CompareTo(a.Rank);
				if (num2 != 0)
				{
					return num2;
				}
				num2 = StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name);
				return (num2 == 0) ? StringComparer.Ordinal.Compare(a.Id, b.Id) : num2;
			});
			maximum = Math.Max(1, Math.Min(20, maximum));
			List<string> list2 = new List<string>();
			for (int num = 0; num < Math.Min(maximum, list.Count); num++)
			{
				LootItem lootItem = list[num];
				list2.Add("* " + ((showRarity && lootItem.Rank >= 0) ? (Marker(lootItem.Color) + " " + lootItem.Rarity + " ") : "") + lootItem.Name + (quantity ? (" ×" + lootItem.Quantity) : "") + ((showUnidentified && lootItem.Unidentified) ? " (unidentified)" : ""));
				if (!lootItem.Unidentified && showModifiers && !string.IsNullOrEmpty(lootItem.Modifiers))
				{
					list2.Add("  " + lootItem.Modifiers.Replace("\n", "\n  "));
				}
				if (!lootItem.Unidentified && showSockets && lootItem.Sockets > 0)
				{
					list2.Add("  Sockets: " + lootItem.Sockets);
				}
			}
			if (list.Count > maximum)
			{
				list2.Add("+" + (list.Count - maximum) + " more item types");
			}
			if (Incomplete)
			{
				list2.Add("Some loot details unavailable.");
			}
			if (Pending)
			{
				list2.Add("Additional Epic Loot details unavailable before upload.");
			}
			return string.Join("\n", list2);
		}

		private static string Marker(string color)
		{
			if (color == null || color.Length != 7 || color[0] != '#' || !int.TryParse(color.Substring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result))
			{
				return "◆";
			}
			int[] array = new int[7] { 4359668, 10181046, 15965202, 15022389, 4431943, 16635957, 15658734 };
			string[] array2 = new string[7] { "\ud83d\udd35", "\ud83d\udfe3", "\ud83d\udfe0", "\ud83d\udd34", "\ud83d\udfe2", "\ud83d\udfe1", "⚪" };
			int num = 0;
			int num2 = int.MaxValue;
			for (int i = 0; i < array.Length; i++)
			{
				int num3 = (result >> 16) - (array[i] >> 16);
				int num4 = ((result >> 8) & 0xFF) - ((array[i] >> 8) & 0xFF);
				int num5 = (result & 0xFF) - (array[i] & 0xFF);
				int num6 = num3 * num3 + num4 * num4 + num5 * num5;
				if (num6 < num2)
				{
					num2 = num6;
					num = i;
				}
			}
			return array2[num];
		}

		internal string Encode()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(2);
				binaryWriter.Write(Observed);
				binaryWriter.Write(Incomplete);
				binaryWriter.Write(Pending);
				binaryWriter.Write(Revision);
				binaryWriter.Write(Items.Count);
				foreach (LootItem item in Items)
				{
					binaryWriter.Write(item.Id);
					binaryWriter.Write(item.Name);
					binaryWriter.Write(item.Quantity);
					binaryWriter.Write(item.Rank);
					binaryWriter.Write(item.Rarity);
					binaryWriter.Write(item.Color);
					binaryWriter.Write(item.Modifiers);
					binaryWriter.Write(item.Sockets);
					binaryWriter.Write(item.Unidentified);
				}
			}
			return Convert.ToBase64String(memoryStream.ToArray());
		}

		internal static BossLoot Decode(string encoded)
		{
			if (encoded == null || encoded.Length > 524288)
			{
				return null;
			}
			try
			{
				using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(encoded));
				using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8);
				if (binaryReader.ReadInt32() != 2)
				{
					return null;
				}
				BossLoot bossLoot = new BossLoot
				{
					Observed = binaryReader.ReadBoolean(),
					Incomplete = binaryReader.ReadBoolean(),
					Pending = binaryReader.ReadBoolean(),
					Revision = binaryReader.ReadInt32()
				};
				if (bossLoot.Revision < 0)
				{
					return null;
				}
				int num = binaryReader.ReadInt32();
				if (num < 0 || num > 64)
				{
					return null;
				}
				for (int i = 0; i < num; i++)
				{
					string text = binaryReader.ReadString();
					string text2 = binaryReader.ReadString();
					int num2 = binaryReader.ReadInt32();
					if (text.Length == 0 || text.Length > 128 || text2.Length == 0 || text2.Length > 256 || num2 <= 0)
					{
						return null;
					}
					LootItem lootItem = new LootItem
					{
						Id = text,
						Name = text2,
						Quantity = num2,
						Rank = binaryReader.ReadInt32(),
						Rarity = binaryReader.ReadString(),
						Color = binaryReader.ReadString(),
						Modifiers = binaryReader.ReadString(),
						Sockets = binaryReader.ReadInt32(),
						Unidentified = binaryReader.ReadBoolean()
					};
					if (lootItem.Rank < -1 || lootItem.Rank > 100 || lootItem.Rarity.Length > 64 || lootItem.Color.Length > 16 || lootItem.Modifiers.Length > 1024 || lootItem.Sockets < 0 || lootItem.Sockets > 64)
					{
						return null;
					}
					bossLoot.Items.Add(lootItem);
				}
				return (memoryStream.Position == memoryStream.Length) ? bossLoot : null;
			}
			catch
			{
				return null;
			}
		}
	}
	internal sealed class LootInbox
	{
		private sealed class Entry
		{
			internal long Sender;

			internal string Enemy;

			internal string Token;

			internal double Time;

			internal bool Taken;

			internal bool Completed;

			internal BossLoot Loot = new BossLoot();
		}

		private readonly List<Entry> entries = new List<Entry>();

		private void Prune(double now)
		{
			entries.RemoveAll((Entry e) => now - e.Time > 30.0);
		}

		internal void Announce(long sender, string enemy, string token, double now)
		{
			if (string.IsNullOrEmpty(enemy) || enemy.Length > 256 || token == null || token.Length != 32 || !Guid.TryParseExact(token, "N", out var _))
			{
				return;
			}
			Prune(now);
			if (!entries.Exists((Entry e) => e.Sender == sender && e.Token == token))
			{
				if (entries.Count >= 64)
				{
					entries.RemoveAt(0);
				}
				entries.Add(new Entry
				{
					Sender = sender,
					Enemy = enemy,
					Token = token,
					Time = now
				});
			}
		}

		internal BossLoot Take(long sender, string enemy, double now)
		{
			Prune(now);
			Entry entry = entries.FindLast((Entry e) => !e.Taken && e.Sender == sender && e.Enemy == enemy && now - e.Time <= 5.0);
			if (entry == null)
			{
				return null;
			}
			entry.Taken = true;
			return entry.Loot;
		}

		internal void Complete(long sender, string token, string payload, double now)
		{
			Prune(now);
			Entry entry = entries.Find((Entry e) => e.Sender == sender && e.Token == token);
			if (entry != null)
			{
				BossLoot bossLoot = BossLoot.Decode(payload);
				if (bossLoot != null && (!entry.Completed || bossLoot.Revision > entry.Loot.Revision))
				{
					entry.Loot.Items.Clear();
					entry.Loot.Items.AddRange(bossLoot.Items);
					entry.Loot.Observed = bossLoot.Observed;
					entry.Loot.Incomplete = bossLoot.Incomplete;
					entry.Loot.Pending = bossLoot.Pending;
					entry.Loot.Revision = bossLoot.Revision;
					entry.Completed = true;
				}
			}
		}

		internal void Clear()
		{
			entries.Clear();
		}
	}
	internal static class BossLootDetector
	{
		private sealed class Context
		{
			internal Character Character;

			internal BossLoot Loot = new BossLoot();

			internal HashSet<long> Recipients = new HashSet<long>();

			internal HashSet<ItemData> EpicItems = new HashSet<ItemData>();

			internal int PendingRagdolls;
		}

		private const string CreditRpc = "ValheimMoments_BossLootCredit_v2";

		private const string ResultRpc = "ValheimMoments_BossLootResult_v2";

		private static readonly FieldInfo DropCharacter = typeof(CharacterDrop).GetField("m_character", BindingFlags.Instance | BindingFlags.NonPublic);

		[ThreadStatic]
		private static Context current;

		private static readonly LootInbox inbox = new LootInbox();

		private static readonly Stopwatch clock = Stopwatch.StartNew();

		private static ZRoutedRpc registered;

		private static bool enabled;

		private static ConditionalWeakTable<object, Context> ragdolls = new ConditionalWeakTable<object, Context>();

		internal static Action<int> OnObserved;

		internal static Action OnError;

		internal static void EnableEpic(Harmony harmony)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			//IL_007f: Expected O, but got Unknown
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Ragdoll), "Setup", (Type[])null, (Type[])null), new HarmonyMethod(typeof(BossLootDetector), "BeforeRagdollSetup", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Ragdoll), "SpawnLoot", (Type[])null, (Type[])null), new HarmonyMethod(typeof(BossLootDetector), "BeforeRagdollLoot", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(BossLootDetector), "AfterRagdollLoot", (Type[])null), (HarmonyMethod)null);
		}

		private static void BeforeRagdollSetup(object __instance, CharacterDrop characterDrop)
		{
			try
			{
				if (current != null && DropCharacter.GetValue(characterDrop) == current.Character)
				{
					ragdolls.Remove(__instance);
					ragdolls.Add(__instance, current);
					current.PendingRagdolls++;
					current.Loot.Pending = true;
				}
			}
			catch
			{
				Error();
			}
		}

		private static void BeforeRagdollLoot(object __instance, out Context __state)
		{
			__state = current;
			current = ((enabled && ragdolls.TryGetValue(__instance, out var value)) ? value : null);
		}

		private static void AfterRagdollLoot(object __instance, Context __state, Exception __exception)
		{
			Context context = current;
			current = __state;
			try
			{
				if (context != null)
				{
					ragdolls.Remove(__instance);
					context.PendingRagdolls = Math.Max(0, context.PendingRagdolls - 1);
					context.Loot.Pending = context.PendingRagdolls > 0;
					if (__exception != null)
					{
						context.Loot.Incomplete = true;
					}
					Publish(context);
					if (!context.Loot.Pending)
					{
						context.EpicItems.Clear();
					}
				}
			}
			catch
			{
				Error();
			}
		}

		internal static void RecordEpic(List<GameObject> objects)
		{
			if (!enabled || current == null || objects == null)
			{
				return;
			}
			foreach (GameObject @object in objects)
			{
				try
				{
					ItemData val2 = (((Object)(object)@object == (Object)null) ? null : @object.GetComponent<ItemDrop>()?.m_itemData);
					if (current.EpicItems.Count >= 256)
					{
						current.Loot.Incomplete = true;
						break;
					}
					if (val2 != null && current.EpicItems.Add(val2))
					{
						current.Loot.AddEpic(EpicLootAdapter.Read(val2, ((Object)@object).name));
						OnObserved?.Invoke(current.Loot.Items.Count);
					}
				}
				catch
				{
					current.Loot.Incomplete = true;
					Error();
				}
			}
		}

		internal static void Install(Harmony harmony)
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			//IL_006b: Expected O, but got Unknown
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Expected O, but got Unknown
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			//IL_0171: Unknown result type (might be due to invalid IL or missing references)
			//IL_017e: Expected O, but got Unknown
			if (DropCharacter == null)
			{
				throw new MissingFieldException("CharacterDrop.m_character");
			}
			enabled = true;
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Character), "OnDeath", Type.EmptyTypes, (Type[])null), new HarmonyMethod(typeof(BossLootDetector), "BeforeDeath", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(BossLootDetector), "AfterDeath", (Type[])null), (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(CharacterDrop), "GenerateDropList", Type.EmptyTypes, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(BossLootDetector), "AfterRoll", (Type[])null)
			{
				priority = 0
			}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "RegisterKill", new Type[6]
			{
				typeof(long),
				typeof(string),
				typeof(int),
				typeof(KillModifiers),
				typeof(int),
				typeof(bool)
			}, (Type[])null), new HarmonyMethod(typeof(BossLootDetector), "BeforeCredit", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			foreach (ConstructorInfo declaredConstructor in AccessTools.GetDeclaredConstructors(typeof(ZRoutedRpc), (bool?)null))
			{
				harmony.Patch((MethodBase)declaredConstructor, (HarmonyMethod)null, new HarmonyMethod(typeof(BossLootDetector), "Register", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			Register(ZRoutedRpc.instance);
		}

		private static void Register(ZRoutedRpc __instance)
		{
			try
			{
				if (__instance == null || registered == __instance)
				{
					return;
				}
				__instance.Register<string, string>("ValheimMoments_BossLootCredit_v2", (Action<long, string, string>)delegate(long sender, string enemy, string token)
				{
					if (enabled)
					{
						inbox.Announce(sender, enemy, token, clock.Elapsed.TotalSeconds);
					}
				});
				__instance.Register<string, string>("ValheimMoments_BossLootResult_v2", (Action<long, string, string>)delegate(long sender, string token, string payload)
				{
					if (enabled)
					{
						inbox.Complete(sender, token, payload, clock.Elapsed.TotalSeconds);
					}
				});
				registered = __instance;
				inbox.Clear();
			}
			catch
			{
				Error();
			}
		}

		private static void BeforeDeath(Character __instance, out Context __state)
		{
			__state = current;
			current = null;
			try
			{
				if (!enabled || !__instance.IsOwner())
				{
					return;
				}
				if (!__instance.IsBoss())
				{
					Func<bool> observeOrdinary = BossKillDetector.ObserveOrdinary;
					if (observeOrdinary == null || !observeOrdinary())
					{
						return;
					}
				}
				current = new Context
				{
					Character = __instance
				};
			}
			catch
			{
				Error();
			}
		}

		private static void AfterDeath(Context __state, Exception __exception)
		{
			Context context = current;
			current = __state;
			try
			{
				if (context != null)
				{
					if (__exception != null)
					{
						context.Loot.Incomplete = true;
					}
					Publish(context);
				}
			}
			catch
			{
				Error();
			}
		}

		private static void Publish(Context context)
		{
			context.Loot.Revision++;
			string text = context.Loot.Encode();
			foreach (long recipient in context.Recipients)
			{
				try
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(recipient, "ValheimMoments_BossLootResult_v2", new object[2]
					{
						context.Loot.Id,
						text
					});
				}
				catch
				{
					Error();
				}
			}
		}

		private static void BeforeCredit(long playerPeerID, string enemyName, int bossNumber)
		{
			try
			{
				if (current != null && !(current.Character.m_name != enemyName) && current.Recipients.Add(playerPeerID))
				{
					ZRoutedRpc.instance.InvokeRoutedRPC(playerPeerID, "ValheimMoments_BossLootCredit_v2", new object[2]
					{
						enemyName,
						current.Loot.Id
					});
				}
			}
			catch
			{
				Error();
			}
		}

		internal static BossLoot Take(long sender, string enemy)
		{
			if (sender == 0L && current != null && current.Character.m_name == enemy)
			{
				return current.Loot;
			}
			return inbox.Take(sender, enemy, clock.Elapsed.TotalSeconds);
		}

		private static void AfterRoll(CharacterDrop __instance, List<KeyValuePair<GameObject, int>> __result)
		{
			try
			{
				if (current == null || DropCharacter.GetValue(__instance) != current.Character)
				{
					return;
				}
				if (__result == null)
				{
					current.Loot.Incomplete = true;
					return;
				}
				current.Loot.Observed = true;
				foreach (KeyValuePair<GameObject, int> item in __result)
				{
					if ((Object)(object)item.Key == (Object)null || item.Value <= 0)
					{
						current.Loot.Incomplete = true;
						continue;
					}
					ItemDrop component = item.Key.GetComponent<ItemDrop>();
					if ((Object)(object)component == (Object)null || component.m_itemData?.m_shared == null)
					{
						current.Loot.Incomplete = true;
					}
					else
					{
						current.Loot.Add(((Object)item.Key).name, component.m_itemData.m_shared.m_name, (long)item.Value * (long)component.m_itemData.m_stack);
					}
				}
				OnObserved?.Invoke(current.Loot.Items.Count);
			}
			catch
			{
				if (current != null)
				{
					current.Loot.Incomplete = true;
				}
				Error();
			}
		}

		private static void Error()
		{
			try
			{
				OnError?.Invoke();
			}
			catch
			{
			}
		}

		internal static void Clear()
		{
			enabled = false;
			current = null;
			ragdolls = new ConditionalWeakTable<object, Context>();
			inbox.Clear();
			OnObserved = null;
			OnError = null;
		}
	}
	internal enum LootDecision
	{
		Accept,
		Wait,
		Reject
	}
	internal static class BossLootFilter
	{
		private static readonly Dictionary<string, int> ranks = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

		internal static void SetRarities(Type enumType)
		{
			ranks.Clear();
			foreach (object value in Enum.GetValues(enumType))
			{
				ranks[Enum.GetName(enumType, value)] = Convert.ToInt32(value);
			}
		}

		internal static bool TryRank(string name, out int rank)
		{
			rank = -1;
			if (string.Equals(name?.Trim(), "None", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return ranks.TryGetValue(name?.Trim() ?? "", out rank);
		}

		internal static LootDecision Decide(BossLoot loot, int minimum, bool deadlineReached)
		{
			if (minimum < 0)
			{
				return LootDecision.Accept;
			}
			if (loot != null)
			{
				foreach (LootItem item in loot.Items)
				{
					if (item.Rank >= minimum)
					{
						return LootDecision.Accept;
					}
				}
			}
			if (!deadlineReached && (loot == null || loot.Pending))
			{
				return LootDecision.Wait;
			}
			return LootDecision.Reject;
		}

		internal static LootDecision Evaluate(BossLoot loot, bool enabled, bool firstKill, bool firstBypasses, string minimumName, bool deadlineReached, out string reason)
		{
			if (!enabled)
			{
				reason = "rarity filter disabled";
				return LootDecision.Accept;
			}
			if (firstKill && firstBypasses)
			{
				reason = "first recorded boss kill bypasses rarity";
				return LootDecision.Accept;
			}
			if (!TryRank(minimumName, out var rank))
			{
				reason = "rarity name unsupported or Epic Loot unavailable";
				return LootDecision.Reject;
			}
			LootDecision lootDecision = Decide(loot, rank, deadlineReached);
			reason = lootDecision switch
			{
				LootDecision.Wait => "awaiting boss loot", 
				LootDecision.Accept => "observed loot meets threshold (or threshold is None)", 
				_ => "no observed item met rarity threshold", 
			};
			return lootDecision;
		}
	}
	internal sealed class ClipRelay : IDisposable
	{
		private const string Rpc = "ValheimMoments_ClipRelay_v1";

		private readonly Func<string, bool> allow;

		private readonly Func<int> limit;

		private readonly Action<RelayBuffer, string, Action<bool>> deliver;

		private readonly Action<string> log;

		private readonly HashSet<ZRpc> registered = new HashSet<ZRpc>();

		private readonly Dictionary<ZRpc, double> nextOffer = new Dictionary<ZRpc, double>();

		private ZNet session;

		private ZRpc source;

		private ZRpc target;

		private ZRpc deliveryPeer;

		private RelayBuffer incoming;

		private byte[] outgoing;

		private Task<byte[]> preparation;

		private string offeredKind;

		private string offeredMessage;

		private string outgoingId;

		private int sent;

		private int acknowledged;

		private double now;

		private double incomingDeadline;

		private double outgoingDeadline;

		private double nextSend;

		private double nextTick;

		private bool delivering;

		private bool awaitingOffer;

		private bool waitingResult;

		private bool disposed;

		internal bool DeliveryPeerConnected
		{
			get
			{
				if (deliveryPeer != null && registered.Contains(deliveryPeer))
				{
					return deliveryPeer.IsConnected();
				}
				return false;
			}
		}

		internal ClipRelay(Func<string, bool> allow, Func<int> limit, Action<RelayBuffer, string, Action<bool>> deliver, Action<string> log)
		{
			this.allow = allow;
			this.limit = limit;
			this.deliver = deliver;
			this.log = log;
		}

		internal void Tick(double time)
		{
			if (disposed)
			{
				return;
			}
			now = time;
			if (now < nextTick)
			{
				return;
			}
			nextTick = now + 0.05;
			ZNet instance = ZNet.instance;
			if (session != instance)
			{
				Reset();
				session = instance;
			}
			if ((Object)(object)session == (Object)null)
			{
				return;
			}
			HashSet<ZRpc> live = new HashSet<ZRpc>();
			foreach (ZNetPeer peer in session.GetPeers())
			{
				if (peer.IsReady() && peer.m_rpc.IsConnected())
				{
					live.Add(peer.m_rpc);
					if (registered.Add(peer.m_rpc))
					{
						peer.m_rpc.Register<string>("ValheimMoments_ClipRelay_v1", (Action<ZRpc, string>)Receive);
					}
				}
			}
			registered.RemoveWhere((ZRpc rpc) => !live.Contains(rpc));
			List<ZRpc> list = new List<ZRpc>();
			foreach (KeyValuePair<ZRpc, double> item in nextOffer)
			{
				if (!live.Contains(item.Key))
				{
					list.Add(item.Key);
				}
			}
			foreach (ZRpc item2 in list)
			{
				nextOffer.Remove(item2);
			}
			if (source != null && (!live.Contains(source) || now > incomingDeadline))
			{
				incoming = null;
				source = null;
			}
			if (target != null && (!live.Contains(target) || now > outgoingDeadline))
			{
				EndOutgoing("Transfer ended or host unavailable; local clip retained.");
			}
			if (preparation != null && preparation.IsCompleted)
			{
				Task<byte[]> task = preparation;
				preparation = null;
				try
				{
					byte[] result = task.GetAwaiter().GetResult();
					if (target != null)
					{
						outgoing = result;
						outgoingDeadline = now + 10.0;
						Send(target, "B|" + outgoingId + "|" + offeredKind + "|" + result.Length + "|" + RelayProtocol.Text(offeredMessage));
					}
				}
				catch
				{
					EndOutgoing("Clip could not be read or exceeds 10 MiB; local copy retained.");
				}
			}
			if (outgoing == null || awaitingOffer || waitingResult || sent != acknowledged || !(now >= nextSend))
			{
				return;
			}
			try
			{
				int val = outgoing.Length - sent;
				byte[] array = new byte[Math.Min(16384, val)];
				int num = array.Length;
				if (num == 0)
				{
					EndOutgoing("Clip could not be read; local copy retained.");
					return;
				}
				Buffer.BlockCopy(outgoing, sent, array, 0, num);
				int num2 = sent;
				sent += num;
				nextSend = now + 0.05;
				Send(target, "C|" + outgoingId + "|" + num2 + "|" + Convert.ToBase64String(array));
			}
			catch
			{
				EndOutgoing("Clip transfer failed; local copy retained.");
			}
		}

		internal bool Offer(ZNet capturedSession, string file, string kind, string message)
		{
			if (disposed || (Object)(object)session == (Object)null || session != capturedSession || session.IsServer() || outgoing != null || preparation != null || target != null)
			{
				return false;
			}
			ZNetPeer serverPeer = session.GetServerPeer();
			if (serverPeer == null || !registered.Contains(serverPeer.m_rpc))
			{
				return false;
			}
			try
			{
				target = serverPeer.m_rpc;
				outgoingId = Guid.NewGuid().ToString("N");
				offeredKind = kind;
				offeredMessage = message;
				sent = (acknowledged = 0);
				awaitingOffer = true;
				waitingResult = false;
				outgoingDeadline = now + 10.0;
				preparation = Task.Run(delegate
				{
					using FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
					if (fileStream.Length < 20 || fileStream.Length > 10485760)
					{
						throw new IOException("Clip size outside relay limit");
					}
					byte[] array = new byte[(int)fileStream.Length];
					int num;
					for (int i = 0; i < array.Length; i += num)
					{
						num = fileStream.Read(array, i, array.Length - i);
						if (num == 0)
						{
							throw new EndOfStreamException();
						}
					}
					return array;
				});
				log("Offered clip to host; client webhook and bot-name settings are ignored.");
				return true;
			}
			catch
			{
				EndOutgoing("Could not offer clip to host; local copy retained.");
				return false;
			}
		}

		private void Receive(ZRpc rpc, string packet)
		{
			if (disposed || (Object)(object)session == (Object)null || session != ZNet.instance || !registered.Contains(rpc) || !rpc.IsConnected() || packet == null || packet.Length > 24000)
			{
				return;
			}
			try
			{
				string[] array = packet.Split('|');
				if (array.Length >= 2 && RelayProtocol.ValidId(array[1]))
				{
					if (session.IsServer())
					{
						ReceiveHost(rpc, array);
					}
					else if (target == rpc && array[1] == outgoingId)
					{
						ReceiveClient(array);
					}
				}
			}
			catch
			{
				log("Invalid relay data ignored.");
			}
		}

		private void ReceiveHost(ZRpc rpc, string[] p)
		{
			if (p[0] == "B" && p.Length == 5)
			{
				if (nextOffer.TryGetValue(rpc, out var value) && now < value)
				{
					Send(rpc, "R|" + p[1] + "|0");
					return;
				}
				nextOffer[rpc] = now + 15.0;
				if (source != null || delivering || !RelayProtocol.ValidKind(p[2]) || !allow(p[2]) || !int.TryParse(p[3], out var result))
				{
					Send(rpc, "R|" + p[1] + "|0");
					return;
				}
				string message = RelayProtocol.ReadText(p[4]);
				try
				{
					incoming = new RelayBuffer(p[1], p[2], message, result, limit());
				}
				catch
				{
					Send(rpc, "R|" + p[1] + "|0");
					return;
				}
				source = rpc;
				incomingDeadline = now + 120.0;
				Send(rpc, "A|" + p[1] + "|0");
			}
			else
			{
				if (!(p[0] == "C") || p.Length != 4 || source != rpc || incoming == null || !(incoming.Id == p[1]))
				{
					return;
				}
				if (!allow(incoming.Kind) || !int.TryParse(p[2], out var result2) || !incoming.Add(result2, Convert.FromBase64String(p[3])))
				{
					Send(rpc, "R|" + p[1] + "|0");
					incoming = null;
					source = null;
					return;
				}
				if (!incoming.Complete)
				{
					Send(rpc, "A|" + p[1] + "|" + incoming.Received);
					return;
				}
				RelayBuffer complete = incoming;
				incoming = null;
				source = null;
				if (complete.ValidWebP())
				{
					delivering = true;
					deliveryPeer = rpc;
					ZNet origin = session;
					string arg = "Connected player";
					foreach (ZNetPeer peer in session.GetPeers())
					{
						if (peer.m_rpc == rpc)
						{
							arg = peer.m_playerName;
							break;
						}
					}
					Send(rpc, "A|" + p[1] + "|" + complete.Received);
					try
					{
						deliver(complete, arg, delegate(bool success)
						{
							delivering = false;
							deliveryPeer = null;
							if (!disposed && origin == session && registered.Contains(rpc))
							{
								Send(rpc, "R|" + complete.Id + "|" + (success ? "1" : "0"));
							}
						});
						return;
					}
					catch
					{
						delivering = false;
						deliveryPeer = null;
						Send(rpc, "R|" + complete.Id + "|0");
						return;
					}
				}
				Send(rpc, "R|" + p[1] + "|0");
			}
		}

		private void ReceiveClient(string[] p)
		{
			if (outgoing == null || p.Length != 3)
			{
				return;
			}
			int result;
			if (p[0] == "R")
			{
				EndOutgoing((p[2] == "1") ? "Host uploaded clip to Discord; local copy retained." : "Host declined or could not deliver clip; local copy retained.");
			}
			else if (!(p[0] != "A") && int.TryParse(p[2], out result) && result == sent)
			{
				acknowledged = result;
				awaitingOffer = false;
				if (sent == outgoing.Length)
				{
					waitingResult = true;
					outgoingDeadline = now + 90.0;
				}
				else
				{
					outgoingDeadline = now + 15.0;
				}
			}
		}

		private static void Send(ZRpc rpc, string packet)
		{
			rpc.Invoke("ValheimMoments_ClipRelay_v1", new object[1] { packet });
		}

		internal void StopSending()
		{
			if (target != null)
			{
				EndOutgoing("Client relay disabled; local clip retained.");
			}
		}

		private void EndOutgoing(string reason)
		{
			outgoing = null;
			target = null;
			outgoingId = null;
			log(reason);
		}

		private void Reset()
		{
			if (target != null)
			{
				EndOutgoing("Session changed; local clip retained.");
			}
			incoming = null;
			source = null;
			registered.Clear();
			nextOffer.Clear();
		}

		public void Dispose()
		{
			disposed = true;
			Reset();
			session = null;
		}
	}
	internal static class DeathCause
	{
		private static readonly FieldInfo LastHit = typeof(Character).GetField("m_lastHit", BindingFlags.Instance | BindingFlags.NonPublic);

		internal static string Read(Player player)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Invalid comparison between Unknown and I4
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Invalid comparison between Unknown and I4
			//IL_0061: 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)
			try
			{
				object? obj = LastHit?.GetValue(player);
				HitData val = (HitData)((obj is HitData) ? obj : null);
				if (val == null)
				{
					return "unknown cause";
				}
				if ((int)val.m_hitType == 1 || (int)val.m_hitType == 2 || (int)val.m_hitType == 0)
				{
					Character attacker = val.GetAttacker();
					if ((Object)(object)attacker != (Object)null)
					{
						string hoverName = attacker.GetHoverName();
						if (!string.IsNullOrWhiteSpace(hoverName))
						{
							return hoverName;
						}
					}
				}
				return Label(val.m_hitType);
			}
			catch
			{
				return "unknown cause";
			}
		}

		internal static string Label(HitType type)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected I4, but got Unknown
			return (type - 1) switch
			{
				0 => "an enemy (attacker unavailable)", 
				1 => "another player (attacker unavailable)", 
				2 => "fall damage", 
				3 => "drowning", 
				4 => "burning", 
				5 => "freezing", 
				6 => "poison", 
				7 => "water damage", 
				8 => "smoke inhalation", 
				9 => "the edge of the world", 
				10 => "an impact", 
				11 => "a cart", 
				12 => "a falling tree", 
				13 => "self-inflicted damage", 
				14 => "structural damage", 
				15 => "a turret", 
				16 => "a boat", 
				17 => "a falling stalactite", 
				18 => "a catapult", 
				19 => "cinder fire", 
				20 => "the Ashlands ocean", 
				21 => "Ashlands lava", 
				22 => "an incinerator", 
				23 => "a drawbridge", 
				_ => "unknown cause", 
			};
		}
	}
	internal static class DiscordRouting
	{
		internal static bool CanSubmit(object capturedSession, bool capturedAsHost, object currentSession, bool currentlyHost)
		{
			if (capturedSession != null && capturedAsHost && currentlyHost)
			{
				return capturedSession == currentSession;
			}
			return false;
		}

		internal static string Destination(string kind, string fallback, bool bossOverride, string boss, bool lootOverride, string loot, bool deathOverride, string death)
		{
			if (kind == "boss" && bossOverride)
			{
				return boss;
			}
			if (kind == "loot" && lootOverride)
			{
				return loot;
			}
			if (kind == "death" && deathOverride)
			{
				return death;
			}
			return fallback;
		}
	}
	internal sealed class DiscordOptions
	{
		internal string WebhookUrl;

		internal string Username;

		internal string Message = "Valheim moment";

		internal bool SaveLocalCopy;

		internal long MaxUploadBytes = 10485760L;
	}
	internal sealed class UploadResult
	{
		internal bool Success;

		internal string Message;

		internal static UploadResult Fail(string message)
		{
			return new UploadResult
			{
				Message = message + " Local clip retained."
			};
		}
	}
	internal static class DiscordWebhook
	{
		[DataContract]
		private sealed class Payload
		{
			[DataMember]
			public string username;

			[DataMember]
			public string content;

			[DataMember]
			public Mentions allowed_mentions = new Mentions();
		}

		[DataContract]
		private sealed class Mentions
		{
			[DataMember]
			public string[] parse = new string[0];
		}

		[DataContract]
		private sealed class RateLimit
		{
			[DataMember(IsRequired = true)]
			public double retry_after { get; set; }
		}

		internal static bool TryEndpoint(string value, out Uri endpoint)
		{
			endpoint = null;
			if (string.IsNullOrWhiteSpace(value) || !Uri.TryCreate(value.Trim(), UriKind.Absolute, out Uri result))
			{
				return false;
			}
			if (result.Scheme != "https" || result.Port != 443 || result.UserInfo.Length != 0 || result.Fragment.Length != 0)
			{
				return false;
			}
			if (result.Host != "discord.com" && result.Host != "discordapp.com" && result.Host != "canary.discord.com" && result.Host != "ptb.discord.com")
			{
				return false;
			}
			if (!Regex.IsMatch(result.AbsolutePath, "^/api/(?:v[0-9]+/)?webhooks/[0-9]+/[A-Za-z0-9_-]+$"))
			{
				return false;
			}
			if (result.Query.Length != 0 && !Regex.IsMatch(result.Query, "^\\?thread_id=[0-9]+$"))
			{
				return false;
			}
			endpoint = new Uri(result.GetLeftPart(UriPartial.Path) + ((result.Query.Length == 0) ? "?" : (result.Query + "&")) + "wait=true");
			return true;
		}

		internal static async Task<UploadResult> UploadAsync(string file, DiscordOptions options, CancellationToken stop, HttpMessageHandler testHandler = null)
		{
			_ = 2;
			try
			{
				if (!TryEndpoint(options.WebhookUrl, out var endpoint))
				{
					return UploadResult.Fail("Missing or invalid Discord webhook configuration.");
				}
				if (string.IsNullOrWhiteSpace(options.Username) || options.Username.Length > 80)
				{
					return UploadResult.Fail("Discord username must contain 1–80 characters.");
				}
				long length = new FileInfo(file).Length;
				if (length == 0L)
				{
					return UploadResult.Fail("Clip is empty.");
				}
				if (options.MaxUploadBytes < 1 || length > options.MaxUploadBytes)
				{
					return UploadResult.Fail("Clip exceeds the configured Discord upload limit (" + length + " bytes).");
				}
				using (CancellationTokenSource timeout = CancellationTokenSource.CreateLinkedTokenSource(new CancellationToken[1] { stop }))
				{
					using HttpClient client = new HttpClient(testHandler ?? new HttpClientHandler
					{
						AllowAutoRedirect = false
					});
					timeout.CancelAfter(TimeSpan.FromSeconds(75.0));
					client.Timeout = Timeout.InfiniteTimeSpan;
					client.DefaultRequestHeaders.UserAgent.ParseAdd("ValheimMoments/0.8.1");
					for (int attempt = 0; attempt < 3; attempt++)
					{
						using MultipartFormDataContent multipart = new MultipartFormDataContent();
						using FileStream stream = File.OpenRead(file);
						using (MemoryStream memoryStream = new MemoryStream())
						{
							new DataContractJsonSerializer(typeof(Payload)).WriteObject(memoryStream, new Payload
							{
								username = options.Username,
								content = options.Message
							});
							ByteArrayContent byteArrayContent = new ByteArrayContent(memoryStream.ToArray());
							byteArrayContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
							multipart.Add(byteArrayContent, "payload_json");
						}
						StreamContent streamContent = new StreamContent(stream);
						streamContent.Headers.ContentType = new MediaTypeHeaderValue("image/webp");
						multipart.Add(streamContent, "files[0]", "valheim-moment.webp");
						using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, endpoint)
						{
							Content = multipart
						};
						using HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, timeout.Token).ConfigureAwait(continueOnCapturedContext: false);
						int statusCode = (int)response.StatusCode;
						switch (statusCode)
						{
						case 200:
							stream.Dispose();
							if (!options.SaveLocalCopy)
							{
								try
								{
									File.Delete(file);
								}
								catch
								{
									return new UploadResult
									{
										Success = true,
										Message = "Uploaded to Discord; local copy could not be removed."
									};
								}
							}
							return new UploadResult
							{
								Success = true,
								Message = (options.SaveLocalCopy ? "Uploaded to Discord; local copy retained." : "Uploaded to Discord; local copy removed.")
							};
						case 429:
						{
							double num = await RetrySeconds(response, timeout.Token).ConfigureAwait(continueOnCapturedContext: false);
							if (attempt == 2 || double.IsNaN(num) || double.IsInfinity(num) || num < 0.0 || num > 30.0)
							{
								return UploadResult.Fail("Discord rate limited the upload; retry budget exhausted or wait exceeds 30 seconds.");
							}
							await Task.Delay(TimeSpan.FromSeconds(Math.Max(0.05, num)), timeout.Token).ConfigureAwait(continueOnCapturedContext: false);
							break;
						}
						case 413:
							return UploadResult.Fail("Discord rejected the attachment as too large.");
						case 401:
						case 403:
						case 404:
							return UploadResult.Fail("Discord webhook is invalid, deleted, or not permitted.");
						default:
							return UploadResult.Fail("Discord rejected the upload (HTTP " + statusCode + ").");
						}
					}
				}
				return UploadResult.Fail("Discord upload did not complete.");
			}
			catch (OperationCanceledException)
			{
				return UploadResult.Fail(stop.IsCancellationRequested ? "Discord upload cancelled." : "Discord upload timed out.");
			}
			catch
			{
				return UploadResult.Fail("Discord upload failed (network, file access, or service error).");
			}
		}

		private static async Task<double> RetrySeconds(HttpResponseMessage response, CancellationToken token)
		{
			if (response.Headers.RetryAfter != null)
			{
				if (response.Headers.RetryAfter.Delta.HasValue)
				{
					return response.Headers.RetryAfter.Delta.Value.TotalSeconds;
				}
				if (response.Headers.RetryAfter.Date.HasValue)
				{
					return Math.Max(0.0, (response.Headers.RetryAfter.Date.Value - DateTimeOffset.UtcNow).TotalSeconds);
				}
			}
			using Stream source = await response.Content.ReadAsStreamAsync().ConfigureAwait(continueOnCapturedContext: false);
			using MemoryStream json = new MemoryStream();
			byte[] bytes = new byte[1024];
			int num;
			while ((num = await source.ReadAsync(bytes, 0, bytes.Length, token).ConfigureAwait(continueOnCapturedContext: false)) > 0)
			{
				if (json.Length + num > 8192)
				{
					return double.NaN;
				}
				json.Write(bytes, 0, num);
			}
			json.Position = 0L;
			return ((RateLimit)new DataContractJsonSerializer(typeof(RateLimit)).ReadObject(json)).retry_after;
		}
	}
	internal static class EncoderClient
	{
		internal static string Encode(CaptureBuffer.Clip clip, string exe, string output, int width, int height, int quality, bool flip, CancellationToken cancellation)
		{
			ProcessStartInfo startInfo = new ProcessStartInfo(exe, "\"" + output + "\"")
			{
				UseShellExecute = false,
				CreateNoWindow = true,
				WindowStyle = ProcessWindowStyle.Hidden,
				RedirectStandardInput = true,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				WorkingDirectory = Path.GetDirectoryName(exe)
			};
			Process process = new Process
			{
				StartInfo = startInfo
			};
			try
			{
				process.Start();
				try
				{
					process.PriorityClass = ProcessPriorityClass.BelowNormal;
				}
				catch
				{
				}
				Task<string> task = process.StandardOutput.ReadToEndAsync();
				Task<string> task2 = process.StandardError.ReadToEndAsync();
				using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120.0));
				using CancellationTokenSource cancellationTokenSource2 = CancellationTokenSource.CreateLinkedTokenSource(cancellation, cancellationTokenSource.Token);
				using (cancellationTokenSource2.Token.Register(delegate
				{
					try
					{
						if (!process.HasExited)
						{
							process.Kill();
						}
					}
					catch
					{
					}
				}))
				{
					try
					{
						using (BinaryWriter binaryWriter = new BinaryWriter(process.StandardInput.BaseStream))
						{
							binaryWriter.Write(826492246);
							binaryWriter.Write(width);
							binaryWriter.Write(height);
							binaryWriter.Write(clip.Count);
							binaryWriter.Write(quality);
							binaryWriter.Write(flip);
							double timestamp = clip.GetTimestamp(0);
							int num = 0;
							for (int num2 = 0; num2 < clip.Count; num2++)
							{
								cancellationTokenSource2.Token.ThrowIfCancellationRequested();
								double num3 = ((num2 + 1 < clip.Count) ? clip.GetTimestamp(num2 + 1) : clip.EndTime);
								int num4 = Math.Max(num + 1, (int)Math.Round((num3 - timestamp) * 1000.0));
								binaryWriter.Write(num4 - num);
								binaryWriter.Write(clip.GetPixels(num2));
								num = num4;
							}
						}
						process.WaitForExit();
						cancellationTokenSource2.Token.ThrowIfCancellationRequested();
						string result = task2.GetAwaiter().GetResult();
						if (process.ExitCode != 0)
						{
							throw new IOException("Encoder exit " + process.ExitCode + ": " + result.Trim());
						}
						if (!File.Exists(output))
						{
							throw new IOException("Encoder produced no output");
						}
						return task.GetAwaiter().GetResult().Trim();
					}
					finally
					{
						try
						{
							if (!process.HasExited)
							{
								process.Kill();
							}
						}
						catch
						{
						}
						try
						{
							process.WaitForExit(5000);
						}
						catch
						{
						}
						try
						{
							if (File.Exists(output + ".partial"))
							{
								File.Delete(output + ".partial");
							}
						}
						catch
						{
						}
					}
				}
			}
			finally
			{
				if (process != null)
				{
					((IDisposable)process).Dispose();
				}
			}
		}
	}
	internal static class EpicLootAdapter
	{
		private static MethodInfo display;

		private static MethodInfo rarity;

		private static MethodInfo rarityName;

		private static MethodInfo rarityColor;

		private static MethodInfo unidentified;

		private static MethodInfo getMagic;

		private static MethodInfo effectText;

		private static FieldInfo effects;

		private static FieldInfo sockets;

		private static FieldInfo magicRarity;

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

		private static readonly Regex tags = new Regex("<[^>]*>", RegexOptions.Compiled);

		internal static bool Install(Assembly assembly, Harmony harmony)
		{
			//IL_0285: Unknown result type (might be due to invalid IL or missing references)
			//IL_0292: Expected O, but got Unknown
			if (assembly == null)
			{
				return false;
			}
			Type type = assembly.GetType("EpicLoot.API", throwOnError: true);
			Type type2 = assembly.GetType("EpicLoot.MagicItem", throwOnError: true);
			Type type3 = assembly.GetType("EpicLoot.ItemRarity", throwOnError: true);
			Type type4 = assembly.GetType("EpicLoot.MagicItemEffect", throwOnError: true);
			display = Required(type, "GetItemDisplayName", typeof(ItemData));
			rarity = Required(type, "TryGetRarity", typeof(ItemData), typeof(int).MakeByRefType());
			rarityName = Required(type, "GetRarityDisplayNameByIndex", typeof(int));
			rarityColor = Required(type, "GetRarityColorByIndex", typeof(int));
			unidentified = Required(type, "IsUnidentified", typeof(ItemData));
			getMagic = Required(assembly.GetType("EpicLoot.ItemDataExtensions", throwOnError: true), "GetMagicItem", typeof(ItemData));
			effectText = Required(type2, "GetEffectText", type4, type3, typeof(bool), typeof(string));
			effects = type2.GetField("Effects");
			sockets = type2.GetField("SocketCount");
			magicRarity = type2.GetField("Rarity");
			if (effects == null || sockets == null || magicRarity == null)
			{
				throw new MissingFieldException("Epic Loot magic item layout changed");
			}
			ranks.Clear();
			foreach (object value in Enum.GetValues(type3))
			{
				ranks.Add(Convert.ToInt32(value));
			}
			BossLootFilter.SetRarities(type3);
			int num = 0;
			MethodInfo[] methods = assembly.GetType("EpicLoot.LootRoller", throwOnError: true).GetMethods(BindingFlags.Static | BindingFlags.Public);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name == "RollLootTableAndSpawnObjects" && methodInfo.ReturnType == typeof(List<GameObject>))
				{
					harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(EpicLootAdapter), "AfterSpawn", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num++;
				}
			}
			if (num != 2)
			{
				throw new MissingMethodException("Expected two Epic Loot spawn-list overloads");
			}
			BossLootDetector.EnableEpic(harmony);
			return true;
		}

		private static MethodInfo Required(Type type, string name, params Type[] args)
		{
			return type.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null) ?? throw new MissingMethodException(type.FullName, name);
		}

		private static void AfterSpawn(List<GameObject> __result)
		{
			BossLootDetector.RecordEpic(__result);
		}

		internal static string Plain(string value, int max = 256)
		{
			string text = tags.Replace(value ?? "", "").Trim();
			if (text.Length <= max)
			{
				return text;
			}
			return text.Substring(0, char.IsHighSurrogate(text[max - 1]) ? (max - 1) : max);
		}

		internal static LootItem Read(ItemData item, string id)
		{
			LootItem lootItem = new LootItem();
			lootItem.Id = Plain(id, 128);
			lootItem.Name = Plain((string)display.Invoke(null, new object[1] { item }));
			lootItem.Quantity = item.m_stack;
			LootItem lootItem2 = lootItem;
			object[] array = new object[2] { item, 0 };
			if ((bool)rarity.Invoke(null, array) && ranks.Contains((int)array[1]))
			{
				lootItem2.Rank = (int)array[1];
				lootItem2.Rarity = Plain((string)rarityName.Invoke(null, new object[1] { lootItem2.Rank }), 64);
				lootItem2.Color = Plain((string)rarityColor.Invoke(null, new object[1] { lootItem2.Rank }), 16);
			}
			lootItem2.Unidentified = (bool)unidentified.Invoke(null, new object[1] { item });
			if (!lootItem2.Unidentified)
			{
				object obj = getMagic.Invoke(null, new object[1] { item });
				if (obj != null)
				{
					lootItem2.Sockets = Math.Max(0, Math.Min(64, (int)sockets.GetValue(obj)));
					List<string> list = new List<string>();
					foreach (object item2 in (IEnumerable)effects.GetValue(obj))
					{
						if (list.Count >= 12)
						{
							break;
						}
						list.Add(Plain((string)effectText.Invoke(null, new object[4]
						{
							item2,
							magicRarity.GetValue(obj),
							false,
							""
						}), 160));
					}
					lootItem2.Modifiers = Plain(string.Join("\n", list), 1024);
				}
			}
			return lootItem2;
		}
	}
	internal static class EventMessages
	{
		internal static string Heading(string text, int level)
		{
			if (string.IsNullOrWhiteSpace(text))
			{
				return "";
			}
			return new string('#', level) + " " + Regex.Replace(text.TrimStart(), "^#{1,6}[ \\t]+", "");
		}

		internal static string FormatPost(string message)
		{
			string input = (message ?? "Valheim moment").TrimStart();
			input = Regex.Replace(input, "^(Kill credit:|Final blow:)", "**$1**", RegexOptions.IgnoreCase | RegexOptions.Multiline);
			input = Heading(input, 1);
			if (input.Length <= 2000)
			{
				return input;
			}
			return input.Substring(0, char.IsHighSurrogate(input[1999]) ? 1999 : 2000);
		}

		internal static string Loot(string template, string enemy, string player, string loot, string itemCount = "")
		{
			return Boss((string.IsNullOrWhiteSpace(template) ? "Great loot from {enemy}!" : template).Replace("{enemy}", "{boss}"), string.IsNullOrWhiteSpace(enemy) ? "a creature" : enemy, player, BossNameMode.KillCredit, null, loot, itemCount);
		}

		internal static string Boss(string template, string boss, string player, BossNameMode? mode = null, string finalBlow = null, string loot = null, string itemCount = "")
		{
			string text = (string.IsNullOrWhiteSpace(template) ? "\ud83c\udfc6 {boss} defeated!" : template);
			bool flag = mode == BossNameMode.KillCredit || mode == BossNameMode.Both;
			bool flag2 = mode == BossNameMode.FinalBlow || mode == BossNameMode.Both;
			string text2 = (string.IsNullOrWhiteSpace(player) ? "A player" : player);
			string text3 = (string.IsNullOrWhiteSpace(finalBlow) ? "unavailable" : finalBlow);
			string text4 = text.Replace("{boss}", string.IsNullOrWhiteSpace(boss) ? "Boss" : boss).Replace("{player}", (mode == BossNameMode.FinalBlow) ? text3 : text2).Replace("{credit}", flag ? text2 : "")
				.Replace("{killer}", flag2 ? text3 : "")
				.Replace("{loot}", loot ?? "")
				.Replace("{item_count}", (loot == null) ? "" : itemCount);
			if (flag && !text.Contains("{credit}") && !text.Contains("{player}"))
			{
				text4 = text4 + "\nKill credit: " + text2;
			}
			if (flag2 && !text.Contains("{killer}") && (mode != BossNameMode.FinalBlow || !text.Contains("{player}")))
			{
				text4 = text4 + "\nFinal blow: " + text3;
			}
			if (loot != null && !text.Contains("{loot}"))
			{
				text4 = text4 + "\n\n" + loot;
			}
			if (text4.Length <= 2000)
			{
				return text4;
			}
			return text4.Substring(0, char.IsHighSurrogate(text4[1999]) ? 1999 : 2000);
		}

		internal static string Death(string template, bool includeName, string nameOverride, string characterName, bool includeCause = false, string cause = "unknown cause")
		{
			string text = ((!includeName) ? "A player" : (string.IsNullOrWhiteSpace(nameOverride) ? characterName : nameOverride));
			if (string.IsNullOrWhiteSpace(text))
			{
				text = "A player";
			}
			string text2 = (string.IsNullOrWhiteSpace(template) ? "\ud83d\udc80 {player} died!" : template);
			string text3 = (string.IsNullOrWhiteSpace(cause) ? "unknown cause" : cause);
			string text4 = text2.Replace("{cause}", includeCause ? text3 : "").Replace("{player}", text);
			if (includeCause && !text2.Contains("{cause}"))
			{
				text4 = text4 + "\nCause: " + text3;
			}
			if (text4.Length <= 2000)
			{
				return text4;
			}
			return text4[..(char.IsHighSurrogate(text4[1999]) ? 1999 : 2000)];
		}
	}
	internal sealed class LootHighlights
	{
		private sealed class Candidate
		{
			internal BossKill Kill;

			internal double Deadline;
		}

		private readonly List<Candidate> pending = new List<Candidate>();

		internal int Count => pending.Count;

		internal void Add(BossKill kill, double now, double wait)
		{
			if (kill == null || kill.BossNumber > 0)
			{
				return;
			}
			foreach (Candidate item in pending)
			{
				if (item.Kill == kill || (kill.Loot != null && item.Kill.Loot == kill.Loot))
				{
					return;
				}
			}
			if (pending.Count == 64)
			{
				pending.RemoveAt(0);
			}
			pending.Add(new Candidate
			{
				Kill = kill,
				Deadline = now + (double.IsNaN(wait) ? 12.0 : Math.Max(0.0, Math.Min(25.0, wait)))
			});
		}

		internal void Poll(double now, string minimumName, Action<BossKill> accept)
		{
			if (!BossLootFilter.TryRank(minimumName, out var rank))
			{
				Clear();
				return;
			}
			int num = 0;
			while (num < pending.Count)
			{
				Candidate candidate = pending[num];
				bool flag = false;
				if (candidate.Kill.Loot != null)
				{
					foreach (LootItem item in candidate.Kill.Loot.Items)
					{
						if (item.Quantity > 0 && item.Rank >= rank)
						{
							flag = true;
							break;
						}
					}
				}
				if (flag && now <= candidate.Deadline)
				{
					pending.RemoveAt(num);
					accept(candidate.Kill);
				}
				else if (now >= candidate.Deadline || (candidate.Kill.Loot != null && candidate.Kill.Loot.Observed && !candidate.Kill.Loot.Pending))
				{
					pending.RemoveAt(num);
				}
				else
				{
					num++;
				}
			}
		}

		internal void Clear()
		{
			pending.Clear();
		}
	}
	internal static class PlayerDeathDetector
	{
		private sealed class DeathState
		{
			internal bool WasAlive;

			internal string Cause;
		}

		internal static Action<Player, string> OnLocalDeath;

		internal static Action OnError;

		internal static void Install(Harmony harmony)
		{
			//IL_005b: 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_007d: Expected O, but got Unknown
			//IL_007d: Expected O, but got Unknown
			MethodInfo method = typeof(Player).GetMethod("OnDeath", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
			if (method == null || method.ReturnType != typeof(void))
			{
				throw new MissingMethodException("Expected Player.OnDeath() was not found.");
			}
			harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(PlayerDeathDetector), "Prefix", (Type[])null), new HarmonyMethod(typeof(PlayerDeathDetector), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void Prefix(Player __instance, out DeathState __state)
		{
			__state = null;
			try
			{
				if ((Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && !((Character)__instance).IsDead())
				{
					__state = new DeathState
					{
						WasAlive = true,
						Cause = DeathCause.Read(__instance)
					};
				}
			}
			catch
			{
				ReportError();
			}
		}

		private static void Postfix(Player __instance, DeathState __state)
		{
			try
			{
				if (__state != null && __state.WasAlive && (Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer && ((Character)__instance).IsDead())
				{
					OnLocalDeath?.Invoke(__instance, __state.Cause);
				}
			}
			catch
			{
				ReportError();
			}
		}

		private static void ReportError()
		{
			try
			{
				OnError?.Invoke();
			}
			catch
			{
			}
		}
	}
	[BepInPlugin("local.valheimeventclips", "Valheim Moments", "0.8.1")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		private sealed class ReadbackSlot
		{
			internal RenderTexture Target;

			internal NativeArray<byte> Pixels;

			internal AsyncGPUReadbackRequest Request;

			internal double Submitted;
		}

		private readonly Stopwatch clock = Stopwatch.StartNew();

		private readonly Queue<ReadbackSlot> free = new Queue<ReadbackSlot>(3);

		private readonly Queue<ReadbackSlot> pending = new Queue<ReadbackSlot>(3);

		private readonly List<ReadbackSlot> allSlots = new List<ReadbackSlot>(3);

		private readonly CancellationTokenSource shutdown = new CancellationTokenSource();

		private CaptureBuffer history;

		private CaptureBuffer.Clip encodingClip;

		private Task<string> encoding;

		private Task<UploadResult> upload;

		private ConfigEntry<bool> discordEnabled;

		private ConfigEntry<bool> uploadClips;

		private ConfigEntry<bool> saveLocalCopy;

		private ConfigEntry<string> webhookUrl;

		private ConfigEntry<string> discordUsername;

		private ConfigEntry<bool> useBossWebhook;

		private ConfigEntry<bool> useLootWebhook;

		private ConfigEntry<bool> useDeathWebhook;

		private ConfigEntry<string> bossWebhook;

		private ConfigEntry<string> lootWebhook;

		private ConfigEntry<string> deathWebhook;

		private ZNet pendingSession;

		private ZNet activeSession;

		private ZNet uploadSession;

		private bool pendingAsHost;

		private bool activeAsHost;

		private string activeKind;

		private CancellationTokenSource uploadCancellation;

		private ClipRelay relay;

		private ConfigEntry<bool> relayEnabled;

		private Action<bool> relayCompletion;

		private string relayDirectory;

		private ConfigEntry<int> uploadLimitMiB;

		private ConfigEntry<bool> manualTrigger;

		private ConfigEntry<bool> deathTrigger;

		private ConfigEntry<bool> deathEnabled;

		private ConfigEntry<bool> includePlayerName;

		private ConfigEntry<bool> includeCause;

		private ConfigEntry<string> deathMessage;

		private ConfigEntry<string> playerNameOverride;

		private Harmony deathHarmony;

		private Harmony bossHarmony;

		private ConfigEntry<bool> bossTrigger;

		private ConfigEntry<bool> bossEnabled;

		private ConfigEntry<bool> firstBossOnly;

		private ConfigEntry<string> bossMessage;

		private double bossPostSeconds;

		private ConfigEntry<BossNameMode> bossNameMode;

		private Harmony attributionHarmony;

		private Harmony lootHarmony;

		private ConfigEntry<bool> showBossLoot;

		private ConfigEntry<bool> showLootQuantity;

		private ConfigEntry<int> maxLootItems;

		private ConfigEntry<string> lootHeader;

		private BossKill pendingBoss;

		private BossKill activeBoss;

		private ConfigEntry<bool> showRarity;

		private ConfigEntry<bool> showModifiers;

		private ConfigEntry<bool> showSockets;

		private ConfigEntry<bool> showUnidentified;

		private ConfigEntry<double> lootWaitSeconds;

		private double lootDeadline;

		private Harmony epicHarmony;

		private ConfigEntry<bool> filterBossLoot;

		private ConfigEntry<bool> firstKillBypassesRarity;

		private ConfigEntry<string> minimumBossRarity;

		private CaptureBuffer.Clip waitingForLoot;

		private readonly LootHighlights lootHighlights = new LootHighlights();

		private ConfigEntry<bool> lootTrigger;

		private ConfigEntry<bool> lootEnabled;

		private ConfigEntry<string> minimumLootRarity;

		private ConfigEntry<string> highlightMessage;

		private ConfigEntry<double> highlightWaitSeconds;

		private double lootPostSeconds;

		private bool epicReady;

		private ConfigEntry<bool> highlightQuantity;

		private ConfigEntry<bool> highlightRarity;

		private ConfigEntry<bool> highlightModifiers;

		private ConfigEntry<bool> highlightSockets;

		private ConfigEntry<bool> highlightUnidentified;

		private ConfigEntry<int> highlightMaxItems;

		private ConfigEntry<string> highlightHeader;

		private string pendingKind = "manual";

		private string pendingMessage = "Valheim moment";

		private string activeMessage;

		private RenderTexture screen;

		private byte[] scratch;

		private ConfigEntry<bool> captureEnabled;

		private ConfigEntry<bool> timing;

		private ConfigEntry<bool> flip;

		private ConfigEntry<KeyCode> captureKey;

		private ConfigEntry<KeyCode> toggleKey;

		private int width;

		private int height;

		private int fps;

		private int quality;

		private string encoderPath;

		private string outputDirectory;

		private string activeOutput;

		private bool initialized;

		private bool stopped;

		private bool paused;

		private bool historyCleared;

		private double nextCapture;

		private double lastReport;

		private double lastUpdate;

		private double submitMs;

		private double copyMs;

		private double latencyMs;

		private double maxSubmitMs;

		private double maxCopyMs;

		private double maxFrameMs;

		private double frameMs;

		private int submitted;

		private int received;

		private int skipped;

		private int errors;

		private int updateCount;

		private int consecutiveErrors;

		private void Start()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			if (!initialized || stopped)
			{
				return;
			}
			try
			{
				Assembly assembly = null;
				Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly assembly2 in assemblies)
				{
					if (assembly2.GetName().Name == "EpicLoot")
					{
						assembly = assembly2;
						break;
					}
				}
				epicHarmony = new Harmony("local.valheimeventclips.epicloot");
				epicReady = EpicLootAdapter.Install(assembly, epicHarmony);
				((BaseUnityPlugin)this).Logger.LogInfo((object)(epicReady ? "[Loot] Optional Epic Loot adapter installed." : "[Loot] Epic Loot absent; vanilla summaries enabled."));
			}
			catch (Exception ex)
			{
				try
				{
					Harmony obj = epicHarmony;
					if (obj != null)
					{
						obj.UnpatchSelf();
					}
				}
				catch
				{
				}
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[Loot] Epic Loot adapter unavailable: " + ex.GetType().Name + ". Vanilla summaries remain enabled."));
			}
		}

		private T Setting<T>(string key, T value, string help)
		{
			return ((BaseUnityPlugin)this).Config.Bind<T>("Capture", key, value, help + " Restart Valheim after changing.").Value;
		}

		private void Awake()
		{
			//IL_091c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0922: Invalid comparison between Unknown and I4
			//IL_0d5b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bd2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0bdc: Expected O, but got Unknown
			//IL_0c6e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0c78: Expected O, but got Unknown
			//IL_0cd2: Unknown result type (might be due to invalid IL or missing references)
			//IL_0cdc: Expected O, but got Unknown
			//IL_0b29: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b2e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b58: Unknown result type (might be due to invalid IL or missing references)
			//IL_0b62: Expected O, but got Unknown
			try
			{
				captureEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Capture", "Enabled", true, "Enable recording. F9 toggles recording for baseline comparison.");
				captureKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Capture", "ManualCaptureKey", (KeyCode)291, "Save recent gameplay plus post-event footage locally.");
				toggleKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Capture", "ToggleCaptureKey", (KeyCode)290, "Pause/resume recording to compare game performance.");
				timing = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogCaptureTiming", true, "Log aggregate CPU timing, readback latency and frame counts every 10 seconds.");
				flip = ((BaseUnityPlugin)this).Config.Bind<bool>("Capture", "FlipVertically", false, "Enable if the test WebP is upside down on your graphics backend.");
				discordEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "Enabled", false, "Host/single-player only: enable Discord delivery. Remote clients send clips to the host and never use local webhook settings.");
				relayEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "EnableClientRelay", true, "Host: accept clips from connected clients. Client: allow sending clips to the host. Both sides need this version. Host Discord.Enabled and event trigger switches also apply. Client copies are always retained.");
				uploadClips = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "UploadClips", true, "Upload newly completed clips when Discord is enabled.");
				saveLocalCopy = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "SaveLocalCopy", true, "Keep uploaded clips locally. Failed/skipped uploads always retain the clip.");
				webhookUrl = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "WebhookURL", "", "Secret: enter locally, never share this config. HTTPS Discord webhook; optional thread_id query.");
				discordUsername = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "Username", "Valheim Moments", "Host/single-player only: bot display name, 1–80 characters. Remote client values are ignored.");
				useBossWebhook = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "UseBossKillWebhook", false, "Host only: route boss clips to BossKillWebhookURL; when off, use WebhookURL.");
				bossWebhook = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "BossKillWebhookURL", "", "Host-only secret: optional boss destination. An enabled but invalid override keeps the clip locally; it does not silently change channels.");
				useLootWebhook = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "UseGoodLootWebhook", false, "Host only: route ordinary-loot clips to GoodLootWebhookURL; when off, use WebhookURL.");
				lootWebhook = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "GoodLootWebhookURL", "", "Host-only secret: optional ordinary-loot destination.");
				useDeathWebhook = ((BaseUnityPlugin)this).Config.Bind<bool>("Discord", "UsePlayerDeathWebhook", false, "Host only: route death clips to PlayerDeathWebhookURL; when off, use WebhookURL.");
				deathWebhook = ((BaseUnityPlugin)this).Config.Bind<string>("Discord", "PlayerDeathWebhookURL", "", "Host-only secret: optional player-death destination.");
				uploadLimitMiB = ((BaseUnityPlugin)this).Config.Bind<int>("Discord", "MaxUploadMiB", 10, "Per-file upload guard. Discord can impose its own limit. Allowed range 1–100.");
				manualTrigger = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "ManualCapture", true, "Enable the manual hotkey independently of automatic events.");
				deathTrigger = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "PlayerDeath", true, "Enable local player death captures.");
				deathEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Player Death", "Enabled", true, "Enable this event. Triggers.PlayerDeath must also be enabled.");
				deathMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Player Death", "Message", "\ud83d\udc80 {player} died!", "Discord death message. Placeholders: {player}, {cause}. Cause appends automatically when enabled and no placeholder is present.");
				includeCause = ((BaseUnityPlugin)this).Config.Bind<bool>("Player Death", "IncludeCause", true, "Include the recorded attacker or environmental cause; unknown when unavailable.");
				includePlayerName = ((BaseUnityPlugin)this).Config.Bind<bool>("Player Death", "IncludePlayerName", true, "Replace {player} with the character name/override; otherwise use A player.");
				playerNameOverride = ((BaseUnityPlugin)this).Config.Bind<string>("Player Death", "PlayerNameOverride", "", "Optional display name instead of the character name.");
				bossTrigger = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "BossKill", true, "Capture boss kills credited by Valheim to this character.");
				bossEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "Enabled", true, "Enable boss capture; Triggers.BossKill must also be enabled.");
				firstBossOnly = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "FirstKillOnly", false, "Only capture when this character has no previous kill of this boss in saved game statistics.");
				bossMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Boss Kill", "Message", "\ud83c\udfc6 {boss} defeated!", "Discord boss message. Supported placeholders: {boss}, {player}. Loot placeholders: {loot}, {item_count}.");
				bossPostSeconds = ((BaseUnityPlugin)this).Config.Bind<double>("Boss Kill", "PostEventSeconds", 4.0, "Seconds to record after a credited boss kill, to show loot dropping. Restart after changing. Longer clips must fit Capture.MemoryBudgetMiB and the encoder's 256 MiB raw-frame limit.").Value;
				bossNameMode = ((BaseUnityPlugin)this).Config.Bind<BossNameMode>("Boss Kill", "PlayerNameMode", BossNameMode.Both, "KillCredit, FinalBlow or Both. Kill credit names this character; final blow names the last-hit player when available. Co-op final-blow sharing requires this version on the client owning the boss. Templates support {credit} and {killer}; selected names append when placeholders are absent.");
				showBossLoot = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "ShowLoot", true, "Show observed vanilla rolls and completed Epic Loot drops when its optional adapter is available.");
				showLootQuantity = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "ShowQuantity", true, "Show item quantities in the boss loot summary.");
				maxLootItems = ((BaseUnityPlugin)this).Config.Bind<int>("Boss Kill", "MaxLootItemsShown", 5, "Maximum entries shown, 1-20; highest verified rarity first, then name. Distinct magic items stay separate.");
				lootHeader = ((BaseUnityPlugin)this).Config.Bind<string>("Boss Kill", "LootHeader", "Generated loot:", "Message placeholders {loot} and {item_count}; count is displayed-data entries before the display limit (grouped vanilla types and individual Epic Loot items).");
				showRarity = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "ShowRarity", true, "Show verified rarity names and approximate color emojis.");
				showModifiers = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "ShowItemModifiers", true, "Show Epic Loot's formatted modifiers for identified items.");
				showSockets = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "ShowItemSockets", true, "Show verified socket counts for identified items.");
				showUnidentified = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "ShowUnidentifiedStatus", true, "Label unidentified items. Hidden modifiers are never exposed.");
				lootWaitSeconds = ((BaseUnityPlugin)this).Config.Bind<double>("Boss Kill", "LootWaitSeconds", 12.0, "Maximum seconds from boss kill to wait for delayed Epic Loot before upload, clamped 0-25. Recording duration remains PostEventSeconds.");
				filterBossLoot = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "OnlyCaptureIfLootMeetsRarity", false, "Only encode/save/upload a boss clip when at least one observed item meets MinimumLootRarity. Preserve kill footage while awaiting drops. Missing/unknown qualifying data skips the clip at the wait deadline.");
				minimumBossRarity = ((BaseUnityPlugin)this).Config.Bind<string>("Boss Kill", "MinimumLootRarity", "Legendary", "None accepts all; otherwise an actual Epic Loot rarity name (0.14.2: Magic, Rare, Epic, Legendary, Mythic, Ancient). Used only when OnlyCaptureIfLootMeetsRarity=true. Unknown names or absent Epic Loot fail closed.");
				firstKillBypassesRarity = ((BaseUnityPlugin)this).Config.Bind<bool>("Boss Kill", "FirstKillBypassesRarity", true, "Always keep this character's first recorded kill of each boss regardless of MinimumLootRarity. Repeat kills still use the rarity filter. FirstKillOnly separately excludes all repeat kills.");
				lootTrigger = ((BaseUnityPlugin)this).Config.Bind<bool>("Triggers", "LootDrop", true, "Enable ordinary-creature loot highlights. Loot Capture.Enabled must also be enabled; bosses use Boss Kill rules exclusively.");
				lootEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Loot Capture", "Enabled", true, "Capture qualifying Epic Loot drops from ordinary kills credited to this character. Requires the optional Epic Loot adapter. No pickup/crafting triggers.");
				minimumLootRarity = ((BaseUnityPlugin)this).Config.Bind<string>("Loot Capture", "MinimumRarity", "Legendary", "Minimum observed rarity: Magic, Rare, Epic, Legendary, Mythic, Ancient. None accepts any observed item. Unknown names skip captures.");
				highlightMessage = ((BaseUnityPlugin)this).Config.Bind<string>("Loot Capture", "Message", "Great loot from {enemy}!", "Placeholders: {enemy}, {player}, {loot}, {item_count}. Loot and kill credit append if omitted. Item display is configured independently in this section.");
				highlightQuantity = ((BaseUnityPlugin)this).Config.Bind<bool>("Loot Capture", "ShowQuantity", showLootQuantity.Value, "Show item quantities in ordinary-loot posts.");
				highlightRarity = ((BaseUnityPlugin)this).Config.Bind<bool>("Loot Capture", "ShowRarity", showRarity.Value, "Show rarity labels and colored markers; does not change MinimumRarity filtering.");
				highlightModifiers = ((BaseUnityPlugin)this).Config.Bind<bool>("Loot Capture", "ShowItemModifiers", showModifiers.Value, "Show identified items' Epic Loot modifier text in ordinary-loot posts.");
				highlightSockets = ((BaseUnityPlugin)this).Config.Bind<bool>("Loot Capture", "ShowItemSockets", showSockets.Value, "Show identified items' socket counts in ordinary-loot posts.");
				highlightUnidentified = ((BaseUnityPlugin)this).Config.Bind<bool>("Loot Capture", "ShowUnidentifiedStatus", showUnidentified.Value, "Label unidentified items. Hidden modifiers and sockets are never revealed.");
				highlightMaxItems = ((BaseUnityPlugin)this).Config.Bind<int>("Loot Capture", "MaxLootItemsShown", maxLootItems.Value, "Maximum displayed entries, 1-20; highest rarity first. Display limits do not affect capture eligibility.");
				highlightHeader = ((BaseUnityPlugin)this).Config.Bind<string>("Loot Capture", "LootHeader", lootHeader.Value, "Header above generated loot in ordinary-loot posts.");
				highlightWaitSeconds = ((BaseUnityPlugin)this).Config.Bind<double>("Loot Capture", "LootWaitSeconds", 12.0, "Wait 0-25 seconds after credited kill for drops. Holds metadata only; up to 64 pending kills.");
				lootPostSeconds = ((BaseUnityPlugin)this).Config.Bind<double>("Loot Capture", "PostEventSeconds", 4.0, "Seconds after observing qualifying loot. Uses rolling pre-event footage before the drop; long ragdoll delays may leave the kill outside the clip. Restart after changing.").Value;
				width = Setting("Width", 640, "Output pixel width, 16–1920.");
				height = Setting("Height", 360, "Output pixel height, 16–1080. Screen is stretched to this aspect ratio.");
				fps = Setting("FPS", 15, "Capture sampling rate, 1–30. Missed captures are skipped.");
				quality = Setting("WebPQuality", 80, "Lossy animated WebP quality, 1–100.");
				double num = Setting("PreEventSeconds", 5.0, "History duration.");
				double num2 = Setting("PostEventSeconds", 2.0, "Post-trigger duration.");
				int num3 = Setting("MemoryBudgetMiB", 192, "Maximum preallocated managed frame pool; excludes GPU and helper memory.");
				string directoryName = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location);
				relayDirectory = Path.Combine(directoryName, "RelayTemp");
				relay = new ClipRelay(CanRelay, () => Math.Max(1, Math.Min(10, uploadLimitMiB.Value)) * 1048576, ReceiveRelayedClip, delegate(string message)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)("[Relay] " + message));
				});
				encoderPath = Path.Combine(directoryName, "Encoder", "ValheimEventClips.Encoder.exe");
				outputDirectory = Path.Combine(directoryName, "Clips");
				if (Application.isBatchMode || (int)SystemInfo.graphicsDeviceType == 4)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[Relay] Host delivery ready; graphics capture disabled on this headless server.");
					return;
				}
				if (!SystemInfo.supportsAsyncGPUReadback)
				{
					throw new NotSupportedException("This graphics backend does not support asynchronous GPU readback.");
				}
				if (!File.Exists(encoderPath))
				{
					throw new FileNotFoundException("Bundled Encoder/ValheimEventClips.Encoder.exe is missing.");
				}
				if (width < 16 || width > 1920 || height < 16 || height > 1080 || fps < 1 || fps > 30 || quality < 1 || quality > 100 || num3 < 16 || num3 > 512)
				{
					throw new ArgumentOutOfRangeException("Capture configuration is outside prototype limits.");
				}
				double num4 = Math.Max(num2, Math.Max(bossPostSeconds, lootPostSeconds));
				if (double.IsNaN(lootPostSeconds) || double.IsInfinity(lootPostSeconds) || lootPostSeconds < 0.0)
				{
					throw new ArgumentOutOfRangeException("Loot Capture.PostEventSeconds");
				}
				if (double.IsNaN(bossPostSeconds) || double.IsInfinity(bossPostSeconds) || bossPostSeconds < 0.0)
				{
					throw new ArgumentOutOfRangeException("Boss Kill.PostEventSeconds");
				}
				if ((double)((long)width * (long)height * 4) * (Math.Ceiling(num * (double)fps) + Math.Ceiling(num4 * (double)fps)) > 268435456.0)
				{
					throw new ArgumentOutOfRangeException("Clip exceeds the encoder's 256 MiB raw-frame limit.");
				}
				history = new CaptureBuffer(width, height, fps, num, num2, (long)num3 * 1024L * 1024, num4);
				scratch = new byte[checked(width * height * 4)];
				for (int num5 = 0; num5 < 3; num5++)
				{
					ReadbackSlot readbackSlot = new ReadbackSlot();
					allSlots.Add(readbackSlot);
					readbackSlot.Target = MakeTarget(width, height);
					readbackSlot.Pixels = new NativeArray<byte>(scratch.Length, (Allocator)4, (NativeArrayOptions)0);
					free.Enqueue(readbackSlot);
				}
				initialized = true;
				try
				{
					deathHarmony = new Harmony("local.valheimeventclips.death");
					PlayerDeathDetector.OnLocalDeath = OnLocalDeath;
					PlayerDeathDetector.OnError = delegate
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"[Death] Could not inspect local death; gameplay was left unchanged.");
					};
					PlayerDeathDetector.Install(deathHarmony);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[Death] Local player death detector installed.");
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("[Death] Detector unavailable: " + ex.GetType().Name + ". Manual capture remains available."));
				}
				try
				{
					bossHarmony = new Harmony("local.valheimeventclips.boss");
					BossKillDetector.OnKill = OnBossKill;
					BossKillDetector.ObserveOrdinary = () => epicReady && lootTrigger.Value && lootEnabled.Value;
					BossKillDetector.OnLootKill = delegate(BossKill kill)
					{
						if (captureEnabled.Value && !paused)
						{
							lootHighlights.Add(kill, clock.Elapsed.TotalSeconds, highlightWaitSeconds.Value);
						}
					};
					BossKillDetector.OnError = delegate
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"[Boss] Unable to verify character kill statistics; boss event skipped.");
					};
					BossKillDetector.Install(bossHarmony);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[Boss] Local kill-credit detector installed.");
				}
				catch (Exception ex2)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("[Boss] Detector unavailable: " + ex2.GetType().Name + ". Other captures remain available."));
				}
				try
				{
					attributionHarmony = new Harmony("local.valheimeventclips.boss.attribution");
					BossAttribution.OnDiagnostic = delegate(string reason)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("[Boss] Final-blow source: " + reason));
					};
					BossAttribution.Install(attributionHarmony);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[Boss] Final-blow attribution installed.");
				}
				catch (Exception ex3)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("[Boss] Final-blow attribution unavailable: " + ex3.GetType().Name));
				}
				try
				{
					lootHarmony = new Harmony("local.valheimeventclips.boss.loot");
					BossLootDetector.OnObserved = delegate(int count)
					{
						((BaseUnityPlugin)this).Logger.LogDebug((object)("[Loot] Observed generated loot; item types=" + count));
					};
					BossLootDetector.OnError = delegate
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)"[Loot] Some loot data unavailable; boss capture remains enabled.");
					};
					BossLootDetector.Install(lootHarmony);
					((BaseUnityPlugin)this).Logger.LogInfo((object)"[Loot] Generated boss loot observer installed.");
				}
				catch (Exception ex4)
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)("[Loot] Observer unavailable: " + ex4.GetType().Name));
				}
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"[Capture] Ready: Unity {Application.unityVersion}, {SystemInfo.graphicsDeviceType}, {width}x{height} at {fps} FPS; pool {(double)history.AllocatedPixelBytes / 1048576.0:F1} MiB. F10 saves; F9 pauses. Output: {outputDirectory}");
				((MonoBehaviour)this).StartCoroutine(CaptureLoop());
			}
			catch (Exception ex5)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("[Capture] Initialization failed: " + ex5.Message));
				StopCapture();
			}
		}

		private static RenderTexture MakeTarget(int w, int h)
		{
			//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_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			RenderTexture val = new RenderTexture(w, h, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2)
			{
				name = "ValheimEventClips",
				antiAliasing = 1,
				useMipMap = false,
				filterMode = (FilterMode)1
			};
			if (!val.Create())
			{
				Object.Destroy((Object)(object)val);
				throw new InvalidOperationException("RenderTexture creation failed");
			}
			return val;
		}

		private void Update()
		{
			//IL_0240: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Unknown result type (might be due to invalid IL or missing references)
			if (stopped)
			{
				return;
			}
			try
			{
				if (relay != null && !relayEnabled.Value)
				{
					relay.StopSending();
				}
				relay?.Tick(clock.Elapsed.TotalSeconds);
				if (uploadCancellation != null && upload != null && !upload.IsCompleted && (!DiscordRouting.CanSubmit(uploadSession, capturedAsHost: true, ZNet.instance, (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer()) || (relayCompletion != null && (!relayEnabled.Value || !discordEnabled.Value || !uploadClips.Value || !relay.DeliveryPeerConnected))))
				{
					uploadCancellation.Cancel();
				}
				if (upload != null && upload.IsCompleted)
				{
					UploadResult result = upload.GetAwaiter().GetResult();
					if (result.Success)
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("[Discord] " + result.Message));
					}
					else
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("[Discord] " + result.Message));
					}
					upload = null;
					uploadCancellation?.Dispose();
					uploadCancellation = null;
					uploadSession = null;
					Action<bool> action = relayCompletion;
					relayCompletion = null;
					action?.Invoke(result.Success);
				}
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)("[Relay] Delivery update failed: " + ex.GetType().Name));
			}
			if (!initialized)
			{
				return;
			}
			try
			{
				double totalSeconds = clock.Elapsed.TotalSeconds;
				if (lastUpdate > 0.0)
				{
					double num = (totalSeconds - lastUpdate) * 1000.0;
					frameMs += num;
					maxFrameMs = Math.Max(maxFrameMs, num);
					updateCount++;
				}
				lastUpdate = totalSeconds;
				if (Input.GetKeyDown(toggleKey.Value))
				{
					paused = !paused;
					((BaseUnityPlugin)this).Logger.LogInfo((object)(paused ? "[Capture] Paused for baseline comparison." : "[Capture] Recording resumed; allow 5 seconds to warm up."));
				}
				DrainReadbacks();
				bool flag = captureEnabled.Value && !paused;
				if (flag && epicReady && lootTrigger.Value && lootEnabled.Value)
				{
					lootHighlights.Poll(totalSeconds, minimumLootRarity.Value, OnLootHighlight);
				}
				else
				{
					lootHighlights.Clear();
				}
				if (!flag)
				{
					if (!historyCleared && pending.Count == 0)
					{
						history.ClearHistory();
						historyCleared = true;
					}
				}
				else
				{
					historyCleared = false;
					if (manualTrigger.Value && Application.isFocused && Input.GetKeyDown(captureKey.Value))
					{
						Trigger("manual", "Valheim moment");
					}
				}
				if (encoding != null && encoding.IsCompleted && (!encoding.IsCompletedSuccessfully || activeBoss?.Loot == null || !activeBoss.Loot.Pending || (activeBoss.BossNumber > 0 && !showBossLoot.Value) || totalSeconds >= lootDeadline))
				{
					try
					{
						((BaseUnityPlugin)this).Logger.LogInfo((object)("[WebP] " + encoding.GetAwaiter().GetResult() + "; saved " + activeOutput));
						StartUpload(activeOutput);
					}
					catch (Exception ex2)
					{
						((BaseUnityPlugin)this).Logger.LogWarning((object)("[WebP] Clip failed: " + ex2.Message));
					}
					encodingClip.Release();
					encodingClip = null;
					encoding = null;
				}
				if (flag && encoding == null)
				{
					CaptureBuffer.Clip clip = history.TryComplete((pending.Count == 0) ? totalSeconds : pending.Peek().Submitted);
					if (clip != null)
					{
						if (clip.Count == 0)
						{
							clip.Release();
							((BaseUnityPlugin)this).Logger.LogWarning((object)"[Capture] No frames available; clip discarded.");
						}
						else if (pendingBoss != null && pendingBoss.BossNumber > 0 && filterBossLoot.Value)
						{
							waitingForLoot = clip;
						}
						else
						{
							StartEncoding(clip);
						}
					}
				}
				if (waitingForLoot != null)
				{
					string reason;
					LootDecision lootDecision = BossLootFilter.Evaluate(pendingBoss?.Loot, filterBossLoot.Value, pendingBoss != null && pendingBoss.FirstKill, firstKillBypassesRarity.Value, minimumBossRarity.Value, totalSeconds >= lootDeadline, out reason);
					if (lootDecision != LootDecision.Wait)
					{
						CaptureBuffer.Clip clip2 = waitingForLoot;
						waitingForLoot = null;
						if (lootDecision == LootDecision.Accept)
						{
							((BaseUnityPlugin)this).Logger.LogInfo((object)("[Loot] Boss clip accepted: " + reason));
							StartEncoding(clip2);
						}
						else
						{
							clip2.Release();
							pendingBoss = null;
							((BaseUnityPlugin)this).Logger.LogInfo((object)("[Loot] Boss clip skipped: " + reason));
						}
					}
				}
				if (timing.Value && totalSeconds - lastReport >= 10.0)
				{
					((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("[Capture] {0}: received={1}, submitted={2}, skipped={3}, errors={4}, buffer={5}; submit CPU avg/max={6:F2}/{7:F2}ms, copy CPU avg/max={8:F2}/{9:F2}ms, readback latency avg={10:F2}ms; game Update avg/max={11:F2}/{12:F2}ms; managed={13:F1}MiB; effective capture FPS={14:F2}", flag ? "recording" : "paused", received, submitted, skipped, errors, history.BufferedFrames, submitMs / (double)Math.Max(1, submitted), maxSubmitMs, copyMs / (double)Math.Max(1, received), maxCopyMs, latencyMs / (double)Math.Max(1, received + errors), frameMs / (double)Math.Max(1, updateCount), maxFrameMs, (double)GC.GetTotalMemory(forceFullCollection: false) / 1048576.0, (double)received / (totalSeconds - lastReport)));
					submitted = (received = (skipped = (errors = (updateCount = 0))));
				

BepInEx/plugins/ValheimEventClips/Encoder/Imazen.WebP.dll

Decompiled 4 hours ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using Imazen.WebP.Extern;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("imazen;lilith")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright 2017-2026 Imazen LLC")]
[assembly: AssemblyDescription(".NET bindings for libwebp. Provides WebP encoding and decoding via both System.Drawing (Bitmap) and raw pixel buffer APIs. Requires a platform-specific Imazen.WebP.NativeRuntime package or Imazen.WebP.AllPlatforms.")]
[assembly: AssemblyFileVersion("11.0.0.0")]
[assembly: AssemblyInformationalVersion("11.0.0+a2d53ed552b46e7f3a9a1a1f8ccd23e19f6f1595")]
[assembly: AssemblyProduct("Imazen.WebP")]
[assembly: AssemblyTitle("Imazen.WebP")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/imazen/libwebp-net")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("11.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[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 Imazen.WebP
{
	public static class AbiVersionCheck
	{
		private static volatile bool _validated;

		public static void ValidateOrThrow()
		{
			if (!_validated)
			{
				int num = NativeMethods.WebPGetDecoderVersion();
				int num2 = NativeMethods.WebPGetEncoderVersion();
				int num3 = (num >> 16) & 0xFF;
				int num4 = (num2 >> 16) & 0xFF;
				if (num3 != 1 || num4 != 1)
				{
					throw new NotSupportedException("Incompatible libwebp version. Expected 1.x, got decoder=" + FormatVersion(num) + ", encoder=" + FormatVersion(num2));
				}
				_validated = true;
			}
		}

		public static string GetVersionString()
		{
			int version = NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetDecoderVersion());
			int version2 = NativeMethods.WebPGetEncoderVersion();
			return "decoder=" + FormatVersion(version) + ", encoder=" + FormatVersion(version2);
		}

		private static string FormatVersion(int version)
		{
			int num = (version >> 16) & 0xFF;
			int num2 = (version >> 8) & 0xFF;
			int num3 = version & 0xFF;
			return $"{num}.{num2}.{num3}";
		}
	}
	public class AnimInfo
	{
		public int Width { get; }

		public int Height { get; }

		public int FrameCount { get; }

		public int LoopCount { get; }

		public uint BackgroundColor { get; }

		internal AnimInfo(int width, int height, int frameCount, int loopCount, uint bgColor)
		{
			Width = width;
			Height = height;
			FrameCount = frameCount;
			LoopCount = loopCount;
			BackgroundColor = bgColor;
		}
	}
	public class AnimDecoder : IDisposable
	{
		private IntPtr _decoder;

		private GCHandle _dataHandle;

		private bool _disposed;

		private readonly AnimInfo _info;

		private int _prevEndTimestamp;

		public AnimInfo Info => _info;

		public AnimDecoder(byte[] webpData, bool useThreads = false)
		{
			AnimDecoder animDecoder = this;
			if (webpData == null)
			{
				throw new ArgumentNullException("webpData");
			}
			_dataHandle = GCHandle.Alloc(webpData, GCHandleType.Pinned);
			WebPData data = new WebPData
			{
				bytes = _dataHandle.AddrOfPinnedObject(),
				size = (UIntPtr)(ulong)webpData.Length
			};
			WebPAnimDecoderOptions options = default(WebPAnimDecoderOptions);
			NativeLibraryLoader.FixDllNotFoundException("webpdemux", delegate
			{
				if (NativeMethods.WebPAnimDecoderOptionsInit(ref options) == 0)
				{
					throw new Exception("Failed to initialize animation decoder options (version mismatch)");
				}
				return 0;
			});
			options.color_mode = WEBP_CSP_MODE.MODE_BGRA;
			options.use_threads = (useThreads ? 1 : 0);
			_decoder = NativeLibraryLoader.FixDllNotFoundException("webpdemux", () => NativeMethods.WebPAnimDecoderNew(ref data, ref options));
			if (_decoder == IntPtr.Zero)
			{
				throw new Exception("Failed to create animation decoder. Data may not be a valid animated WebP.");
			}
			WebPAnimInfo animInfo = default(WebPAnimInfo);
			if (NativeLibraryLoader.FixDllNotFoundException("webpdemux", () => NativeMethods.WebPAnimDecoderGetInfo(animDecoder._decoder, ref animInfo)) == 0)
			{
				throw new Exception("Failed to get animation info");
			}
			_info = new AnimInfo((int)animInfo.canvas_width, (int)animInfo.canvas_height, (int)animInfo.frame_count, (int)animInfo.loop_count, animInfo.bgcolor);
		}

		public AnimDecoder(Stream stream, bool useThreads = false)
			: this(ReadStreamFully(stream), useThreads)
		{
		}

		public List<AnimFrame> DecodeAllFrames()
		{
			ThrowIfDisposed();
			Reset();
			List<AnimFrame> list = new List<AnimFrame>();
			AnimFrame nextFrame;
			while ((nextFrame = GetNextFrame()) != null)
			{
				list.Add(nextFrame);
			}
			return list;
		}

		public AnimFrame? GetNextFrame()
		{
			ThrowIfDisposed();
			if (!HasMoreFrames())
			{
				return null;
			}
			IntPtr buf = IntPtr.Zero;
			int endTimestamp = 0;
			if (NativeLibraryLoader.FixDllNotFoundException("webpdemux", () => NativeMethods.WebPAnimDecoderGetNext(_decoder, ref buf, ref endTimestamp)) == 0)
			{
				return null;
			}
			int num = _info.Width * _info.Height * 4;
			byte[] array = new byte[num];
			Marshal.Copy(buf, array, 0, num);
			int prevEndTimestamp = _prevEndTimestamp;
			AnimFrame result = new AnimFrame(array, _info.Width, _info.Height, prevEndTimestamp)
			{
				DurationMs = endTimestamp - prevEndTimestamp
			};
			_prevEndTimestamp = endTimestamp;
			return result;
		}

		public bool HasMoreFrames()
		{
			ThrowIfDisposed();
			return NativeLibraryLoader.FixDllNotFoundException("webpdemux", () => NativeMethods.WebPAnimDecoderHasMoreFrames(_decoder)) != 0;
		}

		public void Reset()
		{
			ThrowIfDisposed();
			NativeLibraryLoader.FixDllNotFoundException("webpdemux", delegate
			{
				NativeMethods.WebPAnimDecoderReset(_decoder);
				return 0;
			});
			_prevEndTimestamp = 0;
		}

		private void ThrowIfDisposed()
		{
			if (_disposed)
			{
				throw new ObjectDisposedException("AnimDecoder");
			}
		}

		private static byte[] ReadStreamFully(Stream stream)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			if (stream is MemoryStream { Position: 0L } memoryStream)
			{
				return memoryStream.ToArray();
			}
			using MemoryStream memoryStream2 = new MemoryStream();
			byte[] array = new byte[8192];
			int count;
			while ((count = stream.Read(array, 0, array.Length)) > 0)
			{
				memoryStream2.Write(array, 0, count);
			}
			return memoryStream2.ToArray();
		}

		public void Dispose()
		{
			if (_disposed)
			{
				return;
			}
			if (_decoder != IntPtr.Zero)
			{
				NativeLibraryLoader.FixDllNotFoundException("webpdemux", delegate
				{
					NativeMethods.WebPAnimDecoderDelete(_decoder);
					return 0;
				});
				_decoder = IntPtr.Zero;
			}
			if (_dataHandle.IsAllocated)
			{
				_dataHandle.Free();
			}
			_disposed = true;
		}
	}
	public class AnimEncoder : IDisposable
	{
		private IntPtr _encoder;

		private readonly int _width;

		private readonly int _height;

		private bool _disposed;

		private int _lastTimestamp;

		private int _lastDuration = 100;

		private bool _hasFrames;

		public AnimEncoder(int width, int height)
		{
			if (width <= 0)
			{
				throw new ArgumentOutOfRangeException("width");
			}
			if (height <= 0)
			{
				throw new ArgumentOutOfRangeException("height");
			}
			_width = width;
			_height = height;
			WebPAnimEncoderOptions options = default(WebPAnimEncoderOptions);
			NativeLibraryLoader.FixDllNotFoundException("webpmux", delegate
			{
				if (NativeMethods.WebPAnimEncoderOptionsInit(ref options) == 0)
				{
					throw new Exception("Failed to initialize animation encoder options (version mismatch)");
				}
				return 0;
			});
			_encoder = NativeLibraryLoader.FixDllNotFoundException("webpmux", () => NativeMethods.WebPAnimEncoderNew(width, height, ref options));
			if (_encoder == IntPtr.Zero)
			{
				throw new Exception("Failed to create animation encoder");
			}
		}

		public AnimEncoder(int width, int height, int loopCount = 0, uint backgroundColor = 0u, bool allowMixed = false, bool minimizeSize = false)
		{
			if (width <= 0)
			{
				throw new ArgumentOutOfRangeException("width");
			}
			if (height <= 0)
			{
				throw new ArgumentOutOfRangeException("height");
			}
			_width = width;
			_height = height;
			WebPAnimEncoderOptions options = default(WebPAnimEncoderOptions);
			NativeLibraryLoader.FixDllNotFoundException("webpmux", delegate
			{
				if (NativeMethods.WebPAnimEncoderOptionsInit(ref options) == 0)
				{
					throw new Exception("Failed to initialize animation encoder options (version mismatch)");
				}
				return 0;
			});
			options.anim_params.loop_count = loopCount;
			options.anim_params.bgcolor = backgroundColor;
			options.allow_mixed = (allowMixed ? 1 : 0);
			options.minimize_size = (minimizeSize ? 1 : 0);
			_encoder = NativeLibraryLoader.FixDllNotFoundException("webpmux", () => NativeMethods.WebPAnimEncoderNew(width, height, ref options));
			if (_encoder == IntPtr.Zero)
			{
				throw new Exception("Failed to create animation encoder");
			}
		}

		public void AddFrame(byte[] bgraPixels, int timestampMs, float quality = -1f)
		{
			if (bgraPixels == null)
			{
				throw new ArgumentNullException("bgraPixels");
			}
			AddFrameInternal(bgraPixels, _width * 4, WebPPixelFormat.Bgra, timestampMs, quality);
		}

		public void AddFrame(byte[] pixels, int stride, WebPPixelFormat format, int timestampMs, float quality = -1f)
		{
			if (pixels == null)
			{
				throw new ArgumentNullException("pixels");
			}
			AddFrameInternal(pixels, stride, format, timestampMs, quality);
		}

		public void AddFrame(byte[] pixels, int stride, WebPPixelFormat format, int timestampMs, WebPEncoderConfig config)
		{
			if (pixels == null)
			{
				throw new ArgumentNullException("pixels");
			}
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}
			ThrowIfDisposed();
			if (!config.Validate())
			{
				throw new ArgumentException("Invalid encoder configuration", "config");
			}
			WebPConfig config2 = config.GetNativeConfig();
			AddFrameWithConfig(pixels, stride, format, timestampMs, ref config2);
		}

		private void AddFrameInternal(byte[] pixels, int stride, WebPPixelFormat format, int timestampMs, float quality)
		{
			ThrowIfDisposed();
			WebPConfig config = default(WebPConfig);
			NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPConfigInitInternal(ref config, WebPPreset.WEBP_PRESET_DEFAULT, (quality >= 0f) ? quality : 75f, 528));
			if (quality < 0f)
			{
				config.lossless = 1;
				config.quality = 75f;
			}
			else
			{
				config.lossless = 0;
				config.quality = Math.Max(0f, Math.Min(100f, quality));
			}
			AddFrameWithConfig(pixels, stride, format, timestampMs, ref config);
		}

		private void AddFrameWithConfig(byte[] pixels, int stride, WebPPixelFormat format, int timestampMs, ref WebPConfig config)
		{
			WebPConfig localConfig = config;
			WebPPicture picture = default(WebPPicture);
			NativeLibraryLoader.FixDllNotFoundException("webp", delegate
			{
				if (NativeMethods.WebPPictureInitInternal(ref picture, 528) == 0)
				{
					throw new Exception("Failed to initialize WebPPicture (version mismatch)");
				}
				return 0;
			});
			picture.width = _width;
			picture.height = _height;
			picture.use_argb = 1;
			GCHandle gCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
			try
			{
				IntPtr pixelPtr = gCHandle.AddrOfPinnedObject();
				if (format switch
				{
					WebPPixelFormat.Bgra => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportBGRA(ref picture, pixelPtr, stride)), 
					WebPPixelFormat.Rgba => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportRGBA(ref picture, pixelPtr, stride)), 
					WebPPixelFormat.Bgr => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportBGR(ref picture, pixelPtr, stride)), 
					WebPPixelFormat.Rgb => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportRGB(ref picture, pixelPtr, stride)), 
					_ => throw new ArgumentOutOfRangeException("format"), 
				} == 0)
				{
					throw new Exception("Failed to import pixel data into WebPPicture");
				}
				if (NativeLibraryLoader.FixDllNotFoundException("webpmux", () => NativeMethods.WebPAnimEncoderAdd(_encoder, ref picture, timestampMs, ref localConfig)) == 0)
				{
					IntPtr intPtr = NativeMethods.WebPAnimEncoderGetError(_encoder);
					string text = ((intPtr != IntPtr.Zero) ? (Marshal.PtrToStringAnsi(intPtr) ?? "Unknown error") : "Unknown error");
					throw new Exception("Failed to add animation frame: " + text);
				}
				if (_hasFrames)
				{
					_lastDuration = Math.Max(timestampMs - _lastTimestamp, 1);
				}
				_lastTimestamp = timestampMs;
				_hasFrames = true;
			}
			finally
			{
				NativeLibraryLoader.FixDllNotFoundException("webp", delegate
				{
					NativeMethods.WebPPictureFree(ref picture);
					return 0;
				});
				gCHandle.Free();
			}
		}

		public byte[] Assemble()
		{
			ThrowIfDisposed();
			int endTimestamp = _lastTimestamp + Math.Max(_lastDuration, 1);
			NativeLibraryLoader.FixDllNotFoundException("webpmux", () => NativeMethods.WebPAnimEncoderAddNull(_encoder, IntPtr.Zero, endTimestamp, IntPtr.Zero));
			WebPData webpData = default(WebPData);
			if (NativeLibraryLoader.FixDllNotFoundException("webpmux", () => NativeMethods.WebPAnimEncoderAssemble(_encoder, ref webpData)) == 0)
			{
				IntPtr intPtr = NativeMethods.WebPAnimEncoderGetError(_encoder);
				string text = ((intPtr != IntPtr.Zero) ? (Marshal.PtrToStringAnsi(intPtr) ?? "Unknown error") : "Unknown error");
				throw new Exception("Failed to assemble animation: " + text);
			}
			int num = (int)(ulong)webpData.size;
			byte[] array = new byte[num];
			Marshal.Copy(webpData.bytes, array, 0, num);
			return array;
		}

		public void Assemble(Stream outputStream)
		{
			if (outputStream == null)
			{
				throw new ArgumentNullException("outputStream");
			}
			byte[] array = Assemble();
			outputStream.Write(array, 0, array.Length);
		}

		private void ThrowIfDisposed()
		{
			if (_disposed)
			{
				throw new ObjectDisposedException("AnimEncoder");
			}
		}

		public void Dispose()
		{
			if (!_disposed && _encoder != IntPtr.Zero)
			{
				NativeLibraryLoader.FixDllNotFoundException("webpmux", delegate
				{
					NativeMethods.WebPAnimEncoderDelete(_encoder);
					return 0;
				});
				_encoder = IntPtr.Zero;
				_disposed = true;
			}
		}
	}
	public class AnimFrame
	{
		public byte[] Pixels { get; }

		public int TimestampMs { get; }

		public int Width { get; }

		public int Height { get; }

		public int DurationMs { get; internal set; }

		public AnimFrame(byte[] pixels, int width, int height, int timestampMs)
		{
			Pixels = pixels ?? throw new ArgumentNullException("pixels");
			Width = width;
			Height = height;
			TimestampMs = timestampMs;
			DurationMs = -1;
		}
	}
	internal class LoadLogger : ILibraryLoadLogger
	{
		private struct LogEntry
		{
			internal string Basename;

			internal string? FullPath;

			internal bool FileExists;

			internal bool PreviouslyLoaded;

			internal int? LoadErrorCode;
		}

		internal string Verb = "loaded";

		internal string Filename = RuntimeFileLocator.SharedLibraryPrefix.Value + "webp." + RuntimeFileLocator.SharedLibraryExtension.Value;

		internal Exception? FirstException;

		internal Exception? LastException;

		private readonly List<LogEntry> _log = new List<LogEntry>(7);

		public void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, int? loadErrorCode)
		{
			_log.Add(new LogEntry
			{
				Basename = basename,
				FullPath = fullPath,
				FileExists = fileExists,
				PreviouslyLoaded = previouslyLoaded,
				LoadErrorCode = loadErrorCode
			});
		}

		internal void RaiseException()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "Looking for \"{0}\" RID=\"{1}-{2}\", IsUnix={3}, IsDotNetCore={4} RelativeSearchPath=\"{5}\"\n", Filename, RuntimeFileLocator.PlatformRuntimePrefix.Value, RuntimeFileLocator.ArchitectureSubdir.Value, RuntimeFileLocator.IsUnix, RuntimeFileLocator.IsDotNetCore.Value, AppDomain.CurrentDomain.RelativeSearchPath);
			if (FirstException != null)
			{
				stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "Before searching: {0}\n", FirstException.Message);
			}
			foreach (LogEntry item in _log)
			{
				if (item.PreviouslyLoaded)
				{
					stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "\"{0}\" is already {1}", item.Basename, Verb);
				}
				else if (!item.FileExists)
				{
					stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "File not found: {0}", item.FullPath);
				}
				else if (item.LoadErrorCode.HasValue)
				{
					string text = ((item.LoadErrorCode.Value < 0) ? string.Format(CultureInfo.InvariantCulture, "0x{0:X8}", item.LoadErrorCode.Value) : item.LoadErrorCode.Value.ToString(CultureInfo.InvariantCulture));
					stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "Error \"{0}\" ({1}) loading {2} from {3}", new Win32Exception(item.LoadErrorCode.Value).Message, text, item.Basename, item.FullPath);
					if (item.LoadErrorCode.Value == 193 && RuntimeFileLocator.PlatformRuntimePrefix.Value == "win")
					{
						string arg = (Environment.Is64BitProcess ? "32-bit (x86)" : "64-bit (x86_64)");
						string arg2 = (Environment.Is64BitProcess ? "64-bit (x86_64)" : "32-bit (x86)");
						stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "\n> You have installed a {0} copy of libwebp but need the {1} version", arg, arg2);
					}
					if (item.LoadErrorCode.Value == 126 && RuntimeFileLocator.PlatformRuntimePrefix.Value == "win")
					{
						string arg3 = "https://aka.ms/vs/17/release/vc_redist." + (Environment.Is64BitProcess ? "x64.exe" : "x86.exe");
						stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "\n> You may need to install the C Runtime from {0}", arg3);
					}
				}
				else
				{
					stringBuilder.AppendFormat(CultureInfo.InvariantCulture, "{0} {1} in {2}", Verb, item.Basename, item.FullPath);
				}
				stringBuilder.Append('\n');
			}
			if (LastException != null)
			{
				stringBuilder.AppendLine(LastException.Message);
			}
			string text2 = (FirstException ?? LastException)?.StackTrace;
			if (text2 != null)
			{
				stringBuilder.AppendLine(text2);
			}
			throw new DllNotFoundException(stringBuilder.ToString());
		}
	}
	internal static class RuntimeFileLocator
	{
		internal static readonly Lazy<string> SharedLibraryPrefix = new Lazy<string>(() => (!IsUnix) ? "" : "lib", LazyThreadSafetyMode.PublicationOnly);

		internal static readonly Lazy<bool> IsDotNetCore = new Lazy<bool>(delegate
		{
			try
			{
				return typeof(GCSettings).GetTypeInfo().Assembly.CodeBase.Contains("Microsoft.NETCore.App");
			}
			catch
			{
				return false;
			}
		}, LazyThreadSafetyMode.PublicationOnly);

		internal static readonly Lazy<string> PlatformRuntimePrefix = new Lazy<string>(delegate
		{
			if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
			{
				return "osx";
			}
			return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" : "win";
		}, LazyThreadSafetyMode.PublicationOnly);

		internal static readonly Lazy<string> SharedLibraryExtension = new Lazy<string>(delegate
		{
			if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
			{
				return "dylib";
			}
			return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "so" : "dll";
		}, LazyThreadSafetyMode.PublicationOnly);

		internal static readonly Lazy<string> ArchitectureSubdir = new Lazy<string>(delegate
		{
			switch (RuntimeInformation.ProcessArchitecture)
			{
			case Architecture.X86:
				return "x86";
			case Architecture.X64:
				return "x64";
			case Architecture.Arm:
				return "arm";
			case Architecture.Arm64:
				return "arm64";
			default:
			{
				string environmentVariable = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
				if (environmentVariable != null)
				{
					switch (environmentVariable.ToUpperInvariant())
					{
					case "AMD64":
						return "x64";
					case "IA64":
						return "ia64";
					case "ARM64":
						return "arm64";
					case "EM64T":
						return "x64";
					case "X86":
						return "x86";
					}
				}
				if (!Environment.Is64BitProcess)
				{
					return "x86";
				}
				return "x64";
			}
			}
		}, LazyThreadSafetyMode.PublicationOnly);

		internal static bool IsUnix
		{
			get
			{
				if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
				{
					return RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
				}
				return true;
			}
		}

		private static IEnumerable<Tuple<bool, string>> BaseFolders(IEnumerable<string>? customSearchDirectories = null)
		{
			if (customSearchDirectories != null)
			{
				foreach (string customSearchDirectory in customSearchDirectories)
				{
					yield return Tuple.Create(item1: true, customSearchDirectory);
				}
			}
			if (!string.IsNullOrEmpty(AppDomain.CurrentDomain.RelativeSearchPath) && AppDomain.CurrentDomain.RelativeSearchPath.StartsWith(AppDomain.CurrentDomain.BaseDirectory))
			{
				yield return Tuple.Create(item1: true, AppDomain.CurrentDomain.RelativeSearchPath);
			}
			if (!string.IsNullOrEmpty(AppContext.BaseDirectory))
			{
				yield return Tuple.Create(item1: true, AppContext.BaseDirectory);
			}
			yield return Tuple.Create(item1: true, AppDomain.CurrentDomain.BaseDirectory);
			if (AppDomain.CurrentDomain.BaseDirectory.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar).EndsWith("bin"))
			{
				DirectoryInfo parent = Directory.GetParent(AppDomain.CurrentDomain.BaseDirectory);
				if (parent != null)
				{
					yield return Tuple.Create(item1: false, Path.Combine(parent.FullName, "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native"));
				}
			}
			string text = null;
			try
			{
				text = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			}
			catch (NotImplementedException)
			{
			}
			if (!string.IsNullOrEmpty(text))
			{
				yield return Tuple.Create(item1: true, text);
			}
		}

		internal static IEnumerable<string> SearchPossibilitiesForFile(string filename, IEnumerable<string>? customSearchDirectories = null)
		{
			HashSet<string> attemptedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (Tuple<bool, string> item in BaseFolders(customSearchDirectories))
			{
				if (string.IsNullOrEmpty(item.Item2))
				{
					continue;
				}
				string directory = Path.GetFullPath(item.Item2);
				bool searchSubDirs = item.Item1;
				string text;
				if (searchSubDirs)
				{
					text = Path.Combine(directory, "runtimes", PlatformRuntimePrefix.Value + "-" + ArchitectureSubdir.Value, "native", filename);
					if (attemptedPaths.Add(text))
					{
						yield return text;
					}
				}
				if (searchSubDirs)
				{
					text = Path.Combine(directory, ArchitectureSubdir.Value, filename);
					if (attemptedPaths.Add(text))
					{
						yield return text;
					}
				}
				text = Path.Combine(directory, filename);
				if (attemptedPaths.Add(text))
				{
					yield return text;
				}
			}
		}
	}
	internal interface ILibraryLoadLogger
	{
		void NotifyAttempt(string basename, string? fullPath, bool fileExists, bool previouslyLoaded, int? loadErrorCode);
	}
	internal static class NativeLibraryLoader
	{
		private static readonly Lazy<ConcurrentDictionary<string, IntPtr>> LibraryHandlesByBasename = new Lazy<ConcurrentDictionary<string, IntPtr>>(() => new ConcurrentDictionary<string, IntPtr>(StringComparer.OrdinalIgnoreCase), LazyThreadSafetyMode.PublicationOnly);

		internal static string GetFilenameWithoutDirectory(string basename)
		{
			return RuntimeFileLocator.SharedLibraryPrefix.Value + basename + "." + RuntimeFileLocator.SharedLibraryExtension.Value;
		}

		public static T? FixDllNotFoundException<T>(string basename, Func<T> invokingOperation, IEnumerable<string>? customSearchDirectories = null)
		{
			Exception firstException;
			try
			{
				return invokingOperation();
			}
			catch (BadImageFormatException ex)
			{
				firstException = ex;
			}
			catch (DllNotFoundException ex2)
			{
				firstException = ex2;
			}
			LoadLogger loadLogger = new LoadLogger
			{
				FirstException = firstException,
				Filename = GetFilenameWithoutDirectory(basename)
			};
			if (TryLoadByBasename(basename, loadLogger, out var _, customSearchDirectories))
			{
				try
				{
					return invokingOperation();
				}
				catch (DllNotFoundException lastException)
				{
					loadLogger.LastException = lastException;
				}
			}
			loadLogger.RaiseException();
			return default(T);
		}

		public static bool TryLoadByBasename(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable<string>? customSearchDirectories = null)
		{
			if (string.IsNullOrEmpty(basename))
			{
				throw new ArgumentNullException("basename");
			}
			if (LibraryHandlesByBasename.Value.TryGetValue(basename, out handle))
			{
				log.NotifyAttempt(basename, null, fileExists: true, previouslyLoaded: true, 0);
				return true;
			}
			lock (LibraryHandlesByBasename)
			{
				if (LibraryHandlesByBasename.Value.TryGetValue(basename, out handle))
				{
					log.NotifyAttempt(basename, null, fileExists: true, previouslyLoaded: true, 0);
					return true;
				}
				bool num = TryLoadByBasenameInternal(basename, log, out handle, customSearchDirectories);
				if (num)
				{
					LibraryHandlesByBasename.Value[basename] = handle;
					if (string.Equals(basename, "webp", StringComparison.OrdinalIgnoreCase))
					{
						AbiVersionCheck.ValidateOrThrow();
					}
				}
				return num;
			}
		}

		private static bool TryLoadByBasenameInternal(string basename, ILibraryLoadLogger log, out IntPtr handle, IEnumerable<string>? customSearchDirectories = null)
		{
			string filenameWithoutDirectory = GetFilenameWithoutDirectory(basename);
			List<string> list = new List<string> { filenameWithoutDirectory };
			if (!RuntimeFileLocator.IsUnix)
			{
				string text = "lib" + basename + "." + RuntimeFileLocator.SharedLibraryExtension.Value;
				if (!string.Equals(filenameWithoutDirectory, text, StringComparison.OrdinalIgnoreCase))
				{
					list.Add(text);
				}
			}
			foreach (string item in list)
			{
				foreach (string item2 in RuntimeFileLocator.SearchPossibilitiesForFile(item, customSearchDirectories))
				{
					if (!File.Exists(item2))
					{
						log.NotifyAttempt(basename, item2, fileExists: false, previouslyLoaded: false, 0);
						continue;
					}
					int? errorCode;
					bool num = LoadLibrary(item2, out handle, out errorCode);
					log.NotifyAttempt(basename, item2, fileExists: true, previouslyLoaded: false, errorCode);
					if (!num)
					{
						continue;
					}
					return true;
				}
			}
			handle = IntPtr.Zero;
			return false;
		}

		private static bool LoadLibrary(string fullPath, out IntPtr handle, out int? errorCode)
		{
			handle = (RuntimeFileLocator.IsUnix ? UnixLoadLibrary.Execute(fullPath) : WindowsLoadLibrary.Execute(fullPath));
			if (handle == IntPtr.Zero)
			{
				errorCode = Marshal.GetLastWin32Error();
				return false;
			}
			errorCode = null;
			return true;
		}
	}
	[SuppressUnmanagedCodeSecurity]
	[SecurityCritical]
	internal static class WindowsLoadLibrary
	{
		[DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)]
		private static extern IntPtr LoadLibraryEx(string fileName, IntPtr reservedNull, uint flags);

		public static IntPtr Execute(string fileName)
		{
			return LoadLibraryEx(fileName, IntPtr.Zero, 8u);
		}
	}
	[SuppressUnmanagedCodeSecurity]
	[SecurityCritical]
	internal static class UnixLoadLibrary
	{
		private static volatile bool _preferLibdl2 = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);

		[DllImport("libdl.so.2", CharSet = CharSet.Ansi, EntryPoint = "dlopen", SetLastError = true)]
		private static extern IntPtr dlopen_libdl2(string fileName, int flags);

		[DllImport("libdl", CharSet = CharSet.Ansi, EntryPoint = "dlopen", SetLastError = true)]
		private static extern IntPtr dlopen_libdl(string fileName, int flags);

		public static IntPtr Execute(string fileName)
		{
			if (_preferLibdl2)
			{
				try
				{
					return dlopen_libdl2(fileName, 2);
				}
				catch (DllNotFoundException)
				{
					_preferLibdl2 = false;
				}
			}
			return dlopen_libdl(fileName, 2);
		}
	}
	public class SimpleDecoder
	{
		public static string GetDecoderVersion()
		{
			int num = NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetDecoderVersion());
			uint num2 = (uint)num % 256u;
			uint num3 = (uint)(num >>> 8) % 256u;
			uint num4 = (uint)(num >>> 16) % 256u;
			return num4 + "." + num3 + "." + num2;
		}

		public unsafe Bitmap DecodeFromBytes(byte[] data, long length)
		{
			fixed (byte* ptr = data)
			{
				return DecodeFromPointer((IntPtr)ptr, length);
			}
		}

		public Bitmap DecodeFromPointer(IntPtr data, long length)
		{
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			int w = 0;
			int h = 0;
			if (NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetInfo(data, (UIntPtr)(ulong)length, ref w, ref h)) == 0)
			{
				throw new Exception("Invalid WebP header detected");
			}
			bool flag = false;
			Bitmap val = null;
			BitmapData bd = null;
			try
			{
				val = new Bitmap(w, h, (PixelFormat)2498570);
				bd = val.LockBits(new Rectangle(0, 0, w, h), (ImageLockMode)3, (PixelFormat)2498570);
				IntPtr intPtr = NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPDecodeBGRAInto(data, (UIntPtr)(ulong)length, bd.Scan0, (UIntPtr)(ulong)(bd.Stride * bd.Height), bd.Stride));
				if (bd.Scan0 != intPtr)
				{
					throw new Exception("Failed to decode WebP image with error " + (long)intPtr);
				}
				flag = true;
			}
			finally
			{
				if (bd != null && val != null)
				{
					val.UnlockBits(bd);
				}
				if (!flag && val != null)
				{
					((Image)val).Dispose();
				}
			}
			return val;
		}

		public Bitmap DecodeFromStream(Stream stream)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			byte[] array = ReadStreamFully(stream);
			return DecodeFromBytes(array, array.LongLength);
		}

		private static byte[] ReadStreamFully(Stream stream)
		{
			if (stream is MemoryStream { Position: 0L } memoryStream)
			{
				return memoryStream.ToArray();
			}
			using MemoryStream memoryStream2 = new MemoryStream();
			byte[] array = new byte[8192];
			int count;
			while ((count = stream.Read(array, 0, array.Length)) > 0)
			{
				memoryStream2.Write(array, 0, count);
			}
			return memoryStream2.ToArray();
		}
	}
	public class SimpleEncoder
	{
		public static string GetEncoderVersion()
		{
			int num = NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetEncoderVersion());
			uint num2 = (uint)num % 256u;
			uint num3 = (uint)(num >>> 8) % 256u;
			uint num4 = (uint)(num >>> 16) % 256u;
			return num4 + "." + num3 + "." + num2;
		}

		[Obsolete("Use Encode(Bitmap, Stream, float) instead")]
		public void Encode(Bitmap from, Stream to, float quality, bool noAlpha)
		{
			Encode(from, to, quality);
		}

		public void Encode(Bitmap from, Stream to, float quality)
		{
			Encode(from, quality, out var result, out var length);
			try
			{
				byte[] array = new byte[4096];
				for (int i = 0; i < length; i += array.Length)
				{
					int num = (int)Math.Min(array.Length, length - i);
					Marshal.Copy((IntPtr)((long)result + i), array, 0, num);
					to.Write(array, 0, num);
				}
			}
			finally
			{
				NativeMethods.WebPSafeFree(result);
			}
		}

		[Obsolete("Use Encode(Bitmap, float, out IntPtr, out long) instead")]
		public void Encode(Bitmap b, float quality, bool noAlpha, out IntPtr result, out long length)
		{
			Encode(b, quality, out result, out length);
		}

		public void Encode(Bitmap b, float quality, out IntPtr result, out long length)
		{
			//IL_006b: 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_00a0: Invalid comparison between Unknown and I4
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Invalid comparison between Unknown and I4
			if (quality < -1f)
			{
				quality = -1f;
			}
			if (quality > 100f)
			{
				quality = 100f;
			}
			int w = ((Image)b).Width;
			int h = ((Image)b).Height;
			BitmapData val = b.LockBits(new Rectangle(0, 0, w, h), (ImageLockMode)1, ((Image)b).PixelFormat);
			try
			{
				result = IntPtr.Zero;
				IntPtr scan0 = val.Scan0;
				int stride = val.Stride;
				if ((int)((Image)b).PixelFormat == 2498570)
				{
					IntPtr res = IntPtr.Zero;
					if (quality == -1f)
					{
						length = (long)(ulong)NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeLosslessBGRA(scan0, w, h, stride, ref res));
					}
					else
					{
						length = (long)(ulong)NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeBGRA(scan0, w, h, stride, quality, ref res));
					}
					result = res;
				}
				else if ((int)((Image)b).PixelFormat == 137224)
				{
					IntPtr res2 = IntPtr.Zero;
					if (quality == -1f)
					{
						length = (long)(ulong)NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeLosslessBGR(scan0, w, h, stride, ref res2));
					}
					else
					{
						length = (long)(ulong)NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeBGR(scan0, w, h, stride, quality, ref res2));
					}
					result = res2;
				}
				else
				{
					Bitmap val2 = b.Clone(new Rectangle(0, 0, ((Image)b).Width, ((Image)b).Height), (PixelFormat)2498570);
					try
					{
						Encode(val2, quality, out result, out length);
					}
					finally
					{
						((IDisposable)val2)?.Dispose();
					}
				}
				if (length == 0L)
				{
					throw new Exception("WebP encode failed!");
				}
			}
			finally
			{
				b.UnlockBits(val);
			}
		}

		public void Encode(Bitmap b, Stream to, WebPEncoderConfig config)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Invalid comparison between Unknown and I4
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Invalid comparison between Unknown and I4
			if (b == null)
			{
				throw new ArgumentNullException("b");
			}
			if (to == null)
			{
				throw new ArgumentNullException("to");
			}
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}
			int width = ((Image)b).Width;
			int height = ((Image)b).Height;
			Bitmap val = b;
			bool flag = false;
			if ((int)((Image)b).PixelFormat != 2498570 && (int)((Image)b).PixelFormat != 137224)
			{
				val = b.Clone(new Rectangle(0, 0, ((Image)b).Width, ((Image)b).Height), (PixelFormat)2498570);
				flag = true;
			}
			try
			{
				BitmapData val2 = val.LockBits(new Rectangle(0, 0, width, height), (ImageLockMode)1, ((Image)val).PixelFormat);
				try
				{
					_ = ((Image)val).PixelFormat;
					_ = 137224;
					int stride = val2.Stride;
					byte[] array = new byte[Math.Abs(stride) * height];
					Marshal.Copy(val2.Scan0, array, 0, array.Length);
					WebPPixelFormat format = (((int)((Image)val).PixelFormat == 137224) ? WebPPixelFormat.Bgr : WebPPixelFormat.Bgra);
					WebPEncoder.Encode(array, width, height, Math.Abs(stride), format, config, to);
				}
				finally
				{
					val.UnlockBits(val2);
				}
			}
			finally
			{
				if (flag)
				{
					((Image)val).Dispose();
				}
			}
		}
	}
	public enum WebPPixelFormat
	{
		Bgra,
		Rgba,
		Bgr,
		Rgb
	}
	public static class WebPDecoder
	{
		public static byte[] Decode(byte[] data, out int width, out int height)
		{
			return Decode(data, out width, out height, WebPPixelFormat.Bgra);
		}

		public static byte[] Decode(byte[] data, out int width, out int height, WebPPixelFormat format)
		{
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
			try
			{
				IntPtr dataPtr = gCHandle.AddrOfPinnedObject();
				UIntPtr dataSize = (UIntPtr)(ulong)data.Length;
				int w = 0;
				int h = 0;
				if (NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetInfo(dataPtr, dataSize, ref w, ref h)) == 0)
				{
					throw new Exception("Invalid WebP header detected");
				}
				width = w;
				height = h;
				int num = ((format == WebPPixelFormat.Bgr || format == WebPPixelFormat.Rgb) ? 3 : 4);
				int num2 = w * num;
				byte[] array = new byte[num2 * h];
				GCHandle gCHandle2 = GCHandle.Alloc(array, GCHandleType.Pinned);
				try
				{
					IntPtr intPtr = gCHandle2.AddrOfPinnedObject();
					UIntPtr outSize = (UIntPtr)(ulong)array.Length;
					IntPtr intPtr2 = DecodeInto(dataPtr, dataSize, intPtr, outSize, num2, format);
					if (intPtr != intPtr2)
					{
						throw new Exception("Failed to decode WebP image");
					}
				}
				finally
				{
					gCHandle2.Free();
				}
				return array;
			}
			finally
			{
				gCHandle.Free();
			}
		}

		public static void Decode(byte[] data, byte[] output, int stride, WebPPixelFormat format)
		{
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			if (output == null)
			{
				throw new ArgumentNullException("output");
			}
			GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
			GCHandle gCHandle2 = GCHandle.Alloc(output, GCHandleType.Pinned);
			try
			{
				IntPtr dataPtr = gCHandle.AddrOfPinnedObject();
				IntPtr intPtr = gCHandle2.AddrOfPinnedObject();
				UIntPtr dataSize = (UIntPtr)(ulong)data.Length;
				UIntPtr outSize = (UIntPtr)(ulong)output.Length;
				IntPtr intPtr2 = DecodeInto(dataPtr, dataSize, intPtr, outSize, stride, format);
				if (intPtr != intPtr2)
				{
					throw new Exception("Failed to decode WebP image");
				}
			}
			finally
			{
				gCHandle2.Free();
				gCHandle.Free();
			}
		}

		public static byte[] DecodeFromStream(Stream stream, out int width, out int height)
		{
			return DecodeFromStream(stream, out width, out height, WebPPixelFormat.Bgra);
		}

		public static byte[] DecodeFromStream(Stream stream, out int width, out int height, WebPPixelFormat format)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			return Decode(ReadStreamFully(stream), out width, out height, format);
		}

		public static bool IsWebP(byte[] data)
		{
			if (data == null || data.Length < 12)
			{
				return false;
			}
			if (data[0] == 82 && data[1] == 73 && data[2] == 70 && data[3] == 70 && data[8] == 87 && data[9] == 69 && data[10] == 66)
			{
				return data[11] == 80;
			}
			return false;
		}

		public static bool IsWebP(Stream stream)
		{
			if (stream == null)
			{
				throw new ArgumentNullException("stream");
			}
			if (!stream.CanSeek)
			{
				throw new ArgumentException("Stream must be seekable", "stream");
			}
			long position = stream.Position;
			try
			{
				byte[] array = new byte[12];
				int num;
				for (int i = 0; i < 12; i += num)
				{
					num = stream.Read(array, i, 12 - i);
					if (num == 0)
					{
						return false;
					}
				}
				return IsWebP(array);
			}
			finally
			{
				stream.Position = position;
			}
		}

		private static byte[] ReadStreamFully(Stream stream)
		{
			if (stream is MemoryStream { Position: 0L } memoryStream)
			{
				return memoryStream.ToArray();
			}
			using MemoryStream memoryStream2 = new MemoryStream();
			byte[] array = new byte[8192];
			int count;
			while ((count = stream.Read(array, 0, array.Length)) > 0)
			{
				memoryStream2.Write(array, 0, count);
			}
			return memoryStream2.ToArray();
		}

		private static IntPtr DecodeInto(IntPtr dataPtr, UIntPtr dataSize, IntPtr outPtr, UIntPtr outSize, int stride, WebPPixelFormat format)
		{
			return format switch
			{
				WebPPixelFormat.Bgra => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPDecodeBGRAInto(dataPtr, dataSize, outPtr, outSize, stride)), 
				WebPPixelFormat.Rgba => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPDecodeRGBAInto(dataPtr, dataSize, outPtr, outSize, stride)), 
				WebPPixelFormat.Bgr => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPDecodeBGRInto(dataPtr, dataSize, outPtr, outSize, stride)), 
				WebPPixelFormat.Rgb => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPDecodeRGBInto(dataPtr, dataSize, outPtr, outSize, stride)), 
				_ => throw new ArgumentOutOfRangeException("format"), 
			};
		}
	}
	public static class WebPEncoder
	{
		private class EncodeOutput
		{
			public MemoryStream Stream = new MemoryStream();
		}

		[ThreadStatic]
		private static WebPWriterFunction? _writerDelegate;

		private static int ManagedWriter(IntPtr data, UIntPtr dataSize, ref WebPPicture picture)
		{
			int num = (int)(uint)dataSize;
			if (num <= 0)
			{
				return 1;
			}
			byte[] array = new byte[num];
			Marshal.Copy(data, array, 0, num);
			((EncodeOutput)GCHandle.FromIntPtr(picture.custom_ptr).Target).Stream.Write(array, 0, num);
			return 1;
		}

		public static byte[] Encode(byte[] pixels, int width, int height, int stride, WebPPixelFormat format, float quality)
		{
			if (pixels == null)
			{
				throw new ArgumentNullException("pixels");
			}
			if (width <= 0)
			{
				throw new ArgumentOutOfRangeException("width");
			}
			if (height <= 0)
			{
				throw new ArgumentOutOfRangeException("height");
			}
			GCHandle gCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
			try
			{
				IntPtr data = gCHandle.AddrOfPinnedObject();
				IntPtr result = IntPtr.Zero;
				UIntPtr uIntPtr;
				if (quality < 0f)
				{
					uIntPtr = EncodeLossless(data, width, height, stride, format, ref result);
				}
				else
				{
					if (quality > 100f)
					{
						quality = 100f;
					}
					uIntPtr = EncodeLossy(data, width, height, stride, format, quality, ref result);
				}
				if ((ulong)uIntPtr == 0L || result == IntPtr.Zero)
				{
					throw new Exception("WebP encode failed!");
				}
				try
				{
					byte[] array = new byte[(uint)(ulong)uIntPtr];
					Marshal.Copy(result, array, 0, array.Length);
					return array;
				}
				finally
				{
					NativeMethods.WebPSafeFree(result);
				}
			}
			finally
			{
				gCHandle.Free();
			}
		}

		public static void Encode(byte[] pixels, int width, int height, int stride, WebPPixelFormat format, float quality, Stream output)
		{
			if (output == null)
			{
				throw new ArgumentNullException("output");
			}
			byte[] array = Encode(pixels, width, height, stride, format, quality);
			output.Write(array, 0, array.Length);
		}

		private static UIntPtr EncodeLossy(IntPtr data, int width, int height, int stride, WebPPixelFormat format, float quality, ref IntPtr result)
		{
			IntPtr res = result;
			UIntPtr result2 = format switch
			{
				WebPPixelFormat.Bgra => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeBGRA(data, width, height, stride, quality, ref res)), 
				WebPPixelFormat.Rgba => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeRGBA(data, width, height, stride, quality, ref res)), 
				WebPPixelFormat.Bgr => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeBGR(data, width, height, stride, quality, ref res)), 
				WebPPixelFormat.Rgb => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeRGB(data, width, height, stride, quality, ref res)), 
				_ => throw new ArgumentOutOfRangeException("format"), 
			};
			result = res;
			return result2;
		}

		private static UIntPtr EncodeLossless(IntPtr data, int width, int height, int stride, WebPPixelFormat format, ref IntPtr result)
		{
			IntPtr res = result;
			UIntPtr result2 = format switch
			{
				WebPPixelFormat.Bgra => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeLosslessBGRA(data, width, height, stride, ref res)), 
				WebPPixelFormat.Rgba => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeLosslessRGBA(data, width, height, stride, ref res)), 
				WebPPixelFormat.Bgr => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeLosslessBGR(data, width, height, stride, ref res)), 
				WebPPixelFormat.Rgb => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncodeLosslessRGB(data, width, height, stride, ref res)), 
				_ => throw new ArgumentOutOfRangeException("format"), 
			};
			result = res;
			return result2;
		}

		public static byte[] Encode(byte[] pixels, int width, int height, int stride, WebPPixelFormat format, WebPEncoderConfig config)
		{
			if (pixels == null)
			{
				throw new ArgumentNullException("pixels");
			}
			if (config == null)
			{
				throw new ArgumentNullException("config");
			}
			if (width <= 0)
			{
				throw new ArgumentOutOfRangeException("width");
			}
			if (height <= 0)
			{
				throw new ArgumentOutOfRangeException("height");
			}
			if (!config.Validate())
			{
				throw new ArgumentException("Invalid WebP encoder configuration", "config");
			}
			WebPConfig nativeConfig = config.GetNativeConfig();
			WebPPicture picture = default(WebPPicture);
			NativeLibraryLoader.FixDllNotFoundException("webp", delegate
			{
				if (NativeMethods.WebPPictureInitInternal(ref picture, 528) == 0)
				{
					throw new Exception("WebP version mismatch: failed to initialize picture");
				}
				return 0;
			});
			picture.width = width;
			picture.height = height;
			picture.use_argb = 1;
			GCHandle gCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned);
			EncodeOutput encodeOutput = new EncodeOutput();
			GCHandle value = GCHandle.Alloc(encodeOutput);
			_writerDelegate = ManagedWriter;
			try
			{
				IntPtr pixelPtr = gCHandle.AddrOfPinnedObject();
				if (format switch
				{
					WebPPixelFormat.Bgra => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportBGRA(ref picture, pixelPtr, stride)), 
					WebPPixelFormat.Rgba => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportRGBA(ref picture, pixelPtr, stride)), 
					WebPPixelFormat.Bgr => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportBGR(ref picture, pixelPtr, stride)), 
					WebPPixelFormat.Rgb => NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPPictureImportRGB(ref picture, pixelPtr, stride)), 
					_ => throw new ArgumentOutOfRangeException("format"), 
				} == 0)
				{
					throw new Exception("Failed to import pixel data into WebPPicture");
				}
				picture.writer = _writerDelegate;
				picture.custom_ptr = GCHandle.ToIntPtr(value);
				if (NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPEncode(ref nativeConfig, ref picture)) == 0)
				{
					throw new Exception($"WebP encode failed with error: {picture.error_code}");
				}
				return encodeOutput.Stream.ToArray();
			}
			finally
			{
				NativeLibraryLoader.FixDllNotFoundException("webp", delegate
				{
					NativeMethods.WebPPictureFree(ref picture);
					return 0;
				});
				gCHandle.Free();
				value.Free();
				_writerDelegate = null;
			}
		}

		public static void Encode(byte[] pixels, int width, int height, int stride, WebPPixelFormat format, WebPEncoderConfig config, Stream outputStream)
		{
			if (outputStream == null)
			{
				throw new ArgumentNullException("outputStream");
			}
			byte[] array = Encode(pixels, width, height, stride, format, config);
			outputStream.Write(array, 0, array.Length);
		}
	}
	public class WebPEncoderConfig
	{
		private WebPConfig _config;

		private bool _initialized;

		public WebPEncoderConfig()
		{
			_config = default(WebPConfig);
			if (NativeMethods.WebPConfigInit(ref _config) == 0)
			{
				throw new Exception("WebP version mismatch: failed to initialize config");
			}
			_initialized = true;
		}

		public WebPEncoderConfig(WebPPreset preset, float quality)
		{
			_config = default(WebPConfig);
			if (NativeMethods.WebPConfigPreset(ref _config, preset, quality) == 0)
			{
				throw new Exception("WebP version mismatch: failed to initialize config with preset");
			}
			_initialized = true;
		}

		public WebPEncoderConfig SetQuality(float quality)
		{
			EnsureInitialized();
			_config.quality = Math.Max(0f, Math.Min(100f, quality));
			_config.lossless = 0;
			return this;
		}

		public WebPEncoderConfig SetLossless(bool lossless = true)
		{
			EnsureInitialized();
			_config.lossless = (lossless ? 1 : 0);
			return this;
		}

		public WebPEncoderConfig SetLosslessPreset(int level)
		{
			EnsureInitialized();
			_config.lossless = 1;
			NativeMethods.WebPConfigLosslessPreset(ref _config, level);
			return this;
		}

		public WebPEncoderConfig SetMethod(int method)
		{
			EnsureInitialized();
			_config.method = Math.Max(0, Math.Min(6, method));
			return this;
		}

		public WebPEncoderConfig SetNearLossless(int level)
		{
			EnsureInitialized();
			_config.near_lossless = Math.Max(0, Math.Min(100, level));
			return this;
		}

		public WebPEncoderConfig SetTargetSize(int bytes)
		{
			EnsureInitialized();
			_config.target_size = bytes;
			return this;
		}

		public WebPEncoderConfig SetTargetPSNR(float psnr)
		{
			EnsureInitialized();
			_config.target_PSNR = psnr;
			return this;
		}

		public WebPEncoderConfig SetMultiThreaded(bool enabled = true)
		{
			EnsureInitialized();
			_config.thread_level = (enabled ? 1 : 0);
			return this;
		}

		public WebPEncoderConfig SetSnsStrength(int strength)
		{
			EnsureInitialized();
			_config.sns_strength = Math.Max(0, Math.Min(100, strength));
			return this;
		}

		public WebPEncoderConfig SetFilterStrength(int strength)
		{
			EnsureInitialized();
			_config.filter_strength = Math.Max(0, Math.Min(100, strength));
			return this;
		}

		public WebPEncoderConfig SetAlphaQuality(int quality)
		{
			EnsureInitialized();
			_config.alpha_quality = Math.Max(0, Math.Min(100, quality));
			return this;
		}

		public WebPEncoderConfig SetImageHint(WebPImageHint hint)
		{
			EnsureInitialized();
			_config.image_hint = hint;
			return this;
		}

		public WebPEncoderConfig SetExact(bool exact = true)
		{
			EnsureInitialized();
			_config.exact = (exact ? 1 : 0);
			return this;
		}

		public WebPEncoderConfig SetSharpYuv(bool enabled = true)
		{
			EnsureInitialized();
			_config.use_sharp_yuv = (enabled ? 1 : 0);
			return this;
		}

		public bool Validate()
		{
			EnsureInitialized();
			return NativeMethods.WebPValidateConfig(ref _config) != 0;
		}

		public WebPConfig GetNativeConfig()
		{
			EnsureInitialized();
			return _config;
		}

		private void EnsureInitialized()
		{
			if (!_initialized)
			{
				throw new InvalidOperationException("Config not initialized");
			}
		}
	}
	public class WebPImageInfo
	{
		public int Width { get; }

		public int Height { get; }

		public bool HasAlpha { get; }

		public bool HasAnimation { get; }

		public int Format { get; }

		internal WebPImageInfo(int width, int height, bool hasAlpha, bool hasAnimation, int format)
		{
			Width = width;
			Height = height;
			HasAlpha = hasAlpha;
			HasAnimation = hasAnimation;
			Format = format;
		}
	}
	public static class WebPInfo
	{
		public static bool TryGetSize(byte[] data, out int width, out int height)
		{
			width = 0;
			height = 0;
			if (data == null || data.Length < 12)
			{
				return false;
			}
			GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
			try
			{
				IntPtr ptr = gCHandle.AddrOfPinnedObject();
				UIntPtr size = (UIntPtr)(ulong)data.Length;
				int w = 0;
				int h = 0;
				int num = NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetInfo(ptr, size, ref w, ref h));
				width = w;
				height = h;
				return num != 0;
			}
			finally
			{
				gCHandle.Free();
			}
		}

		public static WebPImageInfo GetImageInfo(byte[] data)
		{
			if (data == null)
			{
				throw new ArgumentNullException("data");
			}
			if (data.Length < 12)
			{
				throw new ArgumentException("Data too short to be a valid WebP file", "data");
			}
			GCHandle gCHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
			try
			{
				return GetImageInfo(gCHandle.AddrOfPinnedObject(), data.Length);
			}
			finally
			{
				gCHandle.Free();
			}
		}

		public static WebPImageInfo GetImageInfo(IntPtr data, long length)
		{
			if (data == IntPtr.Zero)
			{
				throw new ArgumentNullException("data");
			}
			if (length < 12)
			{
				throw new ArgumentException("Data too short to be a valid WebP file", "length");
			}
			WebPBitstreamFeatures features = default(WebPBitstreamFeatures);
			VP8StatusCode vP8StatusCode = NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetFeatures(data, (UIntPtr)(ulong)length, ref features));
			if (vP8StatusCode != VP8StatusCode.VP8_STATUS_OK)
			{
				throw new Exception($"Failed to get WebP features: {vP8StatusCode}");
			}
			return new WebPImageInfo(features.width, features.height, features.has_alpha != 0, features.has_animation != 0, features.format);
		}
	}
}
namespace Imazen.WebP.Extern
{
	public class NativeMethods
	{
		public const int WEBP_DECODER_ABI_VERSION = 528;

		public const int WEBP_ENCODER_ABI_VERSION = 528;

		public const int WEBP_MAX_DIMENSION = 16383;

		public const int WEBP_DEMUX_ABI_VERSION = 263;

		public const int WEBP_MUX_ABI_VERSION = 265;

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPGetDecoderVersion();

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPGetInfo([In] IntPtr data, UIntPtr data_size, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeRGBA([In] IntPtr data, UIntPtr data_size, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeARGB([In] IntPtr data, UIntPtr data_size, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeBGRA([In] IntPtr data, UIntPtr data_size, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeRGB([In] IntPtr data, UIntPtr data_size, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeBGR([In] IntPtr data, UIntPtr data_size, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeYUV([In] IntPtr data, UIntPtr data_size, ref int width, ref int height, ref IntPtr u, ref IntPtr v, ref int stride, ref int uv_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeRGBAInto([In] IntPtr data, UIntPtr data_size, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeARGBInto([In] IntPtr data, UIntPtr data_size, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeBGRAInto([In] IntPtr data, UIntPtr data_size, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeRGBInto([In] IntPtr data, UIntPtr data_size, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeBGRInto([In] IntPtr data, UIntPtr data_size, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPDecodeYUVInto([In] IntPtr data, UIntPtr data_size, IntPtr luma, UIntPtr luma_size, int luma_stride, IntPtr u, UIntPtr u_size, int u_stride, IntPtr v, UIntPtr v_size, int v_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPInitDecBufferInternal(ref WebPDecBuffer param0, int param1);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPFreeDecBuffer(ref WebPDecBuffer buffer);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPINewDecoder(ref WebPDecBuffer output_buffer);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPINewRGB(WEBP_CSP_MODE csp, IntPtr output_buffer, UIntPtr output_buffer_size, int output_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPINewYUVA(IntPtr luma, UIntPtr luma_size, int luma_stride, IntPtr u, UIntPtr u_size, int u_stride, IntPtr v, UIntPtr v_size, int v_stride, IntPtr a, UIntPtr a_size, int a_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPINewYUV(IntPtr luma, UIntPtr luma_size, int luma_stride, IntPtr u, UIntPtr u_size, int u_stride, IntPtr v, UIntPtr v_size, int v_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPIDelete(ref WebPIDecoder idec);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern VP8StatusCode WebPIAppend(ref WebPIDecoder idec, [In] IntPtr data, UIntPtr data_size);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern VP8StatusCode WebPIUpdate(ref WebPIDecoder idec, [In] IntPtr data, UIntPtr data_size);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPIDecGetRGB(ref WebPIDecoder idec, ref int last_y, ref int width, ref int height, ref int stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPIDecGetYUVA(ref WebPIDecoder idec, ref int last_y, ref IntPtr u, ref IntPtr v, ref IntPtr a, ref int width, ref int height, ref int stride, ref int uv_stride, ref int a_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPIDecodedArea(ref WebPIDecoder idec, ref int left, ref int top, ref int width, ref int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern VP8StatusCode WebPGetFeaturesInternal([In] IntPtr param0, UIntPtr param1, ref WebPBitstreamFeatures param2, int param3);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPInitDecoderConfigInternal(ref WebPDecoderConfig param0, int param1);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPIDecode([In] IntPtr data, UIntPtr data_size, ref WebPDecoderConfig config);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern VP8StatusCode WebPDecode([In] IntPtr data, UIntPtr data_size, ref WebPDecoderConfig config);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPValidateDecoderConfig(ref WebPDecoderConfig config);

		public static bool WebPIsPremultipliedMode(WEBP_CSP_MODE mode)
		{
			if (mode != WEBP_CSP_MODE.MODE_rgbA && mode != WEBP_CSP_MODE.MODE_bgrA && mode != WEBP_CSP_MODE.MODE_Argb)
			{
				return mode == WEBP_CSP_MODE.MODE_rgbA_4444;
			}
			return true;
		}

		public static bool WebPIsRGBMode(WEBP_CSP_MODE mode)
		{
			return mode < WEBP_CSP_MODE.MODE_YUV;
		}

		public static bool WebPIsAlphaMode(WEBP_CSP_MODE mode)
		{
			if (mode != WEBP_CSP_MODE.MODE_RGBA && mode != WEBP_CSP_MODE.MODE_BGRA && mode != WEBP_CSP_MODE.MODE_ARGB && mode != WEBP_CSP_MODE.MODE_RGBA_4444 && mode != WEBP_CSP_MODE.MODE_YUVA)
			{
				return WebPIsPremultipliedMode(mode);
			}
			return true;
		}

		public static VP8StatusCode WebPGetFeatures(IntPtr data, UIntPtr data_size, ref WebPBitstreamFeatures features)
		{
			return WebPGetFeaturesInternal(data, data_size, ref features, 528);
		}

		public static int WebPInitDecoderConfig(ref WebPDecoderConfig config)
		{
			return WebPInitDecoderConfigInternal(ref config, 528);
		}

		public static int WebPInitDecBuffer(ref WebPDecBuffer buffer)
		{
			return WebPInitDecBufferInternal(ref buffer, 528);
		}

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimDecoderOptionsInitInternal(ref WebPAnimDecoderOptions dec_options, int abi_version);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPAnimDecoderNewInternal(ref WebPData webp_data, ref WebPAnimDecoderOptions dec_options, int abi_version);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPAnimDecoderNewInternal")]
		public static extern IntPtr WebPAnimDecoderNewInternalDefault(ref WebPData webp_data, IntPtr dec_options, int abi_version);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimDecoderGetInfo(IntPtr dec, ref WebPAnimInfo info);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimDecoderGetNext(IntPtr dec, ref IntPtr buf, ref int timestamp);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimDecoderHasMoreFrames(IntPtr dec);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPAnimDecoderReset(IntPtr dec);

		[DllImport("libwebpdemux", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPAnimDecoderDelete(IntPtr dec);

		public static int WebPAnimDecoderOptionsInit(ref WebPAnimDecoderOptions dec_options)
		{
			return WebPAnimDecoderOptionsInitInternal(ref dec_options, 263);
		}

		public static IntPtr WebPAnimDecoderNew(ref WebPData webp_data, ref WebPAnimDecoderOptions dec_options)
		{
			return WebPAnimDecoderNewInternal(ref webp_data, ref dec_options, 263);
		}

		public static IntPtr WebPAnimDecoderNewDefault(ref WebPData webp_data)
		{
			return WebPAnimDecoderNewInternalDefault(ref webp_data, IntPtr.Zero, 263);
		}

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPGetEncoderVersion();

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeRGB([In] IntPtr rgb, int width, int height, int stride, float quality_factor, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeBGR([In] IntPtr bgr, int width, int height, int stride, float quality_factor, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeRGBA([In] IntPtr rgba, int width, int height, int stride, float quality_factor, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeBGRA([In] IntPtr bgra, int width, int height, int stride, float quality_factor, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeLosslessRGB([In] IntPtr rgb, int width, int height, int stride, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeLosslessBGR([In] IntPtr bgr, int width, int height, int stride, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeLosslessRGBA([In] IntPtr rgba, int width, int height, int stride, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern UIntPtr WebPEncodeLosslessBGRA([In] IntPtr bgra, int width, int height, int stride, ref IntPtr output);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPConfigInitInternal(ref WebPConfig param0, WebPPreset param1, float param2, int param3);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPConfigLosslessPreset(ref WebPConfig config, int level);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPValidateConfig(ref WebPConfig config);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPMemoryWriterInit(ref WebPMemoryWriter writer);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPMemoryWriterClear(ref WebPMemoryWriter writer);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPMemoryWrite([In] IntPtr data, UIntPtr data_size, ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureInitInternal(ref WebPPicture param0, int param1);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureAlloc(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPPictureFree(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureCopy(ref WebPPicture src, ref WebPPicture dst);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureDistortion(ref WebPPicture src, ref WebPPicture reference, int metric_type, ref float result);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureCrop(ref WebPPicture picture, int left, int top, int width, int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureView(ref WebPPicture src, int left, int top, int width, int height, ref WebPPicture dst);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureIsView(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureRescale(ref WebPPicture pic, int width, int height);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureImportRGB(ref WebPPicture picture, [In] IntPtr rgb, int rgb_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureImportRGBA(ref WebPPicture picture, [In] IntPtr rgba, int rgba_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureSmartARGBToYUVA(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureImportRGBX(ref WebPPicture picture, [In] IntPtr rgbx, int rgbx_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureImportBGR(ref WebPPicture picture, [In] IntPtr bgr, int bgr_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureImportBGRA(ref WebPPicture picture, [In] IntPtr bgra, int bgra_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureImportBGRX(ref WebPPicture picture, [In] IntPtr bgrx, int bgrx_stride);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureARGBToYUVA(ref WebPPicture picture, WebPEncCSP colorspace);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureYUVAToARGB(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPCleanupTransparentArea(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureHasTransparency(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureARGBToYUVADithered(ref WebPPicture picture, WebPEncCSP colorspace, float dithering);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPictureSharpARGBToYUVA(ref WebPPicture picture);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPPlaneDistortion([In] IntPtr src, UIntPtr src_stride, [In] IntPtr reference, UIntPtr ref_stride, int width, int height, UIntPtr x_step, int type, ref float distortion, ref float result);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPBlendAlpha(ref WebPPicture picture, uint background_rgb);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPEncode(ref WebPConfig config, ref WebPPicture picture);

		public static int WebPConfigInit(ref WebPConfig config)
		{
			return WebPConfigInitInternal(ref config, WebPPreset.WEBP_PRESET_DEFAULT, 75f, 528);
		}

		public static int WebPConfigPreset(ref WebPConfig config, WebPPreset preset, float quality)
		{
			return WebPConfigInitInternal(ref config, preset, quality, 528);
		}

		public static int WebPPictureInit(ref WebPPicture picture)
		{
			return WebPPictureInitInternal(ref picture, 528);
		}

		public static void WebPSafeFree(IntPtr toDeallocate)
		{
			NativeLibraryLoader.FixDllNotFoundException("webp", delegate
			{
				WebPFree(toDeallocate);
				return 0;
			});
		}

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPFree(IntPtr toDeallocate);

		[DllImport("libwebp", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPMalloc(UIntPtr size);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimEncoderOptionsInitInternal(ref WebPAnimEncoderOptions enc_options, int abi_version);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPAnimEncoderNewInternal(int width, int height, ref WebPAnimEncoderOptions enc_options, int abi_version);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPAnimEncoderNewInternal")]
		public static extern IntPtr WebPAnimEncoderNewInternalDefault(int width, int height, IntPtr enc_options, int abi_version);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimEncoderAdd(IntPtr enc, ref WebPPicture frame, int timestamp_ms, ref WebPConfig config);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPAnimEncoderAdd")]
		public static extern int WebPAnimEncoderAddDefaultConfig(IntPtr enc, ref WebPPicture frame, int timestamp_ms, IntPtr config);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl, EntryPoint = "WebPAnimEncoderAdd")]
		public static extern int WebPAnimEncoderAddNull(IntPtr enc, IntPtr frame, int timestamp_ms, IntPtr config);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl)]
		public static extern int WebPAnimEncoderAssemble(IntPtr enc, ref WebPData webp_data);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl)]
		public static extern IntPtr WebPAnimEncoderGetError(IntPtr enc);

		[DllImport("libwebpmux", CallingConvention = CallingConvention.Cdecl)]
		public static extern void WebPAnimEncoderDelete(IntPtr enc);

		public static int WebPAnimEncoderOptionsInit(ref WebPAnimEncoderOptions enc_options)
		{
			return WebPAnimEncoderOptionsInitInternal(ref enc_options, 265);
		}

		public static IntPtr WebPAnimEncoderNew(int width, int height, ref WebPAnimEncoderOptions enc_options)
		{
			return WebPAnimEncoderNewInternal(width, height, ref enc_options, 265);
		}

		public static IntPtr WebPAnimEncoderNewDefault(int width, int height)
		{
			return WebPAnimEncoderNewInternalDefault(width, height, IntPtr.Zero, 265);
		}
	}
	public struct WebPIDecoder
	{
	}
	public enum WEBP_CSP_MODE
	{
		MODE_RGB,
		MODE_RGBA,
		MODE_BGR,
		MODE_BGRA,
		MODE_ARGB,
		MODE_RGBA_4444,
		MODE_RGB_565,
		MODE_rgbA,
		MODE_bgrA,
		MODE_Argb,
		MODE_rgbA_4444,
		MODE_YUV,
		MODE_YUVA,
		MODE_LAST
	}
	public struct WebPRGBABuffer
	{
		public IntPtr rgba;

		public int stride;

		public UIntPtr size;
	}
	public struct WebPYUVABuffer
	{
		public IntPtr y;

		public IntPtr u;

		public IntPtr v;

		public IntPtr a;

		public int y_stride;

		public int u_stride;

		public int v_stride;

		public int a_stride;

		public UIntPtr y_size;

		public UIntPtr u_size;

		public UIntPtr v_size;

		public UIntPtr a_size;
	}
	[StructLayout(LayoutKind.Explicit)]
	public struct Anonymous_690ed5ec_4c3d_40c6_9bd0_0747b5a28b54
	{
		[FieldOffset(0)]
		public WebPRGBABuffer RGBA;

		[FieldOffset(0)]
		public WebPYUVABuffer YUVA;
	}
	public struct WebPDecBuffer
	{
		public WEBP_CSP_MODE colorspace;

		public int width;

		public int height;

		public int is_external_memory;

		public Anonymous_690ed5ec_4c3d_40c6_9bd0_0747b5a28b54 u;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U4)]
		public uint[] pad;

		public IntPtr private_memory;
	}
	public enum VP8StatusCode
	{
		VP8_STATUS_OK,
		VP8_STATUS_OUT_OF_MEMORY,
		VP8_STATUS_INVALID_PARAM,
		VP8_STATUS_BITSTREAM_ERROR,
		VP8_STATUS_UNSUPPORTED_FEATURE,
		VP8_STATUS_SUSPENDED,
		VP8_STATUS_USER_ABORT,
		VP8_STATUS_NOT_ENOUGH_DATA
	}
	public struct WebPBitstreamFeatures
	{
		public int width;

		public int height;

		public int has_alpha;

		public int has_animation;

		public int format;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)]
		public uint[] pad;
	}
	public struct WebPDecoderOptions
	{
		public int bypass_filtering;

		public int no_fancy_upsampling;

		public int use_cropping;

		public int crop_left;

		public int crop_top;

		public int crop_width;

		public int crop_height;

		public int use_scaling;

		public int scaled_width;

		public int scaled_height;

		public int use_threads;

		public int dithering_strength;

		public int flip;

		public int alpha_dithering_strength;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.U4)]
		public uint[] pad;
	}
	public struct WebPDecoderConfig
	{
		public WebPBitstreamFeatures input;

		public WebPDecBuffer output;

		public WebPDecoderOptions options;
	}
	public struct WebPAnimDecoderOptions
	{
		public WEBP_CSP_MODE color_mode;

		public int use_threads;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 7, ArraySubType = UnmanagedType.U4)]
		public uint[] padding;
	}
	public struct WebPAnimInfo
	{
		public uint canvas_width;

		public uint canvas_height;

		public uint loop_count;

		public uint bgcolor;

		public uint frame_count;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U4)]
		public uint[] pad;
	}
	public enum WebPImageHint
	{
		WEBP_HINT_DEFAULT,
		WEBP_HINT_PICTURE,
		WEBP_HINT_PHOTO,
		WEBP_HINT_GRAPH,
		WEBP_HINT_LAST
	}
	public struct WebPConfig
	{
		public int lossless;

		public float quality;

		public int method;

		public WebPImageHint image_hint;

		public int target_size;

		public float target_PSNR;

		public int segments;

		public int sns_strength;

		public int filter_strength;

		public int filter_sharpness;

		public int filter_type;

		public int autofilter;

		public int alpha_compression;

		public int alpha_filtering;

		public int alpha_quality;

		public int pass;

		public int show_compressed;

		public int preprocessing;

		public int partitions;

		public int partition_limit;

		public int emulate_jpeg_size;

		public int thread_level;

		public int low_memory;

		public int near_lossless;

		public int exact;

		public int use_delta_palette;

		public int use_sharp_yuv;

		public int qmin;

		public int qmax;
	}
	public enum WebPPreset
	{
		WEBP_PRESET_DEFAULT,
		WEBP_PRESET_PICTURE,
		WEBP_PRESET_PHOTO,
		WEBP_PRESET_DRAWING,
		WEBP_PRESET_ICON,
		WEBP_PRESET_TEXT
	}
	public struct WebPAuxStats
	{
		public int coded_size;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5, ArraySubType = UnmanagedType.R4)]
		public float[] PSNR;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I4)]
		public int[] block_count;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.I4)]
		public int[] header_bytes;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 12, ArraySubType = UnmanagedType.I4)]
		public int[] residual_bytes;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.I4)]
		public int[] segment_size;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.I4)]
		public int[] segment_quant;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.I4)]
		public int[] segment_level;

		public int alpha_data_size;

		public int layer_data_size;

		public uint lossless_features;

		public int histogram_bits;

		public int transform_bits;

		public int cache_bits;

		public int palette_size;

		public int lossless_size;

		public int lossless_hdr_size;

		public int lossless_data_size;

		public int cross_color_transform_bits;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)]
		public uint[] pad;
	}
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate int WebPWriterFunction([In] IntPtr data, UIntPtr data_size, ref WebPPicture picture);
	public struct WebPMemoryWriter
	{
		public IntPtr mem;

		public UIntPtr size;

		public UIntPtr max_size;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = UnmanagedType.U4)]
		public uint[] pad;
	}
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	public delegate int WebPProgressHook(int percent, ref WebPPicture picture);
	public enum WebPEncCSP
	{
		WEBP_YUV420 = 0,
		WEBP_CSP_UV_MASK = 3,
		WEBP_YUV420A = 4,
		WEBP_CSP_ALPHA_BIT = 4
	}
	public enum WebPEncodingError
	{
		VP8_ENC_OK,
		VP8_ENC_ERROR_OUT_OF_MEMORY,
		VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY,
		VP8_ENC_ERROR_NULL_PARAMETER,
		VP8_ENC_ERROR_INVALID_CONFIGURATION,
		VP8_ENC_ERROR_BAD_DIMENSION,
		VP8_ENC_ERROR_PARTITION0_OVERFLOW,
		VP8_ENC_ERROR_PARTITION_OVERFLOW,
		VP8_ENC_ERROR_BAD_WRITE,
		VP8_ENC_ERROR_FILE_TOO_BIG,
		VP8_ENC_ERROR_USER_ABORT,
		VP8_ENC_ERROR_LAST
	}
	public struct WebPPicture
	{
		public int use_argb;

		public WebPEncCSP colorspace;

		public int width;

		public int height;

		public IntPtr y;

		public IntPtr u;

		public IntPtr v;

		public int y_stride;

		public int uv_stride;

		public IntPtr a;

		public int a_stride;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.U4)]
		public uint[] pad1;

		public IntPtr argb;

		public int argb_stride;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U4)]
		public uint[] pad2;

		public WebPWriterFunction writer;

		public IntPtr custom_ptr;

		public int extra_info_type;

		public IntPtr extra_info;

		public IntPtr stats;

		public WebPEncodingError error_code;

		public WebPProgressHook progress_hook;

		public IntPtr user_data;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.U4)]
		public uint[] pad3;

		public IntPtr pad4;

		public IntPtr pad5;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8, ArraySubType = UnmanagedType.U4)]
		public uint[] pad6;

		public IntPtr memory_;

		public IntPtr memory_argb_;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.SysUInt)]
		public IntPtr[] pad7;
	}
	[Obsolete("Use NativeLibraryLoader.FixDllNotFoundException instead. Library loading is now automatic.")]
	public static class LoadLibrary
	{
		public static void LoadWebPOrFail()
		{
			NativeLibraryLoader.FixDllNotFoundException("webp", () => NativeMethods.WebPGetDecoderVersion());
		}

		[Obsolete]
		public static bool AutoLoadNearby(string name, bool throwFailure)
		{
			LoadWebPOrFail();
			return true;
		}
	}
	public struct WebPMuxAnimParams
	{
		public uint bgcolor;

		public int loop_count;
	}
	public struct WebPAnimEncoderOptions
	{
		public WebPMuxAnimParams anim_params;

		public int minimize_size;

		public int kmin;

		public int kmax;

		public int allow_mixed;

		public int verbose;

		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4, ArraySubType = UnmanagedType.U4)]
		public uint[] padding;
	}
	public struct WebPData
	{
		public IntPtr bytes;

		public UIntPtr size;
	}
	public enum WebPMuxAnimDispose
	{
		WEBP_MUX_DISPOSE_NONE,
		WEBP_MUX_DISPOSE_BACKGROUND
	}
	public enum WebPMuxAnimBlend
	{
		WEBP_MUX_BLEND,
		WEBP_MUX_NO_BLEND
	}
	[Flags]
	public enum WebPFeatureFlags : uint
	{
		ANIMATION_FLAG = 2u,
		XMP_FLAG = 4u,
		EXIF_FLAG = 8u,
		ALPHA_FLAG = 0x10u,
		ICCP_FLAG = 0x20u,
		ALL_VALID_FLAGS = 0x3Eu
	}
}

BepInEx/plugins/ValheimEventClips/Encoder/ValheimEventClips.Encoder.exe

Decompiled 4 hours ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Imazen.WebP;
using Microsoft.CodeAnalysis;

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
internal static class Program
{
	public static int Main(string[] args)
	{
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Expected O, but got Unknown
		//IL_0187: Unknown result type (might be due to invalid IL or missing references)
		//IL_01aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b1: Expected O, but got Unknown
		string text = null;
		try
		{
			if (args.Length == 2 && args[0] == "--verify")
			{
				AnimDecoder val = new AnimDecoder(File.ReadAllBytes(args[1]), false);
				try
				{
					int num = 0;
					int num2 = 0;
					while (val.HasMoreFrames())
					{
						AnimFrame nextFrame = val.GetNextFrame();
						num++;
						num2 += nextFrame.DurationMs;
					}
					Console.WriteLine("frames={0} width={1} height={2} decoder_duration_ms={3:F0}", num, val.Info.Width, val.Info.Height, num2);
				}
				finally
				{
					((IDisposable)val)?.Dispose();
				}
				return 0;
			}
			if (args.Length != 1)
			{
				throw new ArgumentException("Expected output WebP path");
			}
			Stopwatch stopwatch = Stopwatch.StartNew();
			using (BinaryReader binaryReader = new BinaryReader(Console.OpenStandardInput()))
			{
				if (binaryReader.ReadInt32() != 826492246)
				{
					throw new InvalidDataException("Bad VEC1 header");
				}
				int num3 = binaryReader.ReadInt32();
				int num4 = binaryReader.ReadInt32();
				int num5 = binaryReader.ReadInt32();
				int num6 = binaryReader.ReadInt32();
				bool flag = binaryReader.ReadBoolean();
				if (num3 < 16 || num3 > 1920 || num4 < 16 || num4 > 1080 || num5 < 1 || num5 > 1800 || num6 < 1 || num6 > 100)
				{
					throw new InvalidDataException("Invalid capture dimensions/count/quality");
				}
				int num7 = checked(num3 * num4 * 4);
				if ((long)num7 * (long)num5 > 268435456)
				{
					throw new InvalidDataException("Clip exceeds encoder's 256 MiB raw-frame limit");
				}
				byte[] array = new byte[num7];
				WebPEncoderConfig val2 = new WebPEncoderConfig().SetQuality((float)num6).SetMethod(3).SetMultiThreaded(false);
				AnimEncoder val3 = new AnimEncoder(num3, num4, 0, 0u, false, false);
				try
				{
					int num8 = 0;
					int num9 = 0;
					Stopwatch stopwatch2 = Stopwatch.StartNew();
					for (int i = 0; i < num5; i++)
					{
						int num10 = binaryReader.ReadInt32();
						if (num10 < 1 || num10 > 60000 || (num8 += num10) > 60000)
						{
							throw new InvalidDataException("Invalid frame timing");
						}
						int num11;
						for (int j = 0; j < num7; j += num11)
						{
							num11 = binaryReader.Read(array, j, num7 - j);
							if (num11 == 0)
							{
								throw new EndOfStreamException("Incomplete frame");
							}
						}
						if (flag)
						{
							int num12 = num3 * 4;
							for (int k = 0; k < num4 / 2; k++)
							{
								for (int l = 0; l < num12; l++)
								{
									int num13 = k * num12 + l;
									int num14 = (num4 - k - 1) * num12 + l;
									byte b = array[num13];
									array[num13] = array[num14];
									array[num14] = b;
								}
							}
						}
						val3.AddFrame(array, num3 * 4, (WebPPixelFormat)1, num8 - num10, val2);
						num9 = num10;
					}
					string fullPath = Path.GetFullPath(args[0]);
					Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
					text = fullPath + ".partial";
					FieldInfo? field = typeof(AnimEncoder).GetField("_lastDuration", BindingFlags.Instance | BindingFlags.NonPublic);
					if (field == null)
					{
						throw new MissingFieldException("Pinned animation duration contract changed");
					}
					field.SetValue(val3, num9);
					File.WriteAllBytes(text, val3.Assemble());
					stopwatch2.Stop();
					File.Move(text, fullPath);
					text = null;
					Console.WriteLine("frames={0} duration_ms={1} elapsed_ms={2} encode_ms={3} bytes={4} helper_peak_mib={5:F1}", num5, num8, stopwatch.ElapsedMilliseconds, stopwatch2.ElapsedMilliseconds, new FileInfo(fullPath).Length, (double)Process.GetCurrentProcess().PeakWorkingSet64 / 1048576.0);
				}
				finally
				{
					((IDisposable)val3)?.Dispose();
				}
			}
			return 0;
		}
		catch (Exception ex)
		{
			Console.Error.WriteLine(ex.GetType().Name + ": " + ex.Message);
			return 1;
		}
		finally
		{
			if (text != null)
			{
				try
				{
					File.Delete(text);
				}
				catch
				{
				}
			}
		}
	}
}