Decompiled source of ValheimRcon v1.0.1

ValheimRcon.dll

Decompiled 6 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
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.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Splatform;
using UnityEngine;
using UnityEngine.Profiling;
using ValheimRcon.Commands;
using ValheimRcon.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Valheim Rcon")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Valheim Rcon")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("43d6353e-ae3d-424e-8d9d-b274ab342a3e")]
[assembly: AssemblyFileVersion("1.0.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.1.0")]
[module: UnverifiableCode]
internal static class Log
{
	private static ManualLogSource _instance;

	internal static void CreateInstance(ManualLogSource source)
	{
		_instance = source;
	}

	public static void Info(object msg)
	{
		_instance.LogInfo((object)FormatMessage(msg));
	}

	public static void Message(object msg)
	{
		_instance.LogMessage((object)FormatMessage(msg));
	}

	public static void Debug(object msg)
	{
		_instance.LogDebug((object)FormatMessage(msg));
	}

	public static void Warning(object msg)
	{
		_instance.LogWarning((object)FormatMessage(msg));
	}

	public static void Error(object msg)
	{
		_instance.LogError((object)FormatMessage(msg));
	}

	public static void Fatal(object msg)
	{
		_instance.LogFatal((object)FormatMessage(msg));
	}

	private static string FormatMessage(object msg)
	{
		return $"[{DateTime.UtcNow:G}] {msg}";
	}
}
internal static class ThreadingUtil
{
	private class DisposableThread : IDisposable
	{
		private Thread _thread;

		internal DisposableThread(Thread thread)
		{
			_thread = thread;
		}

		public void Dispose()
		{
			_thread.Abort();
		}
	}

	private class MainThreadDispatcher : MonoBehaviour
	{
		private static MainThreadDispatcher _instance;

		private ConcurrentQueue<Action> _queue = new ConcurrentQueue<Action>();

		private ConcurrentQueue<IEnumerator> _coroutinesQueue = new ConcurrentQueue<IEnumerator>();

		public static MainThreadDispatcher GetInstante()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			if ((Object)(object)_instance == (Object)null)
			{
				GameObject val = new GameObject("MainThreadDispatcher", new Type[1] { typeof(MainThreadDispatcher) });
				Object.DontDestroyOnLoad((Object)(object)val);
				_instance = val.GetComponent<MainThreadDispatcher>();
			}
			return _instance;
		}

		public void AddAction(Action action)
		{
			_queue.Enqueue(action);
		}

		public void AddCoroutine(IEnumerator coroutine)
		{
			_coroutinesQueue.Enqueue(coroutine);
		}

		private void Update()
		{
			Action result;
			while (_queue.Count > 0 && _queue.TryDequeue(out result))
			{
				result?.Invoke();
			}
			IEnumerator result2;
			while (_coroutinesQueue.Count > 0 && _coroutinesQueue.TryDequeue(out result2))
			{
				((MonoBehaviour)this).StartCoroutine(result2);
			}
		}
	}

	internal static IDisposable RunPeriodical(Action action, int periodMilliseconds)
	{
		return new Timer(delegate
		{
			action?.Invoke();
		}, null, 0, periodMilliseconds);
	}

	internal static IDisposable RunPeriodicalInSingleThread(Action action, int periodMilliseconds)
	{
		Thread thread = new Thread((ParameterizedThreadStart)delegate
		{
			while (true)
			{
				action?.Invoke();
				Thread.Sleep(periodMilliseconds);
			}
		});
		thread.Start();
		return new DisposableThread(thread);
	}

	internal static void RunInMainThread(Action action)
	{
		MainThreadDispatcher.GetInstante().AddAction(action);
	}

	internal static void RunCoroutine(IEnumerator coroutine)
	{
		MainThreadDispatcher.GetInstante().AddCoroutine(coroutine);
	}

	internal static void RunDelayed(float delay, Action action)
	{
		MainThreadDispatcher.GetInstante().AddCoroutine(DelayedActionCoroutine(delay, action));
	}

	internal static IDisposable RunThread(Action action)
	{
		Thread thread = new Thread(action.Invoke);
		thread.Start();
		return new DisposableThread(thread);
	}

	internal static IEnumerator DelayedActionCoroutine(float delay, Action action)
	{
		yield return (object)new WaitForSeconds(delay);
		action?.Invoke();
	}
}
namespace ValheimRcon
{
	public interface IRconCommand
	{
		string Command { get; }

		Task<CommandResult> HandleCommandAsync(CommandArgs args);
	}
	internal static class Discord
	{
		private static class FormUpload
		{
			internal class FileParameter
			{
				public byte[] File;

				public string FileName;

				public string ContentType;

				public FileParameter(byte[] file, string filename, string contenttype)
				{
					File = file;
					FileName = filename;
					ContentType = contenttype;
				}
			}

			private static readonly Encoding Encoding = Encoding.UTF8;

			public static HttpWebResponse MultipartFormDataPost(string postUrl, Dictionary<string, object> postParameters)
			{
				string text = $"----------{Guid.NewGuid():N}";
				string contentType = "multipart/form-data; boundary=" + text;
				byte[] multipartFormData = GetMultipartFormData(postParameters, text);
				return PostForm(postUrl, contentType, multipartFormData);
			}

