Decompiled source of ValheimRcon v1.2.1

ValheimRcon.dll

Decompiled 5 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.Pool;
using UnityEngine.Profiling;
using ValheimRcon.Commands;
using ValheimRcon.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[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.2.1")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.1.0")]
[module: UnverifiableCode]
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_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			if ((Object)(object)_instance == (Object)null)
			{
				GameObject val = new GameObject("MainThreadDispatcher", new Type[1] { typeof(MainThreadDispatcher) });
				Object.DontDestroyOnLoad((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);
			}
		}
	}

	[CompilerGenerated]
	private sealed class <DelayedActionCoroutine>d__6 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		public float delay;

		public Action action;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <DelayedActionCoroutine>d__6(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<>2__current = (object)new WaitForSeconds(delay);
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				action?.Invoke();
				return false;
			}
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	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);
	}

	[IteratorStateMachine(typeof(<DelayedActionCoroutine>d__6))]
	internal static IEnumerator DelayedActionCoroutine(float delay, Action action)
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <DelayedActionCoroutine>d__6(0)
		{
			delay = delay,
			action = action
		};
	}
}
namespace ValheimRcon
{
	public interface IRconCommand
	{
		string Command { get; }

		string Description { 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);
			string result = new StreamReader(httpWebResponse.GetResponseStream()).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;
				if (string.IsNullOrEmpty(filePath))
				{
					Discord.Send(result.text, "RCON", _webhook);
				}
				else
				{
					string fileName = Path.GetFileName(filePath);
					string extension = Path.GetExtension(filePath);
					Discord.SendFile(result.text, fileName, extension, 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 Log
	{
		private static ManualLogSource _instance;

		public 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}";
		}
	}
	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_0013: 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.2.1")]
	public class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch]
		private class Patches
		{
			[HarmonyFinalizer]
			[HarmonyPatch(typeof(ZNet), "UpdatePlayerList")]
			private static void ZNet_UpdatePlayerList(ZNet __instance)
			{
				//IL_0005: 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_0040: Unknown result type (might be due to invalid IL or missing references)
				//IL_0055: Unknown result type (might be due to invalid IL or missing references)
				//IL_005a: 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_0060: Unknown result type (might be due to invalid IL or missing references)
				//IL_006d: Unknown result type (might be due to invalid IL or missing references)
				//IL_006e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0075: 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.2.1";

		private const int MaxDiscordMessageLength = 1900;

		private const int TruncatedMessageLength = 200;

		public static ConfigEntry<string> DiscordUrl;

		public static ConfigEntry<string> Password;

		public static ConfigEntry<int> Port;

		public static ConfigEntry<string> ServerChatName;

		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_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Expected O, but got Unknown
			Log.CreateInstance(((BaseUnityPlugin)this).Logger);
			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 += SendResultToDiscord;
			RconCommandsUtil.RegisterAllCommands(Assembly.GetExecutingAssembly());
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
		}

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

