using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Logging;
using DiscordConnector.Common;
using DiscordConnector.RPC;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("DiscordConnectorClient")]
[assembly: AssemblyDescription("Enhances Valheim by sending messages to a Discord Webhook")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("nwesterhausen")]
[assembly: AssemblyProduct("DiscordConnectorClient")]
[assembly: AssemblyCopyright("© 2025 nwesterhausen")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("E2942C94-F0B8-414E-A920-328E601BFF87")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8.1", FrameworkDisplayName = ".NET Framework 4.8.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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]
[Microsoft.CodeAnalysis.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]
[Microsoft.CodeAnalysis.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 DiscordConnector
{
[BepInPlugin("nwesterhausen.DiscordConnectorClient", "DiscordConnectorClient", "1.0.1")]
public class DiscordConnectorClientPlugin : BaseUnityPlugin
{
internal const string ModName = "DiscordConnectorClient";
internal const string ModVersion = "1.0.1";
internal const string Author = "nwesterhausen";
private const string ModGuid = "nwesterhausen.DiscordConnectorClient";
private const string LegacyConfigPath = "games.nwest.valheim.discordconnector";
internal const string LegacyModName = "discordconnector";
internal static VdcLogger StaticLogger;
private Harmony? _harmony;
public DiscordConnectorClientPlugin()
{
StaticLogger = new VdcLogger(((BaseUnityPlugin)this).Logger, Paths.ConfigPath);
}
private void Awake()
{
StaticLogger.LogDebug("Plugin DiscordConnectorClient is loaded!");
_harmony = Harmony.CreateAndPatchAll(typeof(DiscordConnectorClientPlugin).Assembly, "nwesterhausen.DiscordConnectorClient");
}
private void OnDestroy()
{
Harmony? harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
}
namespace DiscordConnector.Patches
{
internal class ChatPatches
{
[HarmonyPatch(typeof(Chat), "OnNewChatMessage")]
internal class OnNewChatMessage
{
private static void Prefix(ref GameObject go, ref long senderID, ref Vector3 pos, ref Type type, ref UserInfo sender, ref string text)
{
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
if (senderID != ZNet.GetUID())
{
DiscordConnectorClientPlugin.StaticLogger.LogDebug($"Ignoring message from other {senderID} != {ZNet.GetUID()}");
return;
}
DiscordConnectorClientPlugin.StaticLogger.LogDebug($"User details: name:{sender.Name} DisplayName():{sender.GetDisplayName()} senderID:{senderID} type:{type} text:{text}");
try
{
ChatMessageDetail chatMessageDetail = new ChatMessageDetail(pos, type, text);
ZPackage val = chatMessageDetail.ToZPackage();
long serverPeerID = ZRoutedRpc.instance.GetServerPeerID();
try
{
ZRoutedRpc.instance.InvokeRoutedRPC(serverPeerID, "DiscordConnector_OnNewChatMessage", new object[1] { val });
DiscordConnectorClientPlugin.StaticLogger.LogDebug($"Sent encoded chat message to server {chatMessageDetail} in {val.Size()}B");
}
catch (Exception ex)
{
DiscordConnectorClientPlugin.StaticLogger.LogError("Failed to send chat message to server");
throw ex;
}
}
catch (Exception ex2)
{
DiscordConnectorClientPlugin.StaticLogger.LogError("Failed to encode chat message");
DiscordConnectorClientPlugin.StaticLogger.LogDebug(ex2.ToString());
}
}
}
internal const string ArrivalShout = "I have arrived!";
}
internal class GamePatches
{
[HarmonyPatch(typeof(Game), "Start")]
internal static class GameStartPatch
{
private static void Prefix()
{
ZRoutedRpc.instance.Register<ZPackage>("DiscordConnector_OnNewChatMessage", (Action<long, ZPackage>)Client.RPC_OnNewChatMessage);
DiscordConnectorClientPlugin.StaticLogger.LogInfo("Registered RPC: DiscordConnector_OnNewChatMessage");
}
}
}
}
namespace DiscordConnector.RPC
{
internal class ChatMessageDetail
{
private static readonly char Separator = '|';
private static readonly int ExpectedParts = 5;
public Vector3 Pos { get; }
public Type TalkerType { get; }
public string Text { get; }
public static string EmptyTextMessage { get; } = "#empty#";
public ChatMessageDetail(Vector3 pos, Type type, string text)
{
//IL_0001: 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_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
Pos = pos;
TalkerType = type;
Text = text;
base..ctor();
}
private string EncodeSelf()
{
//IL_000e: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006b: Expected I4, but got Unknown
string text = $"{Pos.x}{Separator}{Pos.y}{Separator}{Pos.z}";
string text2 = ((int)TalkerType).ToString();
return $"{text}{Separator}{text2}{Separator}{Text}";
}
private static ChatMessageDetail DecodeSelf(string encoded)
{
//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
string[] array = encoded.Split(new char[1] { Separator });
if (array.Length != ExpectedParts)
{
throw new ArgumentException($"Invalid number of parts in encoded string ({array.Length} instead of {ExpectedParts})");
}
if (!float.TryParse(array[0], out var result))
{
throw new ArgumentException("Failed to parse Pos.X component: " + array[0]);
}
if (!float.TryParse(array[1], out var result2))
{
throw new ArgumentException("Failed to parse Pos.Y component: " + array[1]);
}
if (!float.TryParse(array[2], out var result3))
{
throw new ArgumentException("Failed to parse Pos.Z component: " + array[2]);
}
Vector3 pos = new Vector3(result, result2, result3);
if (!int.TryParse(array[3], out var result4))
{
throw new ArgumentException("Failed to parse Talker.Type component: " + array[3]);
}
if (!Enum.IsDefined(typeof(Type), result4))
{
throw new ArgumentException($"Invalid Talker.Type value: {result4}");
}
Type type = (Type)result4;
return new ChatMessageDetail(pos, type, array[4]);
}
public ZPackage ToZPackage()
{
//IL_0007: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Expected O, but got Unknown
string text = EncodeSelf();
try
{
ZPackage val = new ZPackage();
val.Write(text);
return val;
}
catch (Exception innerException)
{
throw new Exception("Failed to encode ZPackage with " + text, innerException);
}
}
public static ChatMessageDetail FromZPackage(ZPackage? pkg)
{
if (pkg == null || pkg.Size() == 0)
{
throw new ArgumentException("ZPackage is null or empty");
}
try
{
return DecodeSelf(pkg.ReadString());
}
catch (Exception innerException)
{
throw new Exception("Failed to decode ZPackage", innerException);
}
}
public override string ToString()
{
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
return $"ChatMessageDetail: Pos={Pos}, Type={TalkerType}, Text={Text}";
}
}
internal static class Client
{
public static void RPC_OnNewChatMessage(long sender, ZPackage pkg)
{
}
}
internal static class Common
{
public const string RPC_OnNewChatMessage = "DiscordConnector_OnNewChatMessage";
}
}
namespace DiscordConnector.Common
{
internal sealed class VdcLogger
{
private const string LogName = "vdc.log";
private const int MaxLogFiles = 5;
private static ManualLogSource s_logger;
private static string s_logFilePath;
private bool _logDebugMessages;
public VdcLogger(ManualLogSource logger, string basePath)
{
s_logger = logger;
s_logFilePath = Path.Combine(basePath, "vdc.log");
InitializeLogFile();
s_logger.LogInfo((object)"Logger initialized.");
}
public void SetLogLevel(bool logDebugMessages)
{
_logDebugMessages = logDebugMessages;
}
private static void InitializeLogFile()
{
if (!File.Exists(s_logFilePath))
{
return;
}
for (int num = 5; num > 1; num--)
{
string text = $"{s_logFilePath}.{num}";
string text2 = $"{s_logFilePath}.{num - 1}";
if (File.Exists(text))
{
try
{
File.Delete(text);
}
catch (Exception ex)
{
s_logger.LogError((object)("Error deleting old log file: " + ex.Message));
}
}
if (File.Exists(text2))
{
try
{
File.Move(text2, text);
}
catch (Exception ex2)
{
s_logger.LogError((object)("Error moving log file: " + ex2.Message));
}
}
}
try
{
File.Move(s_logFilePath, s_logFilePath + ".1");
}
catch (Exception ex3)
{
s_logger.LogError((object)("Error moving log file: " + ex3.Message));
}
s_logger.LogInfo((object)"Existing log files versioned.");
}
private async Task LogToFileAsync(string severity, string message)
{
try
{
using StreamWriter writer = new StreamWriter(s_logFilePath, append: true);
await writer.WriteLineAsync($"{DateTime.Now} [{severity}]: {message}");
}
catch (Exception ex)
{
s_logger.LogError((object)("Error writing to log file: " + ex.Message));
}
}
public async Task LogDebugAsync(string message)
{
await LogToFileAsync("DEBUG", message);
if (_logDebugMessages)
{
s_logger.LogInfo((object)message);
}
}
public async Task LogInfoAsync(string message)
{
await LogToFileAsync("INFO", message);
s_logger.LogInfo((object)message);
}
public async Task LogWarningAsync(string message)
{
await LogToFileAsync("WARNING", message);
s_logger.LogWarning((object)message);
}
public async Task LogErrorAsync(string message)
{
await LogToFileAsync("ERROR", message);
s_logger.LogError((object)message);
}
public async Task LogFatalAsync(string message)
{
await LogToFileAsync("FATAL", message);
s_logger.LogFatal((object)message);
}
public void LogDebug(string message)
{
LogDebugAsync(message).GetAwaiter().GetResult();
}
public void LogInfo(string message)
{
LogInfoAsync(message).GetAwaiter().GetResult();
}
public void LogWarning(string message)
{
LogWarningAsync(message).GetAwaiter().GetResult();
}
public void LogError(string message)
{
LogErrorAsync(message).GetAwaiter().GetResult();
}
public void LogFatal(string message)
{
LogFatalAsync(message).GetAwaiter().GetResult();
}
}
}