			private static HttpWebResponse PostForm(string postUrl, string contentType, byte[] formData)
			{
				if (!(WebRequest.Create(postUrl) is HttpWebRequest httpWebRequest))
				{
					throw new ArgumentException("request is not a http request");
				}
				httpWebRequest.Method = "POST";
				httpWebRequest.ContentType = contentType;
				httpWebRequest.CookieContainer = new CookieContainer();
				httpWebRequest.ContentLength = formData.Length;
				using (Stream stream = httpWebRequest.GetRequestStream())
				{
					stream.Write(formData, 0, formData.Length);
					stream.Close();
				}
				return httpWebRequest.GetResponse() as HttpWebResponse;
			}

			private static byte[] GetMultipartFormData(Dictionary<string, object> postParameters, string boundary)
			{
				MemoryStream memoryStream = new MemoryStream();
				bool flag = false;
				foreach (KeyValuePair<string, object> postParameter in postParameters)
				{
					if (flag)
					{
						memoryStream.Write(Encoding.GetBytes("\r\n"), 0, Encoding.GetByteCount("\r\n"));
					}
					flag = true;
					if (postParameter.Value is FileParameter)
					{
						FileParameter fileParameter = (FileParameter)postParameter.Value;
						string s = string.Format("--{0}\r\nContent-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: {3}\r\n\r\n", boundary, postParameter.Key, fileParameter.FileName ?? postParameter.Key, fileParameter.ContentType ?? "application/octet-stream");
						memoryStream.Write(Encoding.GetBytes(s), 0, Encoding.GetByteCount(s));
						memoryStream.Write(fileParameter.File, 0, fileParameter.File.Length);
					}
					else
					{
						string s2 = $"--{boundary}\r\nContent-Disposition: form-data; name=\"{postParameter.Key}\"\r\n\r\n{postParameter.Value}";
						memoryStream.Write(Encoding.GetBytes(s2), 0, Encoding.GetByteCount(s2));
					}
				}
				string s3 = "\r\n--" + boundary + "--\r\n";
				memoryStream.Write(Encoding.GetBytes(s3), 0, Encoding.GetByteCount(s3));
				memoryStream.Position = 0L;
				byte[] array = new byte[memoryStream.Length];
				memoryStream.Read(array, 0, array.Length);
				memoryStream.Close();
				return array;
			}
		}

		internal static string Send(string mssgBody, string userName, string webhook)
		{
			Dictionary<string, object> postParameters = new Dictionary<string, object>
			{
				{ "username", userName },
				{ "content", mssgBody }
			};
			HttpWebResponse httpWebResponse = FormUpload.MultipartFormDataPost(webhook, postParameters);
			StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream());
			string result = streamReader.ReadToEnd();
			streamReader.Close();
			httpWebResponse.Close();
			streamReader.Dispose();
			httpWebResponse.Dispose();
			return result;
		}