		private void SendResultToDiscord(RconPeer peer, string command, IReadOnlyList<string> args, CommandResult result)
		{
			if (!string.IsNullOrEmpty(DiscordUrl.Value))
			{
				string text = command + " " + string.Join(" ", args);
				_builder.Clear();
				_builder.AppendLine("> " + peer.Endpoint + " -> " + text);
				if (_builder.Length > 1900)
				{
					string value = RconCommandsUtil.TruncateMessage(_builder.ToString(), 200);
					_builder.Clear();
					_builder.Append(value);
					_builder.AppendLine("...");
				}
				int num = 1900 - _builder.Length;
				if (result.Text.Length > num)
				{
					string value2 = RconCommandsUtil.TruncateMessage(result.Text, 200);
					_builder.AppendLine(value2);
					_builder.Append("*--- message truncated ---*");
					_discordService.SendResult(_builder.ToString(), result.AttachedFilePath);
					string text2 = Path.Combine(Paths.CachePath, $"{DateTime.UtcNow.Ticks}.txt");
					FileHelpers.EnsureDirectoryExists(text2);
					File.WriteAllText(text2, result.Text);
					_discordService.SendResult("**Full message**", text2);
				}
				else
				{
					_builder.Append(result.Text);
					_discordService.SendResult(_builder.ToString(), result.AttachedFilePath);
				}
			}
		}
	}
	public static class RconCommandsUtil
	{
		public static string TruncateMessage(string message, int maxLength)
		{
			if (message.Length <= maxLength)
			{
				return message;
			}
			return message.Substring(0, maxLength) + "...";
		}

		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);
			foreach (Type item in from t in assembly.GetTypes()
				where t != null
				where !t.IsAbstract && t.IsClass
				where t.GetConstructor(Type.EmptyTypes) != null
				where t.GetInterfaces().Contains(commandInterfaceType)
				where t.GetCustomAttribute<ExcludeAttribute>() == null
				select t)
			{
				RconProxy.Instance.RegisterCommand(item);
			}
		}
	}
	public class RconProxy : MonoBehaviour
	{
		internal delegate void CompletedCommandDelegate(RconPeer peer, 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 class ListCommand : RconCommand
		{
			public override string Command => "list";

			public override string Description => "Prints list of available commands.";

			protected override string OnHandle(CommandArgs args)
			{
				StringBuilder stringBuilder = new StringBuilder();
				stringBuilder.AppendLine("Available commands:");
				foreach (IRconCommand value in Instance._commands.Values)
				{
					stringBuilder.AppendLine(value.Command + " - " + value.Description);
				}
				return stringBuilder.ToString().Trim();
			}
		}

		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, string description, Func<CommandArgs, CommandResult> commandFunc)
		{
			RegisterCommand(new ActionCommand(command, description, commandFunc));
		}

		private async Task<string> HandleCommandAsync(RconPeer peer, 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 + "\n" + result.Text);
			this.OnCommandCompleted?.Invoke(peer, 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 value))
				{
					resultSource.TrySetResult(new CommandResult
					{
						Text = "Unknown command " + commandName
					});
				}
				else
				{
					resultSource.TrySetResult(await value.HandleCommandAsync(new CommandArgs(args)));
				}
			}
			catch (Exception ex)
			{
				resultSource.TrySetResult(new CommandResult
				{
					Text = ex.Message
				});
			}
		}
	}
	public static class ZdoUtils
	{
		[Flags]
		private enum Type
		{
			None = 0,
			ItemDrop = 1,
			GuardStone = 2,
			Character = 4,
			Building = 8,
			ItemStand = 0x10,
			Destructible = 0x20,
			Interactable = 0x40
		}

		[CompilerGenerated]
		private sealed class <GetPermittedPlayers>d__18 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private string <>2__current;

			private int <>l__initialThreadId;

			private ZDO zdo;

			public ZDO <>3__zdo;

			private int <count>5__2;

			private int <i>5__3;

			string IEnumerator<string>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <GetPermittedPlayers>d__18(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				switch (<>1__state)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					<count>5__2 = zdo.GetInt(ZDOVars.s_permitted, 0);
					if (<count>5__2 <= 0)
					{
						return false;
					}
					<i>5__3 = 0;
					break;
				case 1:
					<>1__state = -1;
					<i>5__3++;
					break;
				}
				if (<i>5__3 < <count>5__2)
				{
					long @long = zdo.GetLong($"pu_id{<i>5__3}", 0L);
					string @string = zdo.GetString($"pu_name{<i>5__3}", "");
					<>2__current = $"{@long}({@string})";
					<>1__state = 1;
					return true;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<string> IEnumerable<string>.GetEnumerator()
			{
				<GetPermittedPlayers>d__18 <GetPermittedPlayers>d__;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					<GetPermittedPlayers>d__ = this;
				}
				else
				{
					<GetPermittedPlayers>d__ = new <GetPermittedPlayers>d__18(0);
				}
				<GetPermittedPlayers>d__.zdo = <>3__zdo;
				return <GetPermittedPlayers>d__;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<string>)this).GetEnumerator();
			}
		}

		private static readonly Dictionary<int, Type> PrefabTypes = new Dictionary<int, Type>();

		private static readonly Dictionary<int, float> MaxHealth = new Dictionary<int, float>();

		private static readonly Dictionary<int, float> MaxSupport = new Dictionary<int, float>();

		public static string GetTag(this ZDO zdo)
		{
			return zdo.GetString("tag", "");
		}

		public static void SetTag(this ZDO zdo, string tag)
		{
			zdo.Set("tag", tag);
		}

		public static void AppendZdoStats(ZDO zdo, StringBuilder stringBuilder)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: 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_006c: Unknown result type (might be due to invalid IL or missing references)
			stringBuilder.Append($" Id: {((ZDOID)(ref zdo.m_uid)).ID} UserId: {((ZDOID)(ref zdo.m_uid)).UserID}");
			stringBuilder.Append($" Position: {zdo.GetPosition()}({ZoneSystem.GetZone(zdo.GetPosition())})");
			Quaternion rotation = zdo.GetRotation();
			stringBuilder.Append($" Rotation: {((Quaternion)(ref rotation)).eulerAngles}");
			string tag = zdo.GetTag();
			if (!string.IsNullOrEmpty(tag))
			{
				stringBuilder.Append(" Tag: " + tag);
			}
			zdo.GetPrefab();
			TryAppendItemDropData(zdo, stringBuilder);
			TryAppendBuildingData(zdo, stringBuilder);
			TryAppendCharacterData(zdo, stringBuilder);
			TryAppendGuardStoneData(zdo, stringBuilder);
			TryAppendItemStandData(zdo, stringBuilder);
		}

		public static string GetPrefabName(int prefabId)
		{
			GameObject prefab = ZNetScene.instance.GetPrefab(prefabId);
			if (!((Object)(object)prefab != (Object)null))
			{
				return "Unknown";
			}
			return ((Object)prefab).name;
		}

		public static void DeleteZDO(ZDO zdo)
		{
			//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_001c: 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_0033: Unknown result type (might be due to invalid IL or missing references)
			if (CanDeleteZdo(zdo))
			{
				zdo.SetOwner(ZDOMan.GetSessionID());
				ZDOID connectionZDOID = zdo.GetConnectionZDOID((ConnectionType)3);
				if (connectionZDOID != ZDOID.None && ZDOMan.instance.m_objectsByID.TryGetValue(connectionZDOID, out var value) && value != zdo)
				{
					DeleteZDO(value);
				}
				ZDOMan.instance.DestroyZDO(zdo);
			}
		}

		public static bool CanDeleteZdo(ZDO zdo)
		{
			if (!zdo.IsValid())
			{
				return false;
			}
			if (ZNet.instance.m_peers.Any((ZNetPeer p) => p.m_characterID == zdo.m_uid))
			{
				return false;
			}
			if (GetPrefabName(zdo.GetPrefab()).StartsWith("_"))
			{
				return false;
			}
			if (GetPrefabTypes(zdo.GetPrefab()) == Type.None)
			{
				return false;
			}
			return true;
		}

		public static bool MatchesCriteria(ZDO zdo, long? creatorId, ObjectId? id, string tag)
		{
			if (creatorId.HasValue && zdo.GetLong(ZDOVars.s_creator, 0L) != creatorId.Value)
			{
				return false;
			}
			if (id.HasValue && (((ZDOID)(ref zdo.m_uid)).ID != id.Value.Id || ((ZDOID)(ref zdo.m_uid)).UserID != id.Value.UserId))
			{
				return false;
			}
			if (!string.IsNullOrEmpty(tag) && zdo.GetTag() != tag)
			{
				return false;
			}
			return true;
		}

		private static void TryAppendItemStandData(ZDO zdo, StringBuilder stringBuilder)
		{
			if (!CheckPrefabType(zdo.GetPrefab(), Type.ItemStand))
			{
				return;
			}
			string @string = zdo.GetString(ZDOVars.s_item, "");
			if (!string.IsNullOrEmpty(@string))
			{
				stringBuilder.Append(" Attached item: " + @string);
				stringBuilder.Append($" Durability: {zdo.GetFloat(ZDOVars.s_durability, 0f)}");
				stringBuilder.Append($" Stack: {zdo.GetInt(ZDOVars.s_stack, 0)}");
				stringBuilder.Append($" Quality: {zdo.GetInt(ZDOVars.s_quality, 0)}");
				stringBuilder.Append($" Variant: {zdo.GetInt(ZDOVars.s_variant, 0)}");
				stringBuilder.Append(string.Format(" Crafter: {0} ({1})", zdo.GetString(ZDOVars.s_crafterName, ""), zdo.GetLong(ZDOVars.s_crafterID, 0L)));
				int @int = zdo.GetInt(ZDOVars.s_dataCount, 0);
				if (@int > 0)
				{
					stringBuilder.Append(" Data:");
				}
				for (int i = 0; i < @int; i++)
				{
					stringBuilder.Append(" '" + zdo.GetString($"data_{i}", "") + "'='" + zdo.GetString($"data__{i}", "") + "'");
				}
			}
		}

		private static void TryAppendItemDropData(ZDO zdo, StringBuilder stringBuilder)
		{
			if (CheckPrefabType(zdo.GetPrefab(), Type.ItemDrop))
			{
				stringBuilder.Append($" Durability: {zdo.GetFloat(ZDOVars.s_durability, 0f)}");
				stringBuilder.Append($" Stack: {zdo.GetInt(ZDOVars.s_stack, 0)}");
				stringBuilder.Append($" Quality: {zdo.GetInt(ZDOVars.s_quality, 0)}");
				stringBuilder.Append($" Variant: {zdo.GetInt(ZDOVars.s_variant, 0)}");
				stringBuilder.Append(string.Format(" Crafter: {0} ({1})", zdo.GetString(ZDOVars.s_crafterName, ""), zdo.GetLong(ZDOVars.s_crafterID, 0L)));
				stringBuilder.Append($" WorldLevel: {zdo.GetInt(ZDOVars.s_worldLevel, 0)}");
				stringBuilder.Append($" PickedUp: {zdo.GetBool(ZDOVars.s_pickedUp, false)}");
				int @int = zdo.GetInt(ZDOVars.s_dataCount, 0);
				if (@int > 0)
				{
					stringBuilder.Append(" Data:");
				}
				for (int i = 0; i < @int; i++)
				{
					stringBuilder.Append(" '" + zdo.GetString($"data_{i}", "") + "'='" + zdo.GetString($"data__{i}", "") + "'");
				}
			}
		}

		private static void TryAppendGuardStoneData(ZDO zdo, StringBuilder stringBuilder)
		{
			if (!CheckPrefabType(zdo.GetPrefab(), Type.GuardStone))
			{
				return;
			}
			stringBuilder.Append($" Enabled: {zdo.GetBool(ZDOVars.s_enabled, false)}");
			stringBuilder.Append(" Owner: " + zdo.GetString(ZDOVars.s_creatorName, ""));
			stringBuilder.Append(" Permitted:");
			foreach (string permittedPlayer in GetPermittedPlayers(zdo))
			{
				stringBuilder.Append(" " + permittedPlayer);
			}
		}

		private static void TryAppendCharacterData(ZDO zdo, StringBuilder stringBuilder)
		{
			if (CheckPrefabType(zdo.GetPrefab(), Type.Character))
			{
				stringBuilder.Append($" Level: {zdo.GetInt(ZDOVars.s_level, 0)}");
				float @float = zdo.GetFloat(ZDOVars.s_maxHealth, 0f);
				stringBuilder.Append($" Health: {zdo.GetFloat(ZDOVars.s_health, @float)}/{@float}");
				stringBuilder.Append($" Tamed: {zdo.GetBool(ZDOVars.s_tamed, false)}");
			}
		}

		private static void TryAppendBuildingData(ZDO zdo, StringBuilder stringBuilder)
		{
			if (CheckPrefabType(zdo.GetPrefab(), Type.Building))
			{
				stringBuilder.Append($" Creator: {zdo.GetLong(ZDOVars.s_creator, 0L)}");
				float value;
				float num = (MaxHealth.TryGetValue(zdo.GetPrefab(), out value) ? value : 0f);
				float value2;
				float num2 = (MaxSupport.TryGetValue(zdo.GetPrefab(), out value2) ? value2 : 0f);
				stringBuilder.Append($" Health: {zdo.GetFloat(ZDOVars.s_health, num)}");
				stringBuilder.Append($" Support: {zdo.GetFloat(ZDOVars.s_support, num2)}");
			}
		}

		private static bool CheckPrefabType(int prefabId, Type type)
		{
			if (!ZNetScene.instance.HasPrefab(prefabId))
			{
				return false;
			}
			return (GetPrefabTypes(prefabId) & type) != 0;
		}

		private static Type GetPrefabTypes(int prefabId)
		{
			if (!ZNetScene.instance.HasPrefab(prefabId))
			{
				return Type.None;
			}
			if (!PrefabTypes.TryGetValue(prefabId, out var value))
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(prefabId);
				ItemDrop val = default(ItemDrop);
				if (prefab.TryGetComponent<ItemDrop>(ref val))
				{
					value |= Type.ItemDrop;
				}
				Character val2 = default(Character);
				if (prefab.TryGetComponent<Character>(ref val2))
				{
					value |= Type.Character;
				}
				WearNTear val3 = default(WearNTear);
				if (prefab.TryGetComponent<WearNTear>(ref val3))
				{
					value |= Type.Building;
					MaxHealth[prefabId] = val3.m_health;
					MaxSupport[prefabId] = val3.GetMaxSupport();
				}
				PrivateArea val4 = default(PrivateArea);
				if (prefab.TryGetComponent<PrivateArea>(ref val4))
				{
					value |= Type.GuardStone;
				}
				ItemStand val5 = default(ItemStand);
				if (prefab.TryGetComponent<ItemStand>(ref val5))
				{
					value |= Type.ItemStand;
				}
				IDestructible val6 = default(IDestructible);
				if (prefab.TryGetComponent<IDestructible>(ref val6))
				{
					value |= Type.Destructible;
				}
				Interactable val7 = default(Interactable);
				if (prefab.TryGetComponent<Interactable>(ref val7))
				{
					value |= Type.Interactable;
				}
				PrefabTypes[prefabId] = value;
			}
			return value;
		}

		[IteratorStateMachine(typeof(<GetPermittedPlayers>d__18))]
		private static IEnumerable<string> GetPermittedPlayers(ZDO zdo)
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <GetPermittedPlayers>d__18(-2)
			{
				<>3__zdo = zdo
			};
		}
	}
}
namespace ValheimRcon.Core
{
	public 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 readonly List<RconPeer> _clientsSnapshot = new List<RconPeer>();

