Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Valheim Moments v0.8.1
BepInEx/plugins/ValheimEventClips/ValheimEventClips.dll
Decompiled 4 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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 agousing 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 agousing 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 { } } } } }