		internal static string SendFile(string mssgBody, string filename, string fileformat, string filepath, string userName, string webhook)
		{
			FileStream fileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
			byte[] array = new byte[fileStream.Length];
			fileStream.Read(array, 0, array.Length);
			fileStream.Close();
			Dictionary<string, object> postParameters = new Dictionary<string, object>
			{
				{ "filename", filename },
				{ "fileformat", fileformat },
				{
					"file",
					new FormUpload.FileParameter(array, filename, "application/msexcel")
				},
				{ "username", userName },
				{ "content", mssgBody }
			};
			HttpWebResponse httpWebResponse = FormUpload.MultipartFormDataPost(webhook, postParameters);
			StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream());
			string result = streamReader.ReadToEnd();
			httpWebResponse.Close();
			fileStream.Dispose();
			httpWebResponse.Dispose();
			return result;
		}
	}
	internal class DiscordService : IDisposable
	{
		private struct Message
		{
			public string text;

			public string filePath;
		}

		private const string Name = "RCON";

		private readonly IDisposable _thread;

		private readonly string _webhook;

		private readonly ConcurrentQueue<Message> _queue = new ConcurrentQueue<Message>();

		public DiscordService(string webhook)
		{
			_webhook = webhook;
			_thread = ThreadingUtil.RunPeriodicalInSingleThread(SendQueuedMessage, 333);
		}

		public void SendResult(string text, string filePath)
		{
			_queue.Enqueue(new Message
			{
				filePath = filePath,
				text = text
			});
		}

		private void SendQueuedMessage()
		{
			if (string.IsNullOrEmpty(_webhook) || !_queue.TryDequeue(out var result))
			{
				return;
			}
			try
			{
				string filePath = result.filePath;
				string text = (string.IsNullOrEmpty(filePath) ? Discord.Send(result.text, "RCON", _webhook) : Discord.SendFile(result.text, Path.GetFileName(filePath), Path.GetExtension(filePath), filePath, "RCON", _webhook));
				Log.Debug("Sent to discord " + result.text);
			}
			catch (Exception arg)
			{
				Log.Error($"Cannot send to discord {result.text}\n{arg}");
			}
		}

		public void Dispose()
		{
			_thread.Dispose();
		}
	}
	public static class PlayerUtils
	{
		public static string GetPlayerInfo(this ZNetPeer peer)
		{
			return peer.m_playerName + "(" + peer.GetSteamId() + ")";
		}

		public static void InvokeRoutedRpcToZdo(this ZNetPeer peer, string rpc, params object[] args)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = peer.GetZDO();
			ZRoutedRpc.instance.InvokeRoutedRPC(zDO.GetOwner(), zDO.m_uid, rpc, args);
		}

		public static ZDO GetZDO(this ZNetPeer peer)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			return ZDOMan.instance.GetZDO(peer.m_characterID);
		}

		public static string GetSteamId(this ZNetPeer peer)
		{
			return peer.m_rpc.GetSocket().GetHostName();
		}
	}
	[BepInProcess("valheim_server.exe")]
	[BepInPlugin("org.tristan.rcon", "Valheim Rcon", "1.0.1")]
	public class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch]
		private class Patches
		{
			[HarmonyFinalizer]
			[HarmonyPatch(typeof(ZNet), "UpdatePlayerList")]
			private static void ZNet_UpdatePlayerList(ZNet __instance)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_004a: Unknown result type (might be due to invalid IL or missing references)
				//IL_005f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0064: Unknown result type (might be due to invalid IL or missing references)
				//IL_0069: Unknown result type (might be due to invalid IL or missing references)
				//IL_006b: Unknown result type (might be due to invalid IL or missing references)
				//IL_0078: Unknown result type (might be due to invalid IL or missing references)
				//IL_0079: Unknown result type (might be due to invalid IL or missing references)
				//IL_0080: Unknown result type (might be due to invalid IL or missing references)
				PlayerInfo val = default(PlayerInfo);
				if (!ZNet.TryGetPlayerByPlatformUserID(CommandsUserInfo.UserId, ref val) && __instance.m_players.Count != 0)
				{
					string value = ServerChatName.Value;
					val = default(PlayerInfo);
					val.m_name = value;
					val.m_userInfo = new CrossNetworkUserInfo
					{
						m_displayName = value,
						m_id = CommandsUserInfo.UserId
					};
					val.m_serverAssignedDisplayName = value;
					PlayerInfo item = val;
					__instance.m_players.Add(item);
				}
			}
		}

		public const string Guid = "org.tristan.rcon";

		public const string Name = "Valheim Rcon";

		public const string Version = "1.0.1";

		public static ConfigEntry<string> DiscordUrl;

		public static ConfigEntry<string> Password;

		public static ConfigEntry<int> Port;

		public static ConfigEntry<string> ServerChatName;

		private string _logsPath;

		private DiscordService _discordService;

		private StringBuilder _builder = new StringBuilder();

		public static readonly UserInfo CommandsUserInfo = new UserInfo
		{
			Name = string.Empty,
			UserId = new PlatformUserID("Bot", 0uL, false)
		};

		private void Awake()
		{
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Expected O, but got Unknown
			Log.CreateInstance(((BaseUnityPlugin)this).Logger);
			_logsPath = Path.Combine(Paths.BepInExRootPath, "rcon_logs.log");
			Port = ((BaseUnityPlugin)this).Config.Bind<int>("1. Rcon", "Port", 2458, "Port to receive RCON commands");
			Password = ((BaseUnityPlugin)this).Config.Bind<string>("1. Rcon", "Password", System.Guid.NewGuid().ToString(), "Password for RCON packages validation");
			DiscordUrl = ((BaseUnityPlugin)this).Config.Bind<string>("2. Discord", "Webhook url", "", "Discord webhook for sending command results");
			ServerChatName = ((BaseUnityPlugin)this).Config.Bind<string>("3. Chat", "Server name", "Server", "Name of server to display messages sent with rcon command");
			CommandsUserInfo.Name = ServerChatName.Value;
			_discordService = new DiscordService(DiscordUrl.Value);
			Object.DontDestroyOnLoad((Object)new GameObject("RconProxy", new Type[1] { typeof(RconProxy) }));
			RconProxy.Instance.OnCommandCompleted += OnCommandCompleted;
			RconCommandsUtil.RegisterAllCommands(Assembly.GetExecutingAssembly());
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
		}

		private void OnDestroy()
		{
			_discordService.Dispose();
		}

		private void OnCommandCompleted(string command, IReadOnlyList<string> args, CommandResult result)
		{
			_builder.Clear();
			_builder.AppendFormat("Command {0} {1}", command, string.Join(", ", args));
			_builder.AppendLine();
			_builder.Append(result.Text);
			File.AppendAllText(_logsPath, _builder.ToString());
			if (!string.IsNullOrEmpty(DiscordUrl.Value))
			{
				_discordService.SendResult(_builder.ToString(), result.AttachedFilePath);
			}
		}
	}
	public static class RconCommandsUtil
	{
		public const int MaxMessageLength = 800;

		public static string Truncate(string message)
		{
			return (message.Length > 800) ? message.Substring(0, 800) : message;
		}

		public static void RegisterAllCommands(Assembly assembly)
		{
			if ((Object)(object)RconProxy.Instance == (Object)null)
			{
				Log.Error("RconProxy not initialized");
				return;
			}
			Log.Info("Registering rcon commands...");
			Type commandInterfaceType = typeof(IRconCommand);
			IEnumerable<Type> enumerable = from t in assembly.GetTypes()
				where t != null
				where !t.IsAbstract && t.IsClass
				where t.GetConstructor(Type.EmptyTypes) != null
				where t.GetInterfaces().Contains(commandInterfaceType)
				select t;
			foreach (Type item in enumerable)
			{
				RconProxy.Instance.RegisterCommand(item);
			}
		}
	}
	public class RconProxy : MonoBehaviour
	{
		internal delegate void CompletedCommandDelegate(string command, IReadOnlyList<string> args, CommandResult result);

		[HarmonyPatch]
		private class Patches
		{
			[HarmonyFinalizer]
			[HarmonyPatch(typeof(ZNet), "LoadWorld")]
			private static void ZNet_LoadWorld()
			{
				Instance._receiver.StartListening();
			}

			[HarmonyPrefix]
			[HarmonyPatch(typeof(Game), "Shutdown")]
			private static void Game_Shutdown()
			{
				Instance._receiver.Dispose();
			}
		}

		private RconCommandReceiver _receiver;

		private Dictionary<string, IRconCommand> _commands = new Dictionary<string, IRconCommand>();

		public static RconProxy Instance { get; private set; }

		internal event CompletedCommandDelegate OnCommandCompleted;

		private void Awake()
		{
			Instance = this;
			_receiver = new RconCommandReceiver(Plugin.Port.Value, Plugin.Password.Value, HandleCommandAsync);
		}

		private void Update()
		{
			_receiver.Update();
		}

		public void RegisterCommand<T>() where T : IRconCommand
		{
			RegisterCommand(typeof(T));
		}

		public void RegisterCommand(Type type)
		{
			if (Activator.CreateInstance(type) is IRconCommand rconCommand && !string.IsNullOrEmpty(rconCommand.Command))
			{
				RegisterCommand(rconCommand);
			}
		}

		public void RegisterCommand(IRconCommand command)
		{
			if (_commands.TryGetValue(command.Command, out var _))
			{
				Log.Error("Duplicated commands " + command.Command + "\n" + command.GetType().Name + "\n" + command.GetType().Name);
			}
			_commands[command.Command] = command;
			Log.Info("Registered command " + command.Command + " -> " + command.GetType().Name);
		}

		public void RegisterCommand(string command, Func<CommandArgs, CommandResult> commandFunc)
		{
			RegisterCommand(new ActionCommand(command, commandFunc));
		}

		private async Task<string> HandleCommandAsync(string command, IReadOnlyList<string> args)
		{
			TaskCompletionSource<CommandResult> completionSource = new TaskCompletionSource<CommandResult>();
			ThreadingUtil.RunInMainThread(delegate
			{
				RunCommand(command, args, completionSource);
			});
			CommandResult result = await completionSource.Task;
			Log.Message("Command completed " + command + " - " + result.Text);
			this.OnCommandCompleted?.Invoke(command, args, result);
			return result.Text;
		}

		private async void RunCommand(string commandName, IReadOnlyList<string> args, TaskCompletionSource<CommandResult> resultSource)
		{
			try
			{
				if (!_commands.TryGetValue(commandName, out var command))
				{
					resultSource.TrySetResult(new CommandResult
					{
						Text = "Unknown command " + commandName
					});
				}
				else
				{
					resultSource.TrySetResult(await command.HandleCommandAsync(new CommandArgs(args)));
				}
			}
			catch (Exception ex)
			{
				Exception e = ex;
				resultSource.TrySetResult(new CommandResult
				{
					Text = e.Message
				});
			}
		}
	}
}
namespace ValheimRcon.Core
{
	internal class AsynchronousSocketListener
	{
		internal delegate void MessageReceived(RconPeer peer, RconPacket package);