		private readonly object _clientsLock = new object();

		private readonly object _disconnectLock = new object();

		private IDisposable _acceptThread;

		internal event MessageReceived OnMessage;

		public AsynchronousSocketListener(IPAddress ipAddress, int port)
		{
			if (ipAddress == null)
			{
				throw new ArgumentNullException("ipAddress");
			}
			if (port < 1 || port > 65535)
			{
				throw new ArgumentOutOfRangeException("port", "Port must be between 1 and 65535");
			}
			_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)
		{
			if (peer.IsDisposed)
			{
				Log.Debug("Tried to send to a disposed peer.");
				return;
			}
			string endpointString = "unknown";
			try
			{
				Socket socket = peer.socket;
				if (socket == null || !socket.Connected)
				{
					Log.Debug("Warning: Socket is null or not connected");
					return;
				}
				byte[] array = packet.Serialize();
				int num = await SocketTaskExtensions.SendAsync(socket, new ArraySegment<byte>(array), SocketFlags.None);
				endpointString = socket.RemoteEndPoint.ToString();
				Log.Debug($"Sent {num} bytes to client [{endpointString}]");
			}
			catch (ObjectDisposedException)
			{
				Log.Debug("Attempted to send to a disposed socket (" + endpointString + ").");
			}
			catch (Exception msg)
			{
				Log.Error(msg);
			}
		}

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

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

