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 Discord Screenshots v1.6.5
discordScreenshots.dll
Decompiled 2 days agousing System; using System.Collections; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; 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.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Extensions; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; using discordScreenshots.Patches; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("discordScreenshots")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("discordScreenshots")] [assembly: AssemblyCopyright("Copyright © 2023")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("CA695DDD-2CE8-407B-B7A2-9FB6D3783286")] [assembly: AssemblyFileVersion("0.1.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] 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; } } } [HarmonyPatch(typeof(Player), "OnDamaged")] public static class RemoveDamageFlashOnDeath { [HarmonyPostfix] private static bool Prefix(Player __instance, HitData hit) { if (hit.GetTotalDamage() >= ((Character)__instance).GetHealth()) { return false; } return true; } } namespace discordScreenshots { public class BepinexConfiguration { public static ConfigFile Config; public static ConfigEntry<string> WebhookURL; public static ConfigEntry<string> WebhookUsername; public static ConfigEntry<string> WebhookAvatarURL; public static ConfigEntry<string> DeathMessage; public static ConfigEntry<KeyCode> ScreenshotHotkey; public static ConfigEntry<string> HotkeyScreenshotWebhookURL; public static ConfigEntry<string> HotkeyScreenshotWebhookUsername; public static ConfigEntry<string> HotkeyScreenshotWebhookAvatarURL; public static ConfigEntry<string> HotkeyScreenshotMessage; private static readonly Random _random = new Random(); private static string _localWebhookURL = ""; private static string _localWebhookUsername = ""; private static string _localWebhookAvatarURL = ""; private static string _localHotkeyScreenshotWebhookURL = ""; private static string _localHotkeyScreenshotWebhookUsername = ""; private static string _localHotkeyScreenshotWebhookAvatarURL = ""; public static string GetRandomDeathMessage() { string value = DeathMessage.Value; if (string.IsNullOrEmpty(value)) { return "met their demise!"; } string[] array = (from m in value.Split(new char[1] { ';' }) select m.Trim() into m where !string.IsNullOrEmpty(m) select m).ToArray(); if (array.Length == 0) { return "met their demise!"; } return array[_random.Next(array.Length)]; } public static string GetWebhookURL() { return GetSyncedValueOrLocalFallback(WebhookURL, _localWebhookURL); } public static string GetWebhookUsername() { if (ShouldUseLocalFallback(WebhookURL, _localWebhookURL)) { return _localWebhookUsername; } return GetSyncedValueOrLocalFallback(WebhookUsername, _localWebhookUsername); } public static string GetWebhookAvatarURL() { if (ShouldUseLocalFallback(WebhookURL, _localWebhookURL)) { return _localWebhookAvatarURL; } return GetSyncedValueOrLocalFallback(WebhookAvatarURL, _localWebhookAvatarURL); } public static string GetHotkeyScreenshotWebhookURL() { return GetSyncedValueOrLocalFallback(HotkeyScreenshotWebhookURL, _localHotkeyScreenshotWebhookURL); } public static string GetHotkeyScreenshotWebhookUsername() { if (ShouldUseLocalFallback(HotkeyScreenshotWebhookURL, _localHotkeyScreenshotWebhookURL)) { return _localHotkeyScreenshotWebhookUsername; } return GetSyncedValueOrLocalFallback(HotkeyScreenshotWebhookUsername, _localHotkeyScreenshotWebhookUsername); } public static string GetHotkeyScreenshotWebhookAvatarURL() { if (ShouldUseLocalFallback(HotkeyScreenshotWebhookURL, _localHotkeyScreenshotWebhookURL)) { return _localHotkeyScreenshotWebhookAvatarURL; } return GetSyncedValueOrLocalFallback(HotkeyScreenshotWebhookAvatarURL, _localHotkeyScreenshotWebhookAvatarURL); } public static void GenerateConfigs(ConfigFile configFile) { Config = configFile; ScreenshotHotkey = ConfigFileExtensions.BindConfig<KeyCode>(Config, "Screenshot", "Hotkey", (KeyCode)293, "The hotkey to take a screenshot and send to Discord.", false, (int?)1, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WebhookURL = ConfigFileExtensions.BindConfig<string>(Config, "Webhook", "URL", "", "The URL of the Discord webhook to send messages to.", true, (int?)2, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WebhookUsername = ConfigFileExtensions.BindConfig<string>(Config, "Webhook", "Username", "Valheim Death Bot", "The username of the Discord webhook to send messages to.", true, (int?)3, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); WebhookAvatarURL = ConfigFileExtensions.BindConfig<string>(Config, "Webhook", "AvatarURL", "", "The avatar URL of the Discord webhook to send messages to.", true, (int?)4, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); DeathMessage = ConfigFileExtensions.BindConfig<string>(Config, "Death Screenshot", "Message", "met their demise! Final moments captured...", "The message to send with death screenshots (player name will be prepended automatically).", true, (int?)5, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotWebhookURL = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "URL", "", "Optional separate webhook URL for player capture (F12) screenshots. If empty, uses the main Webhook URL.", true, (int?)6, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotWebhookUsername = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "Username", "Valheim Screenshot Bot", "The username for the player capture webhook.", true, (int?)7, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotWebhookAvatarURL = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "AvatarURL", "", "The avatar URL for the player capture webhook.", true, (int?)8, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); HotkeyScreenshotMessage = ConfigFileExtensions.BindConfig<string>(Config, "Player Capture Webhook", "Message", "captured this screenshot!", "The message to send with player capture screenshots (player name will be prepended automatically).", true, (int?)9, (AcceptableValueBase)null, (Action<ConfigEntryBase>)null, (ConfigurationManagerAttributes)null); _localWebhookURL = Normalize(WebhookURL.Value); _localWebhookUsername = Normalize(WebhookUsername.Value); _localWebhookAvatarURL = Normalize(WebhookAvatarURL.Value); _localHotkeyScreenshotWebhookURL = Normalize(HotkeyScreenshotWebhookURL.Value); _localHotkeyScreenshotWebhookUsername = Normalize(HotkeyScreenshotWebhookUsername.Value); _localHotkeyScreenshotWebhookAvatarURL = Normalize(HotkeyScreenshotWebhookAvatarURL.Value); } private static string GetSyncedValueOrLocalFallback(ConfigEntry<string> entry, string localFallback) { string text = Normalize(entry.Value); if (text.Length <= 0) { return localFallback; } return text; } private static bool ShouldUseLocalFallback(ConfigEntry<string> entry, string localFallback) { if (Normalize(entry.Value).Length == 0) { return localFallback.Length > 0; } return false; } private static string Normalize(string value) { if (!string.IsNullOrWhiteSpace(value)) { return value.Trim(); } return ""; } } public class SimpleDiscordWebhook { private const int LargeScreenshotPixelThreshold = 4000000; private const int LargeScreenshotJpegQuality = 90; private const int WebhookRequestTimeoutMilliseconds = 15000; private static ScreenshotEncoding _startupScreenshotEncoding = ScreenshotEncoding.Png; private static int _startupResolutionWidth; private static int _startupResolutionHeight; private static readonly HttpClient WebhookHttpClient = CreateHttpClient(); private readonly Uri _webhookUri; private readonly string? _username; private readonly string? _avatarUrl; public SimpleDiscordWebhook(string webhookUrl, string? username = null, string? avatarUrl = null) { if (!TryNormalizeWebhookUrl(webhookUrl, out Uri webhookUri, out string reason)) { throw new ArgumentException("Invalid webhook URL: " + reason, "webhookUrl"); } _webhookUri = webhookUri; _username = NormalizeOptionalText(username); _avatarUrl = NormalizeOptionalText(avatarUrl); } public static void ConfigureScreenshotEncodingForStartupResolution() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) int width = Screen.width; int height = Screen.height; if (width <= 0 || height <= 0) { Resolution currentResolution = Screen.currentResolution; width = ((Resolution)(ref currentResolution)).width; height = ((Resolution)(ref currentResolution)).height; } ConfigureScreenshotEncoding(width, height); } public static void ConfigureScreenshotEncoding(int width, int height) { _startupResolutionWidth = Math.Max(0, width); _startupResolutionHeight = Math.Max(0, height); _startupScreenshotEncoding = (((long)_startupResolutionWidth * (long)_startupResolutionHeight >= 4000000) ? ScreenshotEncoding.Jpeg : ScreenshotEncoding.Png); Debug.Log((object)($"DiscordScreenshots: startup resolution {_startupResolutionWidth}x{_startupResolutionHeight}; " + "using " + GetScreenshotFormatName() + " for screenshot uploads.")); } public static string CreateScreenshotFilename(string baseName, DateTime timestamp) { return $"{baseName}_{timestamp:yyyy-MM-dd_HH-mm-ss}.{GetScreenshotExtension()}"; } public async Task SendMessageAsync(string message) { if (string.IsNullOrEmpty(message)) { throw new ArgumentException("Message cannot be null or empty", "message"); } string jsonPayload = JsonConvert.SerializeObject((object)new SimpleWebhookPayload { content = message, username = _username, avatar_url = _avatarUrl }); await SendPayloadAsync(jsonPayload); } public void SendMessage(string message) { SendMessageAsync(message).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } public async Task SendScreenshotAsync(string? message = null, string? filename = null) { try { filename = NormalizeScreenshotFilename(filename); Texture2D val = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)val == (Object)null) { throw new Exception("Failed to capture screenshot - returned null texture"); } ScreenshotUploadData uploadData = ProcessScreenshotForUpload(val); Debug.Log((object)$"Screenshot captured and encoded as {uploadData.FormatName} - {uploadData.Data.Length} bytes, uploading..."); await Task.Run(async delegate { await SendFileAsync(uploadData.Data, filename, message, uploadData.ContentType); }); } catch (Exception ex) { throw new Exception("Error capturing and sending screenshot: " + ex.Message, ex); } } public static Texture2D CaptureScreenshot() { Texture2D obj = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)obj == (Object)null) { throw new Exception("Failed to capture screenshot - returned null texture"); } return obj; } public byte[] ProcessScreenshot(Texture2D screenshot) { return ProcessScreenshotForUpload(screenshot).Data; } public ScreenshotUploadData ProcessScreenshotForUpload(Texture2D screenshot) { byte[] array; string extension; string contentType; string text; if (_startupScreenshotEncoding == ScreenshotEncoding.Jpeg) { array = ImageConversion.EncodeToJPG(screenshot, 90); extension = "jpg"; contentType = "image/jpeg"; text = $"JPEG quality {90}"; } else { array = ImageConversion.EncodeToPNG(screenshot); extension = "png"; contentType = "image/png"; text = "PNG"; } Object.DestroyImmediate((Object)(object)screenshot); if (array == null || array.Length == 0) { throw new Exception("Failed to encode screenshot to " + text); } return new ScreenshotUploadData(array, extension, contentType, text); } public async Task SendFileAsync(byte[] fileData, string filename, string? message = null, string contentType = "image/png") { if (fileData == null || fileData.Length == 0) { throw new ArgumentException("File data cannot be null or empty", "fileData"); } if (string.IsNullOrEmpty(filename)) { throw new ArgumentException("Filename cannot be null or empty", "filename"); } string boundary = "----formdata-discord-" + DateTime.Now.Ticks.ToString("x"); using MemoryStream memoryStream = new MemoryStream(); await WriteMultipartFormDataAsync(memoryStream, boundary, fileData, filename, message, contentType); byte[] formData = memoryStream.ToArray(); await SendMultipartPayloadAsync(formData, boundary); } private async Task SendPayloadAsync(string jsonPayload) { _ = 1; try { StringContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); try { HttpResponseMessage response = await WebhookHttpClient.PostAsync(_webhookUri, (HttpContent)(object)content); try { string text = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"Discord webhook returned status: {(int)response.StatusCode} {response.StatusCode} - {text}"); } if (!string.IsNullOrEmpty(text)) { Debug.Log((object)("Discord response: " + text)); } } finally { ((IDisposable)response)?.Dispose(); } } finally { ((IDisposable)content)?.Dispose(); } } catch (Exception ex) { throw new Exception("Error sending Discord webhook: " + ex.Message, ex); } } private async Task SendMultipartPayloadAsync(byte[] formData, string boundary) { _ = 1; try { ByteArrayContent content = new ByteArrayContent(formData); try { ((HttpContent)content).Headers.ContentType = MediaTypeHeaderValue.Parse("multipart/form-data; boundary=" + boundary); HttpResponseMessage response = await WebhookHttpClient.PostAsync(_webhookUri, (HttpContent)(object)content); try { string text = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"Discord webhook returned status: {(int)response.StatusCode} {response.StatusCode} - {text}"); } if (!string.IsNullOrEmpty(text)) { Debug.Log((object)("Discord file upload response: " + text)); } } finally { ((IDisposable)response)?.Dispose(); } } finally { ((IDisposable)content)?.Dispose(); } } catch (Exception ex) { throw new Exception("Error sending Discord webhook file: " + ex.Message, ex); } } private async Task WriteMultipartFormDataAsync(Stream stream, string boundary, byte[] fileData, string filename, string? message, string contentType) { string newLine = "\r\n"; byte[] boundaryBytes = Encoding.UTF8.GetBytes("--" + boundary + newLine); await stream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); string s = "Content-Disposition: form-data; name=\"files[0]\"; filename=\"" + filename + "\"" + newLine + "Content-Type: " + contentType + newLine + newLine; byte[] bytes = Encoding.UTF8.GetBytes(s); await stream.WriteAsync(bytes, 0, bytes.Length); await stream.WriteAsync(fileData, 0, fileData.Length); byte[] newLineBytes = Encoding.UTF8.GetBytes(newLine); await stream.WriteAsync(newLineBytes, 0, newLineBytes.Length); if (!string.IsNullOrEmpty(message)) { await stream.WriteAsync(boundaryBytes, 0, boundaryBytes.Length); SimpleWebhookPayload simpleWebhookPayload = new SimpleWebhookPayload { content = message, username = _username, avatar_url = _avatarUrl }; string jsonPayload = JsonConvert.SerializeObject((object)simpleWebhookPayload); string s2 = "Content-Disposition: form-data; name=\"payload_json\"" + newLine + "Content-Type: application/json" + newLine + newLine; byte[] bytes2 = Encoding.UTF8.GetBytes(s2); await stream.WriteAsync(bytes2, 0, bytes2.Length); byte[] bytes3 = Encoding.UTF8.GetBytes(jsonPayload); await stream.WriteAsync(bytes3, 0, bytes3.Length); await stream.WriteAsync(newLineBytes, 0, newLineBytes.Length); } byte[] bytes4 = Encoding.UTF8.GetBytes("--" + boundary + "--" + newLine); await stream.WriteAsync(bytes4, 0, bytes4.Length); } private static HttpClient CreateHttpClient() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; ServicePointManager.Expect100Continue = false; HttpClient val = new HttpClient { Timeout = TimeSpan.FromMilliseconds(15000.0) }; val.DefaultRequestHeaders.UserAgent.ParseAdd("discord-screenshots"); return val; } public static async Task SendQuickMessageAsync(string webhookUrl, string message, string? username = null, string? avatarUrl = null) { await new SimpleDiscordWebhook(webhookUrl, username, avatarUrl).SendMessageAsync(message); } public static void SendQuickMessage(string webhookUrl, string message, string? username = null, string? avatarUrl = null) { SendQuickMessageAsync(webhookUrl, message, username, avatarUrl).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } public static async Task SendQuickScreenshotAsync(string webhookUrl, string? message = null, string? username = null, string? avatarUrl = null, string? filename = null) { await new SimpleDiscordWebhook(webhookUrl, username, avatarUrl).SendScreenshotAsync(message, filename); } public static void SendQuickScreenshot(string webhookUrl, string? message = null, string? username = null, string? avatarUrl = null, string? filename = null) { SendQuickScreenshotAsync(webhookUrl, message, username, avatarUrl, filename).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } private static string NormalizeScreenshotFilename(string? filename) { string screenshotExtension = GetScreenshotExtension(); if (string.IsNullOrEmpty(filename)) { return $"valheim_screenshot_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.{screenshotExtension}"; } string extension = Path.GetExtension(filename); if (string.IsNullOrEmpty(extension)) { return filename + "." + screenshotExtension; } if (!extension.Equals(".png", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".jpg", StringComparison.OrdinalIgnoreCase) && !extension.Equals(".jpeg", StringComparison.OrdinalIgnoreCase)) { return filename; } return Path.ChangeExtension(filename, screenshotExtension) ?? (filename + "." + screenshotExtension); } private static bool TryNormalizeWebhookUrl(string? webhookUrl, out Uri webhookUri, out string reason) { webhookUri = null; if (webhookUrl == null) { reason = "URL cannot be empty"; return false; } string text = webhookUrl.Trim(); if (text.Length == 0) { reason = "URL cannot be empty"; return false; } for (int i = 0; i < text.Length; i++) { if (char.IsControl(text[i])) { reason = "URL contains control characters"; return false; } } if (!Uri.TryCreate(text, UriKind.Absolute, out Uri result) || result == null) { reason = "URL must be an absolute HTTPS URL"; return false; } if (!string.Equals(result.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { reason = "URL must use HTTPS"; return false; } if (string.IsNullOrEmpty(result.Host)) { reason = "URL must include a host"; return false; } webhookUri = result; reason = string.Empty; return true; } private static string? NormalizeOptionalText(string? value) { if (value == null) { return null; } string text = value.Trim(); if (text.Length != 0) { return text; } return null; } private static string GetScreenshotExtension() { if (_startupScreenshotEncoding != ScreenshotEncoding.Jpeg) { return "png"; } return "jpg"; } private static string GetScreenshotFormatName() { if (_startupScreenshotEncoding != ScreenshotEncoding.Jpeg) { return "PNG"; } return $"JPEG quality {90}"; } } public sealed class ScreenshotUploadData { public byte[] Data { get; } public string Extension { get; } public string ContentType { get; } public string FormatName { get; } public ScreenshotUploadData(byte[] data, string extension, string contentType, string formatName) { Data = data; Extension = extension; ContentType = contentType; FormatName = formatName; } } internal enum ScreenshotEncoding { Png, Jpeg } internal class SimpleWebhookPayload { public string? content { get; set; } public string? username { get; set; } public string? avatar_url { get; set; } } [BepInPlugin("warpalicious.discordScreenshots", "discordScreenshots", "1.6.5")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class discordScreenshotsPlugin : BaseUnityPlugin { private const string ModName = "discordScreenshots"; private const string ModVersion = "1.6.5"; private const string Author = "warpalicious"; private const string ModGUID = "warpalicious.discordScreenshots"; private readonly Harmony HarmonyInstance = new Harmony("warpalicious.discordScreenshots"); public static readonly ManualLogSource TemplateLogger = Logger.CreateLogSource("discordScreenshots"); public static AssetBundle assetBundle; public void Awake() { Assembly executingAssembly = Assembly.GetExecutingAssembly(); HarmonyInstance.PatchAll(executingAssembly); BepinexConfiguration.GenerateConfigs(((BaseUnityPlugin)this).Config); SimpleDiscordWebhook.ConfigureScreenshotEncodingForStartupResolution(); } public static void LoadAssetBundle() { assetBundle = AssetUtils.LoadAssetBundleFromResources("discordscreenshots", Assembly.GetExecutingAssembly()); } private void OnDestroy() { ((BaseUnityPlugin)this).Config.Save(); } private void OnApplicationQuit() { PlayerDeathScreenshotPatch.UploadStoredDeathScreenshot("application quit", waitForUpload: true); } } } namespace discordScreenshots.Patches { [HarmonyPatch(typeof(Terminal), "InitTerminal")] public static class DiscordConsoleCommandPatch { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleEvent <>9__0_0; public static ConsoleEvent <>9__0_1; public static ConsoleEvent <>9__0_2; internal void <Postfix>b__0_0(ConsoleEventArgs args) { <>c__DisplayClass0_0 CS$<>8__locals6 = new <>c__DisplayClass0_0(); if (args.Length < 2) { Terminal context = args.Context; if (context != null) { context.AddString("Usage: discord <message>"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Example: discord Hello from Valheim!"); } return; } CS$<>8__locals6.message = string.Join(" ", args.Args, 1, args.Args.Length - 1); if (string.IsNullOrWhiteSpace(CS$<>8__locals6.message)) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Message cannot be empty"); } return; } CS$<>8__locals6.playerName = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { CS$<>8__locals6.playerName = Player.m_localPlayer.GetPlayerName(); } Terminal context4 = args.Context; if (context4 != null) { context4.AddString("Sending message to Discord..."); } Task.Run(async delegate { try { string formattedMessage = "\ud83d\udcac **" + CS$<>8__locals6.playerName + "**: " + CS$<>8__locals6.message; await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.GetWebhookURL(), formattedMessage, "Valheim Console"); Debug.Log((object)("Discord message sent successfully: " + formattedMessage)); } catch (Exception ex) { Debug.LogError((object)("Failed to send Discord message: " + ex.Message)); } }); } internal void <Postfix>b__0_1(ConsoleEventArgs args) { <>c__DisplayClass0_1 CS$<>8__locals3 = new <>c__DisplayClass0_1(); Terminal context = args.Context; if (context != null) { context.AddString("Testing Discord webhook connection..."); } CS$<>8__locals3.playerName = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { CS$<>8__locals3.playerName = Player.m_localPlayer.GetPlayerName(); } Task.Run(async delegate { try { await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.GetWebhookURL(), "\ud83e\uddea Discord webhook test from **" + CS$<>8__locals3.playerName + "** - Connection working!", "Valheim Test Bot"); Debug.Log((object)"Discord webhook test message sent successfully!"); } catch (Exception ex) { Debug.LogError((object)("Discord webhook test failed: " + ex.Message)); } }); } internal void <Postfix>b__0_2(ConsoleEventArgs args) { <>c__DisplayClass0_2 CS$<>8__locals17 = new <>c__DisplayClass0_2 { playerName = "Unknown Player" }; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { CS$<>8__locals17.playerName = Player.m_localPlayer.GetPlayerName(); } CS$<>8__locals17.message = null; if (args.Length > 1) { CS$<>8__locals17.message = string.Join(" ", args.Args, 1, args.Args.Length - 1); } if (string.IsNullOrEmpty(CS$<>8__locals17.message)) { CS$<>8__locals17.message = "\ud83d\udcf8 **" + CS$<>8__locals17.playerName + "** took a screenshot!"; } Terminal context = args.Context; if (context != null) { context.AddString("Capturing screenshot..."); } try { CS$<>8__locals17.webhook = new SimpleDiscordWebhook(BepinexConfiguration.GetWebhookURL(), "Valheim Screenshots"); CS$<>8__locals17.filename = SimpleDiscordWebhook.CreateScreenshotFilename(CS$<>8__locals17.playerName + "_screenshot", DateTime.Now); Texture2D val = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)val == (Object)null) { throw new Exception("Failed to capture screenshot"); } CS$<>8__locals17.uploadData = CS$<>8__locals17.webhook.ProcessScreenshotForUpload(val); Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Screenshot captured, uploading to Discord..."); } Task.Run(async delegate { try { await CS$<>8__locals17.webhook.SendFileAsync(CS$<>8__locals17.uploadData.Data, CS$<>8__locals17.filename, CS$<>8__locals17.message, CS$<>8__locals17.uploadData.ContentType); Debug.Log((object)("Screenshot uploaded to Discord for " + CS$<>8__locals17.playerName)); } catch (Exception ex2) { Debug.LogError((object)("Failed to upload screenshot: " + ex2.Message)); } }); } catch (Exception ex) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Error: " + ex.Message); } Debug.LogError((object)("Failed to capture screenshot: " + ex.Message)); } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_0 { public string playerName; public string message; internal async Task <Postfix>b__3() { try { string formattedMessage = "\ud83d\udcac **" + playerName + "**: " + message; await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.GetWebhookURL(), formattedMessage, "Valheim Console"); Debug.Log((object)("Discord message sent successfully: " + formattedMessage)); } catch (Exception ex) { Debug.LogError((object)("Failed to send Discord message: " + ex.Message)); } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_1 { public string playerName; internal async Task <Postfix>b__4() { try { await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.GetWebhookURL(), "\ud83e\uddea Discord webhook test from **" + playerName + "** - Connection working!", "Valheim Test Bot"); Debug.Log((object)"Discord webhook test message sent successfully!"); } catch (Exception ex) { Debug.LogError((object)("Discord webhook test failed: " + ex.Message)); } } } [CompilerGenerated] private sealed class <>c__DisplayClass0_2 { public string message; public string playerName; public SimpleDiscordWebhook webhook; public ScreenshotUploadData uploadData; public string filename; internal async Task <Postfix>b__5() { try { await webhook.SendFileAsync(uploadData.Data, filename, message, uploadData.ContentType); Debug.Log((object)("Screenshot uploaded to Discord for " + playerName)); } catch (Exception ex) { Debug.LogError((object)("Failed to upload screenshot: " + ex.Message)); } } } [HarmonyPostfix] private static void Postfix() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0029: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown object obj = <>c.<>9__0_0; if (obj == null) { ConsoleEvent val = delegate(ConsoleEventArgs args) { if (args.Length < 2) { Terminal context = args.Context; if (context != null) { context.AddString("Usage: discord <message>"); } Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Example: discord Hello from Valheim!"); } } else { string message = string.Join(" ", args.Args, 1, args.Args.Length - 1); if (string.IsNullOrWhiteSpace(message)) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Message cannot be empty"); } } else { string playerName = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { playerName = Player.m_localPlayer.GetPlayerName(); } Terminal context4 = args.Context; if (context4 != null) { context4.AddString("Sending message to Discord..."); } Task.Run(async delegate { try { string formattedMessage = "\ud83d\udcac **" + playerName + "**: " + message; await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.GetWebhookURL(), formattedMessage, "Valheim Console"); Debug.Log((object)("Discord message sent successfully: " + formattedMessage)); } catch (Exception ex) { Debug.LogError((object)("Failed to send Discord message: " + ex.Message)); } }); } } }; <>c.<>9__0_0 = val; obj = (object)val; } new ConsoleCommand("discord", "sends a message to Discord webhook. Usage: discord <message>", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj2 = <>c.<>9__0_1; if (obj2 == null) { ConsoleEvent val2 = delegate(ConsoleEventArgs args) { Terminal context = args.Context; if (context != null) { context.AddString("Testing Discord webhook connection..."); } string playerName = "Server"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { playerName = Player.m_localPlayer.GetPlayerName(); } Task.Run(async delegate { try { await SimpleDiscordWebhook.SendQuickMessageAsync(BepinexConfiguration.GetWebhookURL(), "\ud83e\uddea Discord webhook test from **" + playerName + "** - Connection working!", "Valheim Test Bot"); Debug.Log((object)"Discord webhook test message sent successfully!"); } catch (Exception ex) { Debug.LogError((object)("Discord webhook test failed: " + ex.Message)); } }); }; <>c.<>9__0_1 = val2; obj2 = (object)val2; } new ConsoleCommand("discordtest", "tests the Discord webhook connection", (ConsoleEvent)obj2, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); object obj3 = <>c.<>9__0_2; if (obj3 == null) { ConsoleEvent val3 = delegate(ConsoleEventArgs args) { string playerName = "Unknown Player"; if ((Object)(object)Player.m_localPlayer != (Object)null && !string.IsNullOrEmpty(Player.m_localPlayer.GetPlayerName())) { playerName = Player.m_localPlayer.GetPlayerName(); } string message = null; if (args.Length > 1) { message = string.Join(" ", args.Args, 1, args.Args.Length - 1); } if (string.IsNullOrEmpty(message)) { message = "\ud83d\udcf8 **" + playerName + "** took a screenshot!"; } Terminal context = args.Context; if (context != null) { context.AddString("Capturing screenshot..."); } try { SimpleDiscordWebhook webhook = new SimpleDiscordWebhook(BepinexConfiguration.GetWebhookURL(), "Valheim Screenshots"); string filename = SimpleDiscordWebhook.CreateScreenshotFilename(playerName + "_screenshot", DateTime.Now); Texture2D val4 = ScreenCapture.CaptureScreenshotAsTexture(); if ((Object)(object)val4 == (Object)null) { throw new Exception("Failed to capture screenshot"); } ScreenshotUploadData uploadData = webhook.ProcessScreenshotForUpload(val4); Terminal context2 = args.Context; if (context2 != null) { context2.AddString("Screenshot captured, uploading to Discord..."); } Task.Run(async delegate { try { await webhook.SendFileAsync(uploadData.Data, filename, message, uploadData.ContentType); Debug.Log((object)("Screenshot uploaded to Discord for " + playerName)); } catch (Exception ex2) { Debug.LogError((object)("Failed to upload screenshot: " + ex2.Message)); } }); } catch (Exception ex) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("Error: " + ex.Message); } Debug.LogError((object)("Failed to capture screenshot: " + ex.Message)); } }; <>c.<>9__0_2 = val3; obj3 = (object)val3; } new ConsoleCommand("discordscreenshot", "captures and sends a screenshot to Discord", (ConsoleEvent)obj3, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } [HarmonyPatch(typeof(Player), "Update")] public static class HotkeyScreenshotPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && Input.GetKeyDown(BepinexConfiguration.ScreenshotHotkey.Value)) { string text = __instance.GetPlayerName(); if (string.IsNullOrEmpty(text)) { text = "Unknown Player"; } Debug.Log((object)("HotkeyScreenshotPatch: " + text + " pressed screenshot hotkey")); string hotkeyScreenshotWebhookURL = BepinexConfiguration.GetHotkeyScreenshotWebhookURL(); bool num = !string.IsNullOrEmpty(hotkeyScreenshotWebhookURL); string webhookUrl = (num ? hotkeyScreenshotWebhookURL : BepinexConfiguration.GetWebhookURL()); string webhookUsername = (num ? BepinexConfiguration.GetHotkeyScreenshotWebhookUsername() : BepinexConfiguration.GetWebhookUsername()); string webhookAvatarUrl = (num ? BepinexConfiguration.GetHotkeyScreenshotWebhookAvatarURL() : BepinexConfiguration.GetWebhookAvatarURL()); string text2 = (num ? BepinexConfiguration.HotkeyScreenshotMessage.Value : "captured this screenshot!"); string screenshotMessage = "\ud83d\udcf8 **" + text + "** " + text2; string filename = SimpleDiscordWebhook.CreateScreenshotFilename(text + "_screenshot", DateTime.Now); SendHotkeyScreenshotAsync(webhookUrl, screenshotMessage, webhookUsername, webhookAvatarUrl, filename); } } catch (Exception ex) { Debug.LogError((object)("HotkeyScreenshotPatch: Error: " + ex.Message)); } } private static async Task SendHotkeyScreenshotAsync(string webhookUrl, string screenshotMessage, string webhookUsername, string webhookAvatarUrl, string filename) { try { await SimpleDiscordWebhook.SendQuickScreenshotAsync(webhookUrl, screenshotMessage, webhookUsername, webhookAvatarUrl, filename); } catch (Exception ex) { Debug.LogError((object)("HotkeyScreenshotPatch: Failed to upload screenshot: " + ex.Message)); } } } public static class PlayerDeathScreenshotPatch { internal static Texture2D? storedDeathScreenshot; internal static string? storedPlayerName; internal static DateTime storedDeathTime; internal static IEnumerator CaptureDeathScreenshotCoroutine(string playerName) { yield return (object)new WaitForSeconds(0.1f); storedDeathScreenshot = SimpleDiscordWebhook.CaptureScreenshot(); storedPlayerName = playerName; storedDeathTime = DateTime.Now; Debug.Log((object)("Death screenshot captured and stored for " + playerName)); } internal static void UploadStoredDeathScreenshot(string trigger, bool waitForUpload) { if ((Object)(object)storedDeathScreenshot == (Object)null) { Debug.Log((object)("Death screenshot upload skipped on " + trigger + ": no stored death screenshot found")); return; } Texture2D val = storedDeathScreenshot; string text = (string.IsNullOrEmpty(storedPlayerName) ? "Unknown Player" : storedPlayerName); DateTime timestamp = storedDeathTime; storedDeathScreenshot = null; storedPlayerName = null; try { SimpleDiscordWebhook webhook = new SimpleDiscordWebhook(BepinexConfiguration.GetWebhookURL(), BepinexConfiguration.GetWebhookUsername(), BepinexConfiguration.GetWebhookAvatarURL()); ScreenshotUploadData uploadData = webhook.ProcessScreenshotForUpload(val); string deathMessage = "**" + text + "** " + BepinexConfiguration.GetRandomDeathMessage(); string filename = SimpleDiscordWebhook.CreateScreenshotFilename(text + "_death", timestamp); Task task = Task.Run(async delegate { await webhook.SendFileAsync(uploadData.Data, filename, deathMessage, uploadData.ContentType); }); if (waitForUpload) { task.GetAwaiter().GetResult(); Debug.Log((object)("Death screenshot uploaded successfully for " + text + " on " + trigger)); } else { LogUploadResultAsync(task, text, trigger); } } catch (Exception ex) { Debug.LogError((object)("Error processing stored death screenshot on " + trigger + ": " + ex.Message)); if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); } } } private static async Task LogUploadResultAsync(Task uploadTask, string playerName, string trigger) { try { await uploadTask; Debug.Log((object)("Death screenshot uploaded successfully for " + playerName + " on " + trigger)); } catch (Exception ex) { Debug.LogError((object)("Error uploading death screenshot on " + trigger + ": " + ex.Message)); } } } [HarmonyPatch(typeof(Humanoid), "OnRagdollCreated")] public static class PlayerRagdollScreenshotPatch { [HarmonyPostfix] private static void Postfix(Humanoid __instance) { try { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (val != null && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { string text = val.GetPlayerName(); if (string.IsNullOrEmpty(text)) { text = "Unknown Player"; } Debug.Log((object)("PlayerRagdollScreenshotPatch: " + text + " died - capturing screenshot")); ((MonoBehaviour)val).StartCoroutine(PlayerDeathScreenshotPatch.CaptureDeathScreenshotCoroutine(text)); } } catch (Exception ex) { Debug.LogError((object)("PlayerRagdollScreenshotPatch: Error: " + ex.Message)); } } } [HarmonyPatch(typeof(Player), "Awake")] public static class PlayerRespawnScreenshotPatch { [HarmonyPostfix] private static void Postfix(Player __instance) { try { Debug.Log((object)"PlayerRespawnScreenshotPatch: Awake"); Debug.Log((object)"PlayerRespawnScreenshotPatch: Local player confirmed"); if ((Object)(object)PlayerDeathScreenshotPatch.storedDeathScreenshot != (Object)null) { Debug.Log((object)("PlayerRespawnScreenshotPatch: Processing stored death screenshot for " + PlayerDeathScreenshotPatch.storedPlayerName)); PlayerDeathScreenshotPatch.UploadStoredDeathScreenshot("respawn", waitForUpload: false); } else { Debug.Log((object)"PlayerRespawnScreenshotPatch: No stored death screenshot found"); } } catch (Exception ex) { Debug.LogError((object)("PlayerRespawnScreenshotPatch: Error: " + ex.Message)); } } } [HarmonyPatch(typeof(Game), "Logout")] public static class GameLogoutDeathScreenshotPatch { [HarmonyPrefix] private static void Prefix() { try { PlayerDeathScreenshotPatch.UploadStoredDeathScreenshot("logout", waitForUpload: false); } catch (Exception ex) { Debug.LogError((object)("GameLogoutDeathScreenshotPatch: Error: " + ex.Message)); } } } }