		private static readonly TimeSpan UnauthorizedClientLifetime = TimeSpan.FromSeconds(30.0);

		private readonly IPAddress _address;

		private readonly int _port;

		private readonly Socket _listener;

		private readonly HashSet<RconPeer> _clients = new HashSet<RconPeer>();

		private readonly HashSet<RconPeer> _waitingForDisconnect = new HashSet<RconPeer>();

		private IDisposable _acceptThread;

		internal event MessageReceived OnMessage;

		public AsynchronousSocketListener(IPAddress ipAddress, int port)
		{
			_address = ipAddress;
			_port = port;
			_listener = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
		}

		public void StartListening()
		{
			Log.Message("Start listening rcon commands");
			try
			{
				IPEndPoint localEP = new IPEndPoint(_address, _port);
				_listener.Bind(localEP);
				_listener.Listen(100);
				_acceptThread = ThreadingUtil.RunPeriodicalInSingleThread(TryAcceptClientThread, 100);
			}
			catch (Exception msg)
			{
				Log.Error(msg);
			}
		}

		public async Task SendAsync(RconPeer peer, RconPacket packet)
		{
			try
			{
				Socket socket = peer.socket;
				byte[] byteData = packet.Serialize();
				Log.Debug($"Sent {await SocketTaskExtensions.SendAsync(socket, new ArraySegment<byte>(byteData), SocketFlags.None)} bytes to client [{peer.Endpoint}]");
			}
			catch (Exception ex)
			{
				Exception e = ex;
				Log.Error(e);
			}
		}

		public void Update()
		{
			foreach (RconPeer client in _clients)
			{
				if (!IsConnected(client))
				{
					Disconnect(client);
				}
				else if (IsUnauthorizedTimeout(client))
				{
					Log.Warning("Unauthorized timeout [" + client.Endpoint + "]");
					Disconnect(client);
				}
				else
				{
					TryReceive(client);
				}
			}
			foreach (RconPeer item in _waitingForDisconnect)
			{
				_clients.Remove(item);
				DisconnectPeer(item);
			}
			_waitingForDisconnect.Clear();
		}