		public void Disconnect(RconPeer peer)
		{
			lock (_disconnectLock)
			{
				_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)
			{
				return;
			}
			int available = socket.Available;
			if (available > peer.Buffer.Length)
			{
				Log.Warning($"Available data exceeds buffer size: {available} > {peer.Buffer.Length} [{peer.Endpoint}]");
				Disconnect(peer);
				return;
			}
			int num = socket.Receive(peer.Buffer, 0, Math.Min(available, peer.Buffer.Length), SocketFlags.None);
			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
			{
				lock (_clientsLock)
				{
					_clients.Add(state);
				}
			});
		}

		private void OnPackageReceived(RconPeer peer, int readCount)
		{
			_ = peer.socket;
			Log.Debug($"Got package from client, {readCount} bytes [{peer.Endpoint}]");
			try
			{
				RconPacket rconPacket = new RconPacket(peer.Buffer);
				Log.Debug($"Received package {rconPacket}");
				this.OnMessage?.Invoke(peer, rconPacket);
			}
			catch (Exception ex)
			{
				Log.Warning("Failed to parse packet from [" + peer.Endpoint + "]: " + ex.Message);
				Disconnect(peer);
			}
			finally
			{
				Array.Clear(peer.Buffer, 0, peer.Buffer.Length);
			}
		}

		private void DisconnectPeer(RconPeer peer)
		{
			_ = peer.socket;
			Log.Debug("Client disconnected [" + peer.Endpoint + "]");
			try
			{
				peer.Dispose();
			}
			catch
			{
				Log.Debug("Warning: Could not dispose peer connection");
			}
		}

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

		private static bool IsUnauthorizedTimeout(RconPeer peer)
		{
			if (!peer.Authentificated)
			{
				return DateTime.Now - peer.created > UnauthorizedClientLifetime;
			}
			return false;
		}
	}
	public enum PacketType
	{
		Error = 0,
		Command = 2,
		Login = 3
	}
	public delegate Task<string> RconCommandHandler(RconPeer peer, string command, IReadOnlyList<string> data);
	public class RconCommandReceiver : IDisposable
	{
		private const int MaxPayloadSize = 4080;

		private static readonly Regex MatchRegex = new Regex("(?<=[ ][\\\"]|^[\\\"])[^\\\"]+(?=[\\\"][ ]|[\\\"]$)|(?<=[ ]|^)[^\\\" ]+(?=[ ]|$)", RegexOptions.Compiled | RegexOptions.CultureInvariant);

		private readonly AsynchronousSocketListener _socketListener;

		private readonly string _password;

		private RconCommandHandler _commandHandler;

		public RconCommandReceiver(int port, string password, RconCommandHandler commandHandler)
		{
			if (string.IsNullOrEmpty(password))
			{
				throw new ArgumentException("Password cannot be null or empty", "password");
			}
			if (commandHandler == null)
			{
				throw new ArgumentNullException("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() ?? string.Empty, _password);
				RconPacket rconPacket2;
				if (success)
				{
					peer.SetAuthentificated(authentificated: true);
					rconPacket2 = new RconPacket(packet.requestId, PacketType.Command, "Logic success");
				}
				else
				{
					rconPacket2 = new RconPacket(-1, PacketType.Command, "Login failed");
				}
				Log.Debug($"Login result {rconPacket2}");
				await _socketListener.SendAsync(peer, rconPacket2);
				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 input = packet.payload?.TrimStart(new char[1] { '/' }) ?? string.Empty;
				List<string> list = (from Match m in MatchRegex.Matches(input)
					select m.Value).ToList();
				if (list.Count == 0)
				{
					Log.Warning("Empty command from [" + peer.Endpoint + "]");
					await _socketListener.SendAsync(peer, new RconPacket(packet.requestId, packet.type, "Empty command"));
					break;
				}
				string command = list[0];
				list.RemoveAt(0);
				string text = await _commandHandler(peer, command, list);
				if (RconPacket.GetPayloadSize(text) > 4080)
				{
					int maxLength = text.Length / 2;
					text = RconCommandsUtil.TruncateMessage(text, maxLength) + "\n--- message truncated ---";
				}
				RconPacket rconPacket = new RconPacket(packet.requestId, packet.type, text);
				Log.Debug($"Command result {command} - {rconPacket}");
				await _socketListener.SendAsync(peer, rconPacket);
				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;
			}
		}
	}
	public readonly struct RconPacket
	{
		public readonly int requestId;

		public readonly PacketType type;

		public readonly string payload;

		public RconPacket(byte[] bytes)
		{
			if (bytes == null)
			{
				throw new ArgumentNullException("bytes");
			}
			if (bytes.Length < 14)
			{
				throw new ArgumentException("Packet too small", "bytes");
			}
			if (bytes.Length > 65536)
			{
				throw new ArgumentException("Packet too large", "bytes");
			}
			using MemoryStream input = new MemoryStream(bytes);
			using BinaryReader binaryReader = new BinaryReader(input);
			int num = binaryReader.ReadInt32();
			if (num < 0)
			{
				throw new ArgumentException("Invalid packet length", "bytes");
			}
			if (num < 10)
			{
				throw new ArgumentException("Packet data too small", "bytes");
			}
			if (num > 2147483643 || num + 4 > bytes.Length)
			{
				throw new ArgumentException("Packet length exceeds buffer size", "bytes");
			}
			requestId = binaryReader.ReadInt32();
			type = (PacketType)binaryReader.ReadInt32();
			if (!Enum.IsDefined(typeof(PacketType), type))
			{
				throw new ArgumentException("Invalid packet type", "bytes");
			}
			int num2 = num - 10;
			if (num2 < 0)
			{
				throw new ArgumentException("Invalid payload size", "bytes");
			}
			if (num2 > 4096)
			{
				throw new ArgumentException("Payload too large", "bytes");
			}
			byte[] bytes2 = binaryReader.ReadBytes(num2);
			payload = Encoding.UTF8.GetString(bytes2);
		}

		public RconPacket(int requestId, PacketType type, string payload)
		{
			if (payload != null && GetPayloadSize(payload) > 4096)
			{
				throw new ArgumentException("Payload too large", "payload");
			}
			this.requestId = requestId;
			this.type = type;
			this.payload = payload ?? string.Empty;
		}

		public byte[] Serialize()
		{
			byte[] bytes = Encoding.UTF8.GetBytes(payload ?? string.Empty);
			if (bytes.Length > 4096)
			{
				throw new InvalidOperationException("Payload too large for serialization");
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			long position = memoryStream.Position;
			binaryWriter.Write(0);
			binaryWriter.Write(requestId);
			binaryWriter.Write((int)type);
			binaryWriter.Write(bytes);
			binaryWriter.Write((byte)0);
			binaryWriter.Write((byte)0);
			long num = memoryStream.Position - position - 4;
			if (num > int.MaxValue)
			{
				throw new InvalidOperationException("Packet too large for serialization");
			}
			int value = (int)num;
			memoryStream.Position = position;
			binaryWriter.Write(value);
			return memoryStream.ToArray();
		}

		public static int GetPayloadSize(string payload)
		{
			if (payload == null)
			{
				return 0;
			}
			return Encoding.UTF8.GetByteCount(payload);
		}

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

		private bool disposed;

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

		public readonly Socket socket;

		public readonly DateTime created;

		public bool IsDisposed => disposed;

		public bool Authentificated { get; private set; }

		public string Endpoint
		{
			get
			{
				try
				{
					return socket?.RemoteEndPoint?.ToString() ?? string.Empty;
				}
				catch
				{
					return "unknown";
				}
			}
		}

		public RconPeer(Socket workSocket)
		{
			if (workSocket == null)
			{
				throw new ArgumentNullException("workSocket");
			}
			socket = workSocket;
			created = DateTime.Now;
		}

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

		public void Dispose()
		{
			if (!disposed)
			{
				disposed = true;
				try
				{
					socket?.Shutdown(SocketShutdown.Both);
				}
				catch
				{
				}
				try
				{
					socket?.Close();
				}
				catch
				{
				}
				socket.Dispose();
			}
		}
	}
}
namespace ValheimRcon.Commands
{
	internal class ActionCommand : IRconCommand
	{
		private readonly Func<CommandArgs, CommandResult> _execute;

		public string Command { get; }

		public string Description { get; }

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

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

		public override string Description => "Adds a player to the admin list. Usage: addAdmin <steamId>";

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

		public override string Description => "Adds a global key to the server. Usage: addGlobalKey <key>";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZoneSystem.instance.GlobalKeyAdd(@string, true);
			return "Added global key: " + @string;
		}
	}
	internal class AddPermitted : RconCommand
	{
		public override string Command => "addPermitted";

		public override string Description => "Adds a player to the permitted list. Usage: addPermitted <steamId>";

		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";

		public override string Description => "Ban a user from the server. Usage: ban <playername or steamid>";

		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";

		public override string Description => "Ban a player by their Steam ID. Usage: banSteamId <steamId>";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.m_bannedList.Add(@string);
			return @string + " banned";
		}
	}
	public class CommandArgs
	{
		[CompilerGenerated]
		private sealed class <GetOptionalArguments>d__17 : IEnumerable<int>, IEnumerable, IEnumerator<int>, IDisposable, IEnumerator
		{
			private int <>1__state;

			private int <>2__current;

			private int <>l__initialThreadId;

			public CommandArgs <>4__this;

			private int <i>5__2;

			int IEnumerator<int>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <GetOptionalArguments>d__17(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				int num = <>1__state;
				CommandArgs commandArgs = <>4__this;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_0060;
				}
				<>1__state = -1;
				<i>5__2 = 0;
				goto IL_0070;
				IL_0060:
				<i>5__2++;
				goto IL_0070;
				IL_0070:
				if (<i>5__2 < commandArgs.Arguments.Count)
				{
					if (OptionalArgumentRegex.IsMatch(commandArgs.Arguments[<i>5__2]))
					{
						<>2__current = <i>5__2;
						<>1__state = 1;
						return true;
					}
					goto IL_0060;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<int> IEnumerable<int>.GetEnumerator()
			{
				<GetOptionalArguments>d__17 result;
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					result = this;
				}
				else
				{
					result = new <GetOptionalArguments>d__17(0)
					{
						<>4__this = <>4__this
					};
				}
				return result;
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<int>)this).GetEnumerator();
			}
		}

		private static readonly Regex OptionalArgumentRegex = new Regex("^-[A-Za-z]+$");

		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 int TryGetInt(int index, int defaultValue = 0)
		{
			if (!HasArgument(index))
			{
				return defaultValue;
			}
			return GetInt(index);
		}

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

		public long TryGetLong(int index, long defaultValue)
		{
			if (!HasArgument(index))
			{
				return defaultValue;
			}
			return GetLong(index);
		}

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

		public float TryGetFloat(int index, float defaultValue)
		{
			if (!HasArgument(index))
			{
				return defaultValue;
			}
			return GetFloat(index);
		}

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

		public string TryGetString(int index, string defaultValue = "")
		{
			if (!HasArgument(index))
			{
				return defaultValue;
			}
			return GetString(index);
		}

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

		public uint TryGetUInt(int index, uint defaultValue = 0u)
		{
			if (!HasArgument(index))
			{
				return defaultValue;
			}
			return GetUInt(index);
		}

		private void ValidateIndex(int index)
		{
			if (HasArgument(index))
			{
				return;
			}
			throw new ArgumentException($"Cannot get argument at {index}");
		}

		private bool HasArgument(int index)
		{
			if (index >= 0)
			{
				return index < Arguments.Count;
			}
			return false;
		}

		[IteratorStateMachine(typeof(<GetOptionalArguments>d__17))]
		public IEnumerable<int> GetOptionalArguments()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <GetOptionalArguments>d__17(-2)
			{
				<>4__this = this
			};
		}

		public override string ToString()
		{
			return string.Join(" ", Arguments);
		}
	}
	public static class CommandArgsExtensions
	{
		public static Vector3 GetVector3(this CommandArgs args, int index)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(args.GetFloat(index), args.GetFloat(index + 1), args.GetFloat(index + 2));
		}

		public static ObjectId GetObjectId(this CommandArgs args, int index)
		{
			return new ObjectId(args.GetUInt(index), args.GetLong(index + 1));
		}
	}
	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";

		public override string Description => "Damage a player by a specified amount. Usage: damage <steamid> <amount>";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: 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_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: 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_0040: 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 DeleteObjects : RconCommand
	{
		public override string Command => "deleteObjects";

		public override string Description => "Delete objects matching all search criteria. Usage (with optional arguments): deleteObjects -creator <creator id> -id <id> <userid> -tag <tag>";

		protected override string OnHandle(CommandArgs args)
		{
			IEnumerable<int> optionalArguments = args.GetOptionalArguments();
			if (!optionalArguments.Any())
			{
				return "At least one criteria must be provided.";
			}
			long? creatorId = null;
			ObjectId? id = null;
			string tag = string.Empty;
			foreach (int item in optionalArguments)
			{
				string @string = args.GetString(item);
				switch (@string.ToLower())
				{
				case "-creator":
					creatorId = args.GetLong(item + 1);
					break;
				case "-id":
					id = args.GetObjectId(item + 1);
					break;
				case "-tag":
					tag = args.GetString(item + 1);
					break;
				default:
					return "Unknown argument: " + @string;
				}
			}
			ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => ZdoUtils.MatchesCriteria(zdo, creatorId, id, tag)).ToArray();
			if (array.Length == 0)
			{
				return "No objects found matching the provided criteria.";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"Deleting {array.Length} objects:");
			ZDO[] array2 = array;
			foreach (ZDO val in array2)
			{
				string prefabName = ZdoUtils.GetPrefabName(val.GetPrefab());
				stringBuilder.Append("- Prefab: " + prefabName);
				ZdoUtils.AppendZdoStats(val, stringBuilder);
				if (ZdoUtils.CanDeleteZdo(val))
				{
					ZdoUtils.DeleteZDO(val);
					stringBuilder.AppendLine(" [deleted]");
				}
				else
				{
					stringBuilder.AppendLine(" [NOT ALLOWED TO DELETE]");
				}
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}
	}
	internal class ExcludeAttribute : Attribute
	{
	}
	internal class FindObjects : RconCommand
	{
		public override string Command => "findObjects";

		public override string Description => "Find objects matching all search criteria. Usage (with optional arguments): findObjects -prefab <prefab> -creator <creator id> -id <id> <userid> -tag <tag>";

		protected override string OnHandle(CommandArgs args)
		{
			IEnumerable<int> optionalArguments = args.GetOptionalArguments();
			if (!optionalArguments.Any())
			{
				return "At least one search criteria must be provided.";
			}
			string prefab = string.Empty;
			long? creatorId = null;
			ObjectId? id = null;
			string tag = string.Empty;
			foreach (int item in optionalArguments)
			{
				string @string = args.GetString(item);
				switch (@string.ToLower())
				{
				case "-prefab":
					prefab = args.GetString(item + 1);
					break;
				case "-creator":
					creatorId = args.GetLong(item + 1);
					break;
				case "-id":
					id = args.GetObjectId(item + 1);
					break;
				case "-tag":
					tag = args.GetString(item + 1);
					break;
				default:
					return "Unknown argument: " + @string;
				}
			}
			int prefabHash = StringExtensionMethods.GetStableHashCode(prefab);
			ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => (string.IsNullOrEmpty(prefab) || zdo.GetPrefab() == prefabHash) && ZdoUtils.MatchesCriteria(zdo, creatorId, id, tag)).ToArray();
			if (array.Length == 0)
			{
				return "No objects found matching the provided criteria.";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"Found {array.Length} objects:");
			ZDO[] array2 = array;
			foreach (ZDO obj in array2)
			{
				string prefabName = ZdoUtils.GetPrefabName(obj.GetPrefab());
				stringBuilder.Append("- Prefab: " + prefabName);
				ZdoUtils.AppendZdoStats(obj, stringBuilder);
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}
	}
	internal class FindObjectsNear : RconCommand
	{
		public override string Command => "findObjectsNear";

		public override string Description => "Find objects near a location. Usage (with optional arguments): findObjectsNear <x> <z> <y> <radius> -prefab <prefab> -creator <creator id> -id <id> <userid> -tag <tag>";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0168: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = args.GetVector3(0);
			float radius = args.GetFloat(3);
			long? creatorId = null;
			ObjectId? id = null;
			string tag = string.Empty;
			foreach (int optionalArgument in args.GetOptionalArguments())
			{
				string @string = args.GetString(optionalArgument);
				switch (@string.ToLower())
				{
				case "-creator":
					creatorId = args.GetLong(optionalArgument + 1);
					break;
				case "-id":
					id = args.GetObjectId(optionalArgument + 1);
					break;
				case "-tag":
					tag = args.GetString(optionalArgument + 1);
					break;
				default:
					return "Unknown argument: " + @string;
				}
			}
			ZDO[] array = ZDOMan.instance.m_objectsByID.Values.Where((ZDO zdo) => IsInRange(zdo.GetPosition(), position, radius) && ZdoUtils.MatchesCriteria(zdo, creatorId, id, tag)).ToArray();
			if (array.Length == 0)
			{
				return "No objects found";
			}
			StringBuilder stringBuilder = new StringBuilder();
			ZDO[] array2 = array;
			foreach (ZDO val in array2)
			{
				stringBuilder.Append("- Prefab: " + ZdoUtils.GetPrefabName(val.GetPrefab()));
				float num = Vector3.Distance(position, val.GetPosition());
				stringBuilder.Append($" Distance: {num}");
				ZdoUtils.AppendZdoStats(val, stringBuilder);
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString().Trim();
		}

		private static bool IsInRange(Vector3 zdoPosition, Vector3 position, float radius)
		{
			//IL_0000: 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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: 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)
			if (zdoPosition.x < position.x + radius && zdoPosition.x > position.x - radius && zdoPosition.y < position.y + radius && zdoPosition.y > position.y - radius && zdoPosition.z < position.z + radius)
			{
				return zdoPosition.z > position.z - radius;
			}
			return false;
		}
	}
	[Exclude]
	internal class MoveObjectById : RconCommand
	{
		public override string Command => "moveObjectById";

		public override string Description => "[WIP: Currently requires scene reload] Move an object by its user and object ids (which together make up a ZDO.m_uid). Usage: moveObjectById <userId> <objectId> <x> <y> <x> <rx> <ry> <rz>";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			long objectId = args.GetLong(0);
			ZDO val = ((IEnumerable<ZDO>)ZDOMan.instance.m_objectsByID.Values).FirstOrDefault((Func<ZDO, bool>)((ZDO obj) => ((ZDOID)(ref obj.m_uid)).ID == objectId));
			if (val == null)
			{
				return $"No objects found for id {objectId}";
			}
			Vector3 position = default(Vector3);
			((Vector3)(ref position))..ctor(args.GetFloat(2), args.GetFloat(3), args.GetFloat(4));
			Quaternion rotation = Quaternion.Euler(args.GetFloat(5), args.GetFloat(6), args.GetFloat(7));
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Moving object:");
			stringBuilder.Append("- Prefab: " + ZdoUtils.GetPrefabName(val.GetPrefab()));
			ZdoUtils.AppendZdoStats(val, stringBuilder);
			stringBuilder.AppendLine();
			val.SetPosition(position);
			val.SetRotation(rotation);
			val.IncreaseDataRevision();
			val.DataRevision += 120;
			ZDOMan.instance.ClientChanged(val.m_uid);
			ZDOMan.instance.ForceSendZDO(val.m_uid);
			stringBuilder.Append("- To new location: ");
			ZdoUtils.AppendZdoStats(val, stringBuilder);
			stringBuilder.AppendLine();
			return stringBuilder.ToString().Trim();
		}
	}
	internal class FindPlayer : RconCommand
	{
		public override string Command => "findPlayer";

		public override string Description => "Find a player and show their details. Usage: findPlayer <playername or steamid>";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			string @string = args.GetString(0);
			ZNetPeer val = ZNet.instance.GetPeerByPlayerName(@string) ?? ZNet.instance.GetPeerByHostName(@string);
			if (val == null)
			{
				return "Player " + @string + " not found";
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Player " + @string + " found:");
			stringBuilder.AppendLine("Steam ID: " + val.GetSteamId());
			stringBuilder.AppendLine("Name: " + val.m_playerName);
			ZDO zDO = val.GetZDO();
			bool flag = zDO == null;
			stringBuilder.AppendLine($"Is Dead: {flag}");
			if (!flag)
			{
				stringBuilder.AppendLine($"Position: {val.GetRefPos()} ({ZoneSystem.GetZone(val.GetRefPos())})");
				stringBuilder.AppendLine($"Health: {zDO.GetFloat(ZDOVars.s_health, 0f)}/{zDO.GetFloat(ZDOVars.s_maxHealth, 0f)}");
			}
			return stringBuilder.ToString().Trim();
		}
	}
	internal class GetServerStats : RconCommand
	{
		private StringBuilder builder = new StringBuilder();

		public override string Command => "serverStats";

		public override string Description => "Prints server statistics including player count, FPS, memory usage, and world information.";

		protected override string OnHandle(CommandArgs args)
		{
			builder.Clear();
			_ = ZNet.World?.m_name;
			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";

		public override string Description => "Spawns an item on the player. Usage (with optional arguments): give <steamid> <item_name> -count <count> -quality <quality> -variant <variant> -data <key> <value> -nocrafter";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_0206: Unknown result type (might be due to invalid IL or missing references)
			//IL_0288: Unknown result type (might be due to invalid IL or missing references)
			//IL_028d: Unknown result type (might be due to invalid IL or missing references)
			string @string = args.GetString(1);
			int num = 1;
			int num2 = 1;
			int num3 = 0;
			string crafterName = Plugin.ServerChatName.Value;
			long crafterID = -1L;
			Dictionary<string, string> dictionary = new Dictionary<string, string>();
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(@string);
			if ((Object)(object)itemPrefab == (Object)null)
			{
				return "Cannot find prefab " + @string;
			}
			ItemData itemData = itemPrefab.GetComponent<ItemDrop>().m_itemData;
			SharedData shared = itemData.m_shared;
			foreach (int optionalArgument in args.GetOptionalArguments())
			{
				string string2 = args.GetString(optionalArgument);
				switch (string2)
				{
				case "-count":
					num = args.GetInt(optionalArgument + 1);
					if (num < 1)
					{
						return "Count must be at least 1";
					}
					break;
				case "-quality":
					num2 = args.GetInt(optionalArgument + 1);
					if (num2 < 0)
					{
						return "Quality must be at least 0";
					}
					break;
				case "-variant":
					num3 = args.GetInt(optionalArgument + 1);
					if (num3 < 0)
					{
						return "Variant must be at least 0";
					}
					if (num3 > 0 && shared.m_variants == 0)
					{
						return "Item " + @string + " does not have variants";
					}
					if (num3 > shared.m_variants - 1)
					{
						return $"Item {@string} has only {shared.m_variants} variants";
					}
					break;
				case "-nocrafter":
					crafterID = 0L;
					crafterName = string.Empty;
					break;
				case "-data":
				{
					string string3 = args.GetString(optionalArgument + 1);
					string value = args.TryGetString(optionalArgument + 2);
					dictionary[string3] = value;
					break;
				}
				default:
					return "Unknown argument: " + string2;
				}
			}
			List<ItemDrop> list = default(List<ItemDrop>);
			IDisposable disposable = (IDisposable)(object)CollectionPool<List<ItemDrop>, ItemDrop>.Get(ref list);
			ZNetView.StartGhostInit();
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"Spawning items on player {peer.GetPlayerInfo()} {peer.GetRefPos()}:");
			while (num > 0)
			{
				ItemData val = itemData.Clone();
				int num4 = Math.Min(shared.m_maxStackSize, num);
				val.m_dropPrefab = itemPrefab;
				val.m_quality = num2;
				val.m_variant = num3;
				val.m_crafterID = crafterID;
				val.m_crafterName = crafterName;
				val.m_customData = dictionary;
				if (shared.m_useDurability)
				{
					val.m_durability = val.GetMaxDurability();
				}
				ItemDrop val2 = ItemDrop.DropItem(val, num4, peer.GetRefPos(), Quaternion.identity);
				list.Add(val2);
				num -= num4;
				stringBuilder.Append('-');
				ZdoUtils.AppendZdoStats(val2.m_nview.GetZDO(), stringBuilder);
				stringBuilder.AppendLine();
			}
			ZNetView.FinishGhostInit();
			foreach (ItemDrop item in list)
			{
				Object.Destroy((Object)(object)((Component)item).gameObject);
			}
			disposable.Dispose();
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}
	}
	internal class Heal : PlayerRconCommand
	{
		public override string Command => "heal";

		public override string Description => "Heals the player's to a specified value. Usage: heal <steamid> <value>";

		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 InvokeConsoleCommand : RconCommand
	{
		public override string Command => "consoleCommand";

		public override string Description => "Executes a console command on the server. Usage: consoleCommand <command>";

		protected override string OnHandle(CommandArgs args)
		{
			string text = string.Join(" ", args.Arguments);
			if (string.IsNullOrWhiteSpace(text))
			{
				return "No command provided.";
			}
			((Terminal)Console.instance).TryRunCommand(text, false, true);
			return "Command '" + text + "' executed.";
		}
	}
	internal class Kick : RconCommand
	{
		public override string Command => "kick";

		public override string Description => "Kicks a player from the server. Usage: kick <playername or steamid>";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZNet.instance.Kick(@string);
			return "Kicked " + @string;
		}
	}
	public readonly struct ObjectId
	{
		public readonly uint Id;

		public readonly long UserId;

		public ObjectId(uint id, long userId)
		{
			Id = id;
			UserId = userId;
		}
	}
	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";

		public override string Description => "Prints the list of admins on the server.";

		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";

		public override string Description => "Prints the list of banned players";

		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 PrintGlobalKeysCommand : RconCommand
	{
		public override string Command => "globalKeys";

		public override string Description => "Prints all global keys and their values.";

		protected override string OnHandle(CommandArgs args)
		{
			List<string> globalKeys = ZoneSystem.instance.GetGlobalKeys();
			Dictionary<string, string> globalKeysValues = ZoneSystem.instance.m_globalKeysValues;
			StringBuilder stringBuilder = new StringBuilder("Global Keys:\n");
			foreach (string item in globalKeys)
			{
				stringBuilder.Append(item);
				if (globalKeysValues.TryGetValue(item, out var value))
				{
					stringBuilder.Append(" : " + value);
				}
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString().Trim();
		}
	}
	internal class PrintPermitlist : RconCommand
	{
		private StringBuilder stringBuilder = new StringBuilder();

		public override string Command => "permitted";

		public override string Description => "Prints the list of permitted players on the server.";

		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
	{
		public abstract string Command { get; }

		public abstract string Description { get; }

		public Task<CommandResult> HandleCommandAsync(CommandArgs args)
		{
			CommandResult result = default(CommandResult);
			result.Text = OnHandle(args).Trim();
			return Task.FromResult(result);
		}

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

		public override string Description => "Removes a player from the admin list. Usage: removeAdmin <steamId>";

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

		public override string Description => "Removes a global key from the server. Usage: removeGlobalKey <key>";

		protected override string OnHandle(CommandArgs args)
		{
			string @string = args.GetString(0);
			ZoneSystem.instance.GlobalKeyRemove(@string, true);
			return "Global key '" + @string + "' removed successfully.";
		}
	}
	internal class RemovePermitted : RconCommand
	{
		public override string Command => "removePermitted";

		public override string Description => "Removes a player from the permitted list. Usage: removePermitted <steamId>";

		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";

		public override string Description => "Sends a message to the chat as a shout. Usage: say <message>";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_004c: 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";

		public override string Description => "Sends a ping message to all players at the specified coordinates. Usage: ping <x> <y> <z>";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: 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 const int MaxLinesToDisplay = 5;

		private readonly StringBuilder _builder = new StringBuilder();

		public string Command => "logs";

		public string Description => "Get the server 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);
			int num = Math.Max(array.Length - 5 - 1, 0);
			_builder.Clear();
			for (int i = num; i < array.Length; i++)
			{
				_builder.AppendLine(array[i]);
			}
			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";

		public override string Description => "Displays a message in the center of the screen for all players. Usage: showMessage <message>";

		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";

		public override string Description => "Show all online players with their positions and zones";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: 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";

		public override string Description => "Creates the specified number of objects at the given position. Usage (with optional arguments): spawn <prefabName> <x> <y> <z> -count(-c) <count> -radius(-rad) <radius> -level(-l) <level> -rotation(-rot) <x> <y> <z> -tag(-t) <tag> -tamed ";

		protected override string OnHandle(CommandArgs args)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: 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_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0212: Unknown result type (might be due to invalid IL or missing references)
			//IL_029d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c5: Unknown result type (might be due to invalid IL or missing references)
			string @string = args.GetString(0);
			Vector3 vector = args.GetVector3(1);
			int num = 1;
			int num2 = 0;
			string text = string.Empty;
			Quaternion val = Quaternion.identity;
			float num3 = 0f;
			bool flag = false;
			foreach (int optionalArgument in args.GetOptionalArguments())
			{
				string string2 = args.GetString(optionalArgument);
				switch (string2.ToLower())
				{
				case "-level":
				case "-l":
					num2 = args.GetInt(optionalArgument + 1);
					break;
				case "-count":
				case "-c":
					num = args.GetInt(optionalArgument + 1);
					break;
				case "-t":
				case "-tag":
					text = args.GetString(optionalArgument + 1);
					break;
				case "-rot":
				case "-rotation":
					val = Quaternion.Euler(args.GetVector3(optionalArgument + 1));
					break;
				case "-rad":
				case "-radius":
					num3 = args.GetFloat(optionalArgument + 1);
					break;
				case "-tamed":
					flag = true;
					break;
				default:
					return "Unknown argument: " + string2;
				}
			}
			GameObject prefab = ZNetScene.instance.GetPrefab(@string);
			if ((Object)(object)prefab == (Object)null)
			{
				return "Prefab " + @string + " not found";
			}
			if (num <= 0)
			{
				return "Nothing to spawn";
			}
			List<ZDO> list = new List<ZDO>(num);
			Character val4 = default(Character);
			ItemDrop val5 = default(ItemDrop);
			for (int i = 0; i < num; i++)
			{
				ZNetView.StartGhostInit();
				Vector3 val2 = Random.onUnitSphere * num3;
				val2.y = 0f;
				Vector3 val3 = vector + val2;
				GameObject obj = Object.Instantiate<GameObject>(prefab, val3, val);
				if (obj.TryGetComponent<Character>(ref val4))
				{
					val4.SetLevel(num2);
				}
				if (obj.TryGetComponent<ItemDrop>(ref val5))
				{
					val5.SetQuality(num2);
				}
				ZDO zDO = obj.GetComponent<ZNetView>().GetZDO();
				list.Add(zDO);
				if (!string.IsNullOrEmpty(text))
				{
					zDO.SetTag(text);
				}
				if (flag)
				{
					zDO.Set(ZDOVars.s_tamed, true);
				}
				ZNetView.FinishGhostInit();
				Object.Destroy((Object)(object)obj);
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine($"Spawned {num} objects:");
			foreach (ZDO item in list)
			{
				stringBuilder.Append("- Prefab: " + ZdoUtils.GetPrefabName(item.GetPrefab()));
				ZdoUtils.AppendZdoStats(item, stringBuilder);
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString().TrimEnd(Array.Empty<char>());
		}
	}
	internal class TeleportPlayer : PlayerRconCommand
	{
		public override string Command => "teleport";

		public override string Description => "Teleports the player to a specified position. Usage: teleport <x> <y> <z>";

		protected override string OnHandle(ZNetPeer peer, ZDO zdo, CommandArgs args)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			Vector3 vector = args.GetVector3(1);
			peer.InvokeRoutedRpcToZdo("RPC_TeleportTo", vector, Quaternion.identity, true);
			return $"Player {peer.GetPlayerInfo()} teleported to {vector}";
		}
	}
	internal class ServerTimeCommand : RconCommand
	{
		public override string Command => "time";

		public override string Description => "Get the current server time and day.";

		protected override string OnHandle(CommandArgs args)
		{
			double timeSeconds = ZNet.instance.GetTimeSeconds();
			int currentDay = EnvMan.instance.GetCurrentDay();
			return $"Current server time: {timeSeconds} sec. Day: {currentDay}";
		}
	}
	internal class Unban : RconCommand
	{
		public override string Command => "unban";

		public override string Description => "Unban a user from the server. Usage: unban <playername or steamid>";

		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";

		public override string Description => "Save the current world state.";

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