		public void Close()
		{
			_listener.Close();
			_acceptThread?.Dispose();
			foreach (RconPeer client in _clients)
			{
				client.Dispose();
			}
			_waitingForDisconnect.Clear();
			_clients.Clear();
		}

		public void Disconnect(RconPeer peer)
		{
			_waitingForDisconnect.Add(peer);
		}

		private void TryAcceptClientThread()
		{
			try
			{
				if (_listener.Poll(0, SelectMode.SelectRead))
				{
					Socket socket = _listener.Accept();
					OnClientConnected(socket);
				}
			}
			catch (Exception msg)
			{
				Log.Error(msg);
			}
		}

		private void TryReceive(RconPeer peer)
		{
			Socket socket = peer.socket;
			if (socket.Poll(0, SelectMode.SelectRead) && socket.Available > 0)
			{
				int num = socket.Receive(peer.Buffer);
				if (num != 0)
				{
					OnPackageReceived(peer, num);
				}
			}
		}

		private void OnClientConnected(Socket socket)
		{
			RconPeer state = new RconPeer(socket);
			Log.Debug("Client connected [" + state.Endpoint + "]");
			ThreadingUtil.RunInMainThread(delegate
			{
				_clients.Add(state);
			});
		}

		private void OnPackageReceived(RconPeer peer, int readCount)
		{
			Socket socket = peer.socket;
			Log.Debug($"Got package from client, {readCount} bytes [{peer.Endpoint}]");
			RconPacket rconPacket = new RconPacket(peer.Buffer);
			Log.Debug($"Received package {rconPacket}");
			this.OnMessage?.Invoke(peer, rconPacket);
			Array.Clear(peer.Buffer, 0, peer.Buffer.Length);
		}

		private void DisconnectPeer(RconPeer peer)
		{
			Socket socket = peer.socket;
			Log.Debug("Client disconnected [" + peer.Endpoint + "]");
			peer.Dispose();
		}

		private static bool IsConnected(RconPeer peer)
		{
			Socket socket = peer.socket;
			return socket.Connected && (!socket.Poll(0, SelectMode.SelectRead) || socket.Available != 0);
		}

		private static bool IsUnauthorizedTimeout(RconPeer peer)
		{
			return !peer.Authentificated && DateTime.Now - peer.created > UnauthorizedClientLifetime;
		}
	}
	internal enum PacketType
	{
		Error = 0,
		Command = 2,
		Login = 3
	}
	internal delegate Task<string> RconCommandHandler(string command, IReadOnlyList<string> data);
	internal class RconCommandReceiver : IDisposable
	{
		private static readonly Regex MatchRegex = new Regex("(?<=[ ][\\\"]|^[\\\"])[^\\\"]+(?=[\\\"][ ]|[\\\"]$)|(?<=[ ]|^)[^\\\" ]+(?=[ ]|$)");

		private readonly AsynchronousSocketListener _socketListener;

		private readonly string _password;

		private RconCommandHandler _commandHandler;

		public RconCommandReceiver(int port, string password, RconCommandHandler commandHandler)
		{
			_password = password;
			_socketListener = new AsynchronousSocketListener(IPAddress.Any, port);
			_socketListener.OnMessage += SocketListener_OnMessage;
			_commandHandler = commandHandler;
		}

		public void StartListening()
		{
			_socketListener.StartListening();
		}

		public void Update()
		{
			_socketListener.Update();
		}

		public void Dispose()
		{
			_socketListener.Close();
		}

		private async void SocketListener_OnMessage(RconPeer peer, RconPacket packet)
		{
			_ = peer.socket;
			switch (packet.type)
			{
			case PacketType.Login:
			{
				if (peer.Authentificated)
				{
					Log.Error("Already authorized [" + peer.Endpoint + "]");
					await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, PacketType.Command, "Already authorized"));
					_socketListener.Disconnect(peer);
					break;
				}
				bool success = string.Equals(packet.payload.Trim(), _password);
				RconPacket result2;
				if (success)
				{
					peer.SetAuthentificated(authentificated: true);
					result2 = new RconPacket(packet.requestId, PacketType.Command, "Logic success");
				}
				else
				{
					result2 = new RconPacket(-1, PacketType.Command, "Login failed");
				}
				Log.Debug($"Login result {result2}");
				await _socketListener.SendAsync(peer, result2);
				if (!success)
				{
					_socketListener.Disconnect(peer);
				}
				break;
			}
			case PacketType.Command:
			{
				if (!peer.Authentificated)
				{
					Log.Warning("Not authorized [" + peer.Endpoint + "]");
					await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, packet.type, "Unauthorized"));
					_socketListener.Disconnect(peer);
					break;
				}
				string payload = packet.payload.TrimStart(new char[1] { '/' });
				List<string> data = (from Match m in MatchRegex.Matches(payload)
					select m.Value).ToList();
				string command = data[0];
				data.RemoveAt(0);
				RconPacket result = new RconPacket(payload: await _commandHandler(command, data), requestId: packet.requestId, type: packet.type);
				Log.Debug($"Command result {command} - {result}");
				await _socketListener.SendAsync(peer, result);
				break;
			}
			default:
				Log.Error($"Unknown packet type: {packet} [{peer.Endpoint}]");
				await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, PacketType.Error, "Cannot handle command"));
				_socketListener.Disconnect(peer);
				break;
			}
		}
	}
	internal readonly struct RconPacket
	{
		public readonly int requestId;

		public readonly PacketType type;

		public readonly string payload;

		public RconPacket(byte[] bytes)
		{
			using MemoryStream input = new MemoryStream(bytes);
			using BinaryReader binaryReader = new BinaryReader(input);
			int num = binaryReader.ReadInt32();
			requestId = binaryReader.ReadInt32();
			type = (PacketType)binaryReader.ReadInt32();
			byte[] bytes2 = binaryReader.ReadBytes(num - 8 - 2);
			payload = Encoding.UTF8.GetString(bytes2);
		}

		public RconPacket(int requestId, PacketType type, string payload)
		{
			this.requestId = requestId;
			this.type = type;
			this.payload = payload;
		}

		public byte[] Serialize()
		{
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(requestId);
			binaryWriter.Write((int)type);
			byte[] bytes = Encoding.UTF8.GetBytes(payload);
			binaryWriter.Write(bytes);
			binaryWriter.Write((byte)0);
			binaryWriter.Write((byte)0);
			byte[] array = memoryStream.ToArray();
			memoryStream.Position = 0L;
			binaryWriter.Write(array.Length);
			binaryWriter.Write(array);
			return memoryStream.ToArray();
		}

		public override string ToString()
		{
			return $"[{requestId} t:{type} {payload}]";
		}
	}
	internal class RconPeer : IDisposable
	{
		internal const int BufferSize = 4096;

		internal readonly byte[] Buffer = new byte[4096];

		public readonly Socket socket;

		public readonly DateTime created;

		public bool Authentificated { get; private set; }

		public string Endpoint => socket.RemoteEndPoint?.ToString() ?? string.Empty;

		public RconPeer(Socket workSocket)
		{
			socket = workSocket;
			created = DateTime.Now;
		}

		public void SetAuthentificated(bool authentificated)
		{
			Authentificated = authentificated;
		}

		public void Dispose()
		{
			socket.Close();
			socket.Dispose();
		}
	}
}
namespace ValheimRcon.Commands
{
	internal class ActionCommand : IRconCommand
	{
		private readonly Func<CommandArgs, CommandResult> _execute;

		public string Command { get; }

		public ActionCommand(string command, Func<CommandArgs, CommandResult> execute)
		{
			Command = command;
			_execute = execute;
		}

		public Task<CommandResult> HandleCommandAsync(CommandArgs args)
		{
			return Task.FromResult(_execute(args));
		}
	}
	internal class AddAdmin : RconCommand
	{
		public override string Command => "addAdmin";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.m_adminList.Add(@string);
			return @string + " is admin now";
		}
	}
	internal class AddPermitted : RconCommand
	{
		public override string Command => "addPermitted";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.m_permittedList.Add(@string);
			return @string + " added to permitted";
		}
	}
	internal class Ban : RconCommand
	{
		public override string Command => "ban";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.Ban(@string);
			return "Banned " + @string;
		}
	}
	internal class BanSteamId : RconCommand
	{
		public override string Command => "banSteamId";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.m_bannedList.Add(@string);
			return @string + " banned";
		}
	}
	public class CommandArgs
	{
		public IReadOnlyList<string> Arguments { get; }

		public CommandArgs(IReadOnlyList<string> args)
		{
			Arguments = args;
		}

		public int GetInt(int index)
		{
			ValidateIndex(index);
			if (!int.TryParse(Arguments[index], out var result))
			{
				throw new ArgumentException($"Argument at {index} is invalid");
			}
			return result;
		}

		public string GetString(int index)
		{
			ValidateIndex(index);
			return Arguments[index];
		}

		private void ValidateIndex(int index)
		{
			if (index >= 0 && index < Arguments.Count)
			{
				return;
			}
			throw new ArgumentException($"Cannot get argument at {index}");
		}

		public override string ToString()
		{
			return string.Join(" ", Arguments);
		}
	}
	public struct CommandResult
	{
		public string Text;

		public string AttachedFilePath;

		public static CommandResult WithText(string text)
		{
			CommandResult result = default(CommandResult);
			result.Text = text;
			return result;
		}
	}
	internal class Damage : PlayerRconCommand
	{
		public override string Command => "damage";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: 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_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			HitData val = new HitData
			{
				m_blockable = false,
				m_dodgeable = false,
				m_ignorePVP = true,
				m_hitType = (HitType)0,
				m_damage = new DamageTypes
				{
					m_damage = args.GetInt(1)
				}
			};
			peer.InvokeRoutedRpcToZdo("RPC_Damage", val);
			return $"{peer.GetPlayerInfo()} damaged to {val.m_damage.m_damage}hp";
		}
	}
	internal class GetServerStats : RconCommand
	{
		private StringBuilder builder = new StringBuilder();

		public override string Command => "serverStats";

		protected override string OnHandle(CommandArgs args)
		{
			builder.Clear();
			string text = ZNet.World?.m_name ?? "INVALID WORLD";
			int num = ZNet.instance?.m_peers.Count ?? (-1);
			float num2 = 1f / Time.deltaTime;
			int valueOrDefault = (ZDOMan.instance?.m_objectsByID?.Count).GetValueOrDefault(-1);
			EnvMan instance = EnvMan.instance;
			int num3;
			if (instance == null)
			{
				num3 = -1;
			}
			else
			{
				ZNet instance2 = ZNet.instance;
				num3 = instance.GetDay((instance2 != null) ? instance2.GetTimeSeconds() : 0.0);
			}
			int num4 = num3;
			int num5 = ZDOMan.instance?.m_deadZDOs.Count ?? 0;
			int num6 = ToMegabytes(Profiler.GetMonoUsedSizeLong());
			int num7 = ToMegabytes(Profiler.GetMonoHeapSizeLong());
			builder.AppendLine($"Stats - Online {num} FPS {num2:0.0}");
			builder.AppendLine($"Memory - Mono {num6}MB, Heap {num7}MB");
			builder.Append($"World - Day {num4}, Objects {valueOrDefault}, Dead objects {num5}");
			return builder.ToString();
		}

		private int ToMegabytes(long bytes)
		{
			return Mathf.FloorToInt((float)bytes / 1048576f);
		}
	}
	internal class GiveItem : PlayerRconCommand
	{
		public override string Command => "give";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			//IL_0070: 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_00b8: Unknown result type (might be due to invalid IL or missing references)
			string @string = args.GetString(1);
			int @int = args.GetInt(2);
			int int2 = args.GetInt(3);
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(@string);
			if ((Object)(object)itemPrefab == (Object)null)
			{
				return "Cannot find prefab " + @string;
			}
			ZNetView.StartGhostInit();
			ItemData val = itemPrefab.GetComponent<ItemDrop>().m_itemData.Clone();
			val.m_dropPrefab = itemPrefab;
			val.m_quality = @int;
			ItemDrop val2 = ItemDrop.DropItem(val, int2, peer.GetRefPos(), Quaternion.identity);
			ZNetView.FinishGhostInit();
			Object.Destroy((Object)(object)((Component)val2).gameObject);
			return $"Item {@string} x{int2} spawned on player {peer.GetPlayerInfo()} {peer.GetRefPos()}";
		}
	}
	internal class Heal : PlayerRconCommand
	{
		public override string Command => "heal";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			int @int = args.GetInt(1);
			peer.InvokeRoutedRpcToZdo("RPC_Heal", (float)@int, true);
			return $"{peer.GetPlayerInfo()} healed to {@int}hp";
		}
	}
	internal class Kick : RconCommand
	{
		public override string Command => "kick";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.Kick(@string);
			return "Kicked " + @string;
		}
	}
	public abstract class PlayerRconCommand : RconCommand
	{
		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNetPeer val = ZNet.instance.GetPeerByHostName(@string);
			if (val == null)
			{
				val = ZNet.instance.GetPeerByPlayerName(@string);
			}
			if (val == null)
			{
				return "Cannot find user " + @string;
			}
			ZDO zDO = val.GetZDO();
			if (zDO == null)
			{
				return "Cannot handle command for player " + val.GetPlayerInfo() + ". ZDO not found";
			}
			return OnHandle(val, zDO, args);
		}

		protected abstract string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args);
	}
	internal class PrintAdminList : RconCommand
	{
		private StringBuilder stringBuilder = new StringBuilder();

		public override string Command => "adminlist";

		protected override string OnHandle(CommandArgs args)
		{
			stringBuilder.Clear();
			foreach (string item in ZNet.instance.m_adminList.GetList())
			{
				stringBuilder.AppendLine(item);
			}
			return stringBuilder.ToString();
		}
	}
	internal class PrintBanlist : RconCommand
	{
		private StringBuilder stringBuilder = new StringBuilder();

		public override string Command => "banlist";

		protected override string OnHandle(CommandArgs args)
		{
			stringBuilder.Clear();
			foreach (string item in ZNet.instance.m_bannedList.GetList())
			{
				stringBuilder.AppendLine(item);
			}
			return stringBuilder.ToString();
		}
	}
	internal class PrintPermitlist : RconCommand
	{
		private StringBuilder stringBuilder = new StringBuilder();

		public override string Command => "permitted";

		protected override string OnHandle(CommandArgs args)
		{
			stringBuilder.Clear();
			foreach (string item in ZNet.instance.m_permittedList.GetList())
			{
				stringBuilder.AppendLine(item);
			}
			return stringBuilder.ToString();
		}
	}
	public abstract class RconCommand : IRconCommand
	{
		private static readonly string ResultFilePath = Path.Combine(Paths.CachePath, "result.txt");

		public abstract string Command { get; }

		public RconCommand()
		{
			FileHelpers.EnsureDirectoryExists(ResultFilePath);
		}

		public Task<CommandResult> HandleCommandAsync(CommandArgs args)
		{
			string text = OnHandle(args).Trim();
			string attachedFilePath = string.Empty;
			if (text.Length > 800)
			{
				File.WriteAllText(ResultFilePath, text);
				attachedFilePath = ResultFilePath;
				text = RconCommandsUtil.Truncate(text);
			}
			CommandResult result = default(CommandResult);
			result.Text = OnHandle(args);
			result.AttachedFilePath = attachedFilePath;
			return Task.FromResult(result);
		}

		protected abstract string OnHandle(CommandArgs args);
	}
	internal class RemoveAdmin : RconCommand
	{
		public override string Command => "removeAdmin";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.m_adminList.Remove(@string);
			return @string + " removed from admins";
		}
	}
	internal class RemovePermitted : RconCommand
	{
		public override string Command => "removePermitted";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.m_permittedList.Remove(@string);
			return @string + " removed from permitted";
		}
	}
	internal class SayChat : RconCommand
	{
		public override string Command => "say";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			string text = args.ToString();
			Vector3 val = default(Vector3);
			if (!ZoneSystem.instance.GetLocationIcon(Game.instance.m_StartLocation, ref val))
			{
				((Vector3)(ref val))..ctor(0f, 30f, 0f);
			}
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4]
			{
				val,
				2,
				Plugin.CommandsUserInfo,
				text
			});
			return "Sent to chat - " + text;
		}
	}
	internal class SayPing : RconCommand
	{
		public override string Command => "ping";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			val.x = args.GetInt(0);
			val.y = args.GetInt(1);
			val.z = args.GetInt(2);
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ChatMessage", new object[4]
			{
				val,
				3,
				Plugin.CommandsUserInfo,
				""
			});
			return $"Ping sent to {val}";
		}
	}
	internal class ServerLogs : IRconCommand
	{
		private readonly StringBuilder _builder = new StringBuilder();

		public string Command => "logs";

		public Task<CommandResult> HandleCommandAsync(CommandArgs args)
		{
			string text = Path.Combine(Paths.BepInExRootPath, "LogOutput.log");
			if (!File.Exists(text))
			{
				return Task.FromResult(CommandResult.WithText("No logs"));
			}
			string text2 = Path.Combine(Paths.CachePath, "LogOutput.log");
			File.Copy(text, text2, overwrite: true);
			string[] array = File.ReadAllLines(text2);
			_builder.Clear();
			for (int num = array.Length - 1; num >= 0; num--)
			{
				_builder.Insert(0, array[num]);
				_builder.Insert(0, '\n');
				if (_builder.Length > 800)
				{
					break;
				}
			}
			CommandResult result = default(CommandResult);
			result.Text = _builder.ToString().Trim(new char[1] { '\n' });
			result.AttachedFilePath = text2;
			return Task.FromResult(result);
		}
	}
	internal class ShowMessage : RconCommand
	{
		public override string Command => "showMessage";

		protected override string OnHandle(CommandArgs args)
		{
			string text = args.ToString();
			ZRoutedRpc.instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "ShowMessage", new object[2] { 2, text });
			return "Message sent - " + text;
		}
	}
	internal class ShowPlayers : RconCommand
	{
		private StringBuilder _builder = new StringBuilder();

		public override string Command => "players";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			_builder.Clear();
			int count = ZNet.instance.GetPeers().Count;
			_builder.AppendFormat("Online {0}\n", count);
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				_builder.AppendFormat("{0}:{1} - {2}({3})", peer.GetSteamId(), peer.m_playerName, peer.GetRefPos(), ZoneSystem.GetZone(peer.GetRefPos()));
				_builder.AppendLine();
			}
			return _builder.ToString();
		}
	}
	internal class SpawnObject : RconCommand
	{
		public override string Command => "spawn";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			string @string = args.GetString(0);
			int @int = args.GetInt(1);
			int int2 = args.GetInt(2);
			Vector3 val = default(Vector3);
			val.x = args.GetInt(3);
			val.y = args.GetInt(4);
			val.z = args.GetInt(5);
			GameObject prefab = ZNetScene.instance.GetPrefab(@string);
			if ((Object)(object)prefab == (Object)null)
			{
				return "Prefab " + @string + " not found";
			}
			if (int2 <= 0)
			{
				return "Nothing to spawn";
			}
			Character val3 = default(Character);
			ItemDrop val4 = default(ItemDrop);
			for (int i = 0; i < int2; i++)
			{
				ZNetView.StartGhostInit();
				GameObject val2 = Object.Instantiate<GameObject>(prefab, val, Quaternion.identity);
				if (val2.TryGetComponent<Character>(ref val3))
				{
					val3.SetLevel(@int);
				}
				if (val2.TryGetComponent<ItemDrop>(ref val4))
				{
					val4.SetQuality(@int);
				}
				ZNetView.FinishGhostInit();
				Object.Destroy((Object)(object)val2);
			}
			return $"{@string} x{int2} level:{@int} instantiated at {val}";
		}
	}
	internal class TeleportPlayer : PlayerRconCommand
	{
		public override string Command => "teleport";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = default(Vector3);
			val.x = args.GetInt(1);
			val.y = args.GetInt(2);
			val.z = args.GetInt(3);
			peer.InvokeRoutedRpcToZdo("RPC_TeleportTo", val, Quaternion.identity, true);
			return $"Player {peer.GetPlayerInfo()} teleported to {val}";
		}
	}
	internal class Unban : RconCommand
	{
		public override string Command => "unban";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.Unban(@string);
			return @string + " unbanned";
		}
	}
	internal class WorldSave : RconCommand
	{
		public override string Command => "save";

		protected override string OnHandle(CommandArgs args)
		{
			ZNet.instance.Save(false, false, false);
			return "World save started";
		}
	}
}