Decompiled source of RCON Next v1.2.3

BepInEx/plugins/RconNext/rcon.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using Microsoft.CodeAnalysis;
using RconNext;
using RconNext.Internal;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("ferrinius")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("Secure Source RCON support for BepInEx-compatible game servers")]
[assembly: AssemblyFileVersion("1.2.3.0")]
[assembly: AssemblyInformationalVersion("1.2.3+745fdf876e26817758a5e2704fe6faf0802328b3")]
[assembly: AssemblyProduct("rcon")]
[assembly: AssemblyTitle("rcon")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/ferrinius/bepinex-rcon")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: InternalsVisibleTo("RconNext.ProtocolTests")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace rcon
{
	public abstract class AbstractCommand : ICommand
	{
		protected BaseUnityPlugin? Plugin { get; private set; }

		void ICommand.setOwner(BaseUnityPlugin owner)
		{
			Plugin = owner;
		}

		public abstract string onCommand(string[] args);
	}
	internal interface ICommand
	{
		void setOwner(BaseUnityPlugin owner);

		string onCommand(string[] args);
	}
	[BepInPlugin("nl.avii.plugins.rcon", "rcon", "1.0.5")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class rcon : BaseUnityPlugin
	{
		public delegate string UnknownCommand(string command, string[] args);

		public delegate string ParamsAction(params string[] args);

		private const string ConfigurationSection = "rcon";

		private const string DefaultPassword = "ChangeMe";

		public const string PluginGuid = "nl.avii.plugins.rcon";

		public const string PluginName = "rcon";

		public const string PluginVersion = "1.0.5";

		private ConfigEntry<bool>? _legacyEnabled;

		private ConfigEntry<int>? _legacyPort;

		private ConfigEntry<string>? _legacyPassword;

		private RconPlugin? _implementation;

		private RconPlugin Implementation => _implementation ?? throw new InvalidOperationException("The RCON Next implementation plugin is not available.");

		public event UnknownCommand? OnUnknownCommand;

		private rcon()
		{
		}

		private void Awake()
		{
			_implementation = ((Component)this).GetComponent<RconPlugin>() ?? throw new InvalidOperationException("The RCON Next implementation plugin was not loaded.");
			_implementation.OnUnknownCommand += ForwardUnknownCommand;
			if (!_implementation.HadConfigurationAtStartup && File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				_legacyEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("rcon", "enabled", false, "Enable RCON Communication");
				_legacyPort = ((BaseUnityPlugin)this).Config.Bind<int>("rcon", "port", 2458, "Port to use for RCON Communication");
				_legacyPassword = ((BaseUnityPlugin)this).Config.Bind<string>("rcon", "password", "ChangeMe", "Password to use for RCON Communication");
			}
		}

		private void OnEnable()
		{
			if (_legacyEnabled != null && _legacyPort != null && _legacyPassword != null)
			{
				Implementation.ApplyLegacyConfiguration(_legacyEnabled.Value, _legacyPort.Value, _legacyPassword.Value);
			}
		}

		private void OnDestroy()
		{
			if ((Object)(object)_implementation != (Object)null)
			{
				_implementation.OnUnknownCommand -= ForwardUnknownCommand;
				_implementation = null;
			}
		}

		public void RegisterCommand<T>(BaseUnityPlugin owner, string command) where T : AbstractCommand, new()
		{
			Implementation.RegisterCommand(owner, command, delegate(string[] arguments)
			{
				T val = new T();
				((ICommand)val).setOwner(owner);
				return val.onCommand(arguments);
			});
		}

		public void RegisterCommand(BaseUnityPlugin owner, string command, ParamsAction action)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			Implementation.RegisterCommand(owner, command, (string[] arguments) => action(arguments));
		}

		public void UnRegisterCommand(BaseUnityPlugin owner, string command)
		{
			Implementation.UnRegisterCommand(owner, command);
		}

		private string ForwardUnknownCommand(string command, string[] arguments)
		{
			return this.OnUnknownCommand?.Invoke(command, arguments) ?? string.Empty;
		}
	}
}
namespace RconNext
{
	public abstract class AbstractCommand
	{
		protected BaseUnityPlugin? Plugin { get; private set; }

		internal string? Execute(BaseUnityPlugin owner, string[] arguments)
		{
			Plugin = owner;
			return onCommand(arguments);
		}

		public abstract string onCommand(string[] args);
	}
	[BepInPlugin("com.github.ferrinius.rcon-next", "rcon-next", "1.2.3")]
	public class RconPlugin : BaseUnityPlugin
	{
		public delegate string UnknownCommand(string command, string[] args);

		public delegate string ParamsAction(params string[] args);

		private sealed class RegisteredCommand(BaseUnityPlugin owner, Func<string[], string?> handler)
		{
			internal BaseUnityPlugin Owner { get; } = owner;

			internal Func<string[], string?> Handler { get; } = handler;
		}

		private const string ConfigurationSection = "rcon-next";

		private const string DefaultPassword = "ChangeMe";

		public const string PluginGuid = "com.github.ferrinius.rcon-next";

		public const string PluginName = "rcon-next";

		public const string PluginVersion = "1.2.3";

		private readonly object _commandLock = new object();

		private readonly Dictionary<string, RegisteredCommand> _commands = new Dictionary<string, RegisteredCommand>(StringComparer.OrdinalIgnoreCase);

		private ConfigEntry<bool>? _enabled;

		private ConfigEntry<int>? _port;

		private ConfigEntry<string>? _password;

		private SourceRconServer? _server;

		internal bool HadConfigurationAtStartup { get; private set; }

		public event UnknownCommand? OnUnknownCommand;

		private void Awake()
		{
			HadConfigurationAtStartup = File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath);
			_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("rcon-next", "enabled", false, "Enable Source RCON communication");
			_port = ((BaseUnityPlugin)this).Config.Bind<int>("rcon-next", "port", 2458, "TCP port used for Source RCON");
			_password = ((BaseUnityPlugin)this).Config.Bind<string>("rcon-next", "password", "ChangeMe", "Password required by Source RCON clients");
			((MonoBehaviour)this).InvokeRepeating("CleanupConnections", 1f, 1f);
		}

		private void OnEnable()
		{
			ConfigEntry<bool>? enabled = _enabled;
			if (enabled == null || !enabled.Value)
			{
				return;
			}
			try
			{
				StartServer((_port ?? throw new InvalidOperationException("RCON Next configuration is not initialized.")).Value, _password?.Value ?? throw new InvalidOperationException("RCON Next configuration is not initialized."));
			}
			catch (Exception ex)
			{
				StopServer();
				((BaseUnityPlugin)this).Logger.LogError((object)("RCON Next failed to start: " + ex.Message));
				throw;
			}
		}

		private void OnDisable()
		{
			StopServer();
		}

		private void OnDestroy()
		{
			((MonoBehaviour)this).CancelInvoke("CleanupConnections");
			StopServer();
		}

		public void RegisterCommand<T>(BaseUnityPlugin owner, string command) where T : AbstractCommand, new()
		{
			if ((Object)(object)owner == (Object)null)
			{
				throw new ArgumentNullException("owner");
			}
			AddCommand(owner, command, (string[] arguments) => new T().Execute(owner, arguments));
		}

		public void RegisterCommand(BaseUnityPlugin owner, string command, ParamsAction action)
		{
			if ((Object)(object)owner == (Object)null)
			{
				throw new ArgumentNullException("owner");
			}
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			AddCommand(owner, command, (string[] arguments) => action(arguments));
		}

		public void UnregisterCommand(BaseUnityPlugin owner, string command)
		{
			if ((Object)(object)owner == (Object)null)
			{
				throw new ArgumentNullException("owner");
			}
			string key = NormalizeCommandName(command);
			lock (_commandLock)
			{
				if (_commands.TryGetValue(key, out RegisteredCommand value) && value.Owner == owner)
				{
					_commands.Remove(key);
				}
			}
		}

		public void UnRegisterCommand(BaseUnityPlugin owner, string command)
		{
			UnregisterCommand(owner, command);
		}

		internal void ApplyLegacyConfiguration(bool enabled, int port, string password)
		{
			if (_enabled == null || _port == null || _password == null)
			{
				throw new InvalidOperationException("RCON Next configuration is not initialized.");
			}
			_enabled.Value = enabled;
			_port.Value = port;
			_password.Value = password;
			((BaseUnityPlugin)this).Config.Save();
			if (!enabled)
			{
				StopServer();
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Imported the existing legacy rcon configuration; RCON remains disabled.");
				return;
			}
			try
			{
				StartServer(port, password);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Imported and activated the existing legacy rcon configuration.");
			}
			catch (Exception ex)
			{
				StopServer();
				((BaseUnityPlugin)this).Logger.LogError((object)("RCON Next failed to start from the legacy rcon configuration: " + ex.Message));
				throw;
			}
		}

		private void StartServer(int port, string password)
		{
			if (string.Equals(password.Trim(), "ChangeMe", StringComparison.Ordinal))
			{
				throw new InvalidOperationException("The RCON password still has its default value. Change it and restart the server.");
			}
			if ((port < 1 || port > 65535) ? true : false)
			{
				throw new InvalidOperationException("The configured RCON port must be between 1 and 65535.");
			}
			StopServer();
			SourceRconServer sourceRconServer = new SourceRconServer(port, ExecuteCommand);
			sourceRconServer.Start(password);
			_server = sourceRconServer;
			((BaseUnityPlugin)this).Logger.LogInfo((object)$"RCON Next is listening on TCP port {sourceRconServer.Port}.");
		}

		private void StopServer()
		{
			SourceRconServer? server = _server;
			_server = null;
			server?.Dispose();
		}

		private void CleanupConnections()
		{
			_server?.Cleanup();
		}

		private void AddCommand(BaseUnityPlugin owner, string command, Func<string[], string?> handler)
		{
			string text = NormalizeCommandName(command);
			lock (_commandLock)
			{
				if (_commands.ContainsKey(text))
				{
					((BaseUnityPlugin)this).Logger.LogError((object)("The RCON command '" + text + "' is already registered."));
					return;
				}
				_commands.Add(text, new RegisteredCommand(owner, handler));
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Registered RCON command '" + text + "'."));
		}

		private RconCommandResult ExecuteCommand(string command, string[] arguments)
		{
			RegisteredCommand value;
			lock (_commandLock)
			{
				_commands.TryGetValue(command, out value);
			}
			try
			{
				if (value != null)
				{
					return new RconCommandResult(value.Handler(arguments));
				}
				return new RconCommandResult(this.OnUnknownCommand?.Invoke(command, arguments) ?? string.Empty);
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"RCON command '{command}' failed: {arg}");
				return new RconCommandResult("Command execution failed");
			}
		}

		private static string NormalizeCommandName(string command)
		{
			if (command == null)
			{
				throw new ArgumentNullException("command");
			}
			string text = command.Trim();
			if (text.Length == 0)
			{
				throw new ArgumentException("A command name cannot be empty.", "command");
			}
			for (int i = 0; i < text.Length; i++)
			{
				bool flag = char.IsWhiteSpace(text[i]);
				if (!flag)
				{
					char c = text[i];
					bool flag2 = ((c == '"' || c == '/') ? true : false);
					flag = flag2;
				}
				if (flag)
				{
					throw new ArgumentException("A command name cannot contain whitespace, quotes, or slashes.", "command");
				}
			}
			return text;
		}
	}
	public static class CommandLineFormatter
	{
		public static string Format(string command, IReadOnlyList<string> arguments)
		{
			if (command == null)
			{
				throw new ArgumentNullException("command");
			}
			if (arguments == null)
			{
				throw new ArgumentNullException("arguments");
			}
			ValidateUtf16(command, "command");
			StringBuilder stringBuilder = new StringBuilder();
			if (NeedsQuoting(command))
			{
				AppendQuoted(stringBuilder, command);
			}
			else
			{
				stringBuilder.Append(command);
			}
			string text = FormatArguments(arguments);
			if (text.Length > 0)
			{
				stringBuilder.Append(' ');
				stringBuilder.Append(text);
			}
			return stringBuilder.ToString();
		}

		public static string FormatArguments(IReadOnlyList<string> arguments)
		{
			if (arguments == null)
			{
				throw new ArgumentNullException("arguments");
			}
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < arguments.Count; i++)
			{
				string text = arguments[i];
				if (text == null)
				{
					throw new ArgumentException("Command-line arguments cannot contain null.", "arguments");
				}
				ValidateUtf16(text, "arguments");
				if (i > 0)
				{
					stringBuilder.Append(' ');
				}
				AppendQuoted(stringBuilder, text);
			}
			return stringBuilder.ToString();
		}

		private static bool NeedsQuoting(string value)
		{
			if (value.Length == 0 || value[0] == '/')
			{
				return true;
			}
			foreach (char c in value)
			{
				bool flag = c <= '\u001f' || char.IsWhiteSpace(c);
				if (!flag)
				{
					bool flag2 = ((c == '"' || c == '\\') ? true : false);
					flag = flag2;
				}
				if (flag)
				{
					return true;
				}
			}
			return false;
		}

		private static void AppendQuoted(StringBuilder result, string value)
		{
			result.Append('"');
			foreach (char c in value)
			{
				if (c < ' ')
				{
					switch (c)
					{
					case '\b':
						result.Append("\\b");
						continue;
					case '\f':
						result.Append("\\f");
						continue;
					case '\n':
						result.Append("\\n");
						continue;
					case '\r':
						result.Append("\\r");
						continue;
					case '\t':
						result.Append("\\t");
						continue;
					}
					result.Append("\\u");
					int num = c;
					result.Append(num.ToString("X4"));
				}
				else
				{
					switch (c)
					{
					case '"':
						result.Append("\\\"");
						break;
					case '\\':
						result.Append("\\\\");
						break;
					default:
						result.Append(c);
						break;
					}
				}
			}
			result.Append('"');
		}

		private static void ValidateUtf16(string value, string parameterName)
		{
			for (int i = 0; i < value.Length; i++)
			{
				char c = value[i];
				if (char.IsSurrogate(c))
				{
					if (!char.IsHighSurrogate(c) || i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1]))
					{
						throw new ArgumentException("Command-line values must contain valid UTF-16.", parameterName);
					}
					i++;
				}
			}
		}
	}
	internal static class RconBuildInfo
	{
		internal const string Version = "1.2.3";
	}
}
namespace RconNext.Internal
{
	internal readonly struct SourceRconPacket
	{
		internal int RequestId { get; }

		internal SourceRconPacketType Type { get; }

		internal string Payload { get; }

		internal SourceRconPacket(int requestId, SourceRconPacketType type, string payload)
		{
			RequestId = requestId;
			Type = type;
			Payload = payload;
		}
	}
	internal enum SourceRconPacketType
	{
		CommandResponse = 0,
		Command = 2,
		AuthenticationResponse = 2,
		AuthenticationRequest = 3
	}
	internal sealed class SourceRconPacketBuffer
	{
		internal const int Capacity = 8200;

		private readonly byte[] _storage = new byte[8200];

		private int _count;

		internal bool Append(byte[] source, int count)
		{
			if (count < 0 || count > source.Length)
			{
				throw new ArgumentOutOfRangeException("count");
			}
			if (count > _storage.Length - _count)
			{
				return false;
			}
			Buffer.BlockCopy(source, 0, _storage, _count, count);
			_count += count;
			return true;
		}

		internal FrameDecodeStatus Read(out SourceRconPacket packet)
		{
			int consumedBytes;
			FrameDecodeStatus frameDecodeStatus = SourceRconPacketCodec.Decode(_storage, _count, out packet, out consumedBytes);
			if (frameDecodeStatus != FrameDecodeStatus.Complete)
			{
				return frameDecodeStatus;
			}
			int num = _count - consumedBytes;
			if (num > 0)
			{
				Buffer.BlockCopy(_storage, consumedBytes, _storage, 0, num);
			}
			Array.Clear(_storage, num, consumedBytes);
			_count = num;
			return FrameDecodeStatus.Complete;
		}
	}
	internal static class SourceRconPacketCodec
	{
		internal const int LengthPrefixSize = 4;

		internal const int MinimumBodySize = 10;

		internal const int MaximumBodySize = 4096;

		internal const int MaximumFrameSize = 4100;

		internal const int MaximumPayloadSize = 4086;

		internal static byte[] Encode(SourceRconPacket packet)
		{
			string text = packet.Payload ?? throw new ArgumentException("A Source RCON packet must have a payload.", "packet");
			if (text.IndexOf('\0') >= 0)
			{
				throw new ArgumentException("A Source RCON payload cannot contain a null character.", "packet");
			}
			byte[] bytes = Encoding.ASCII.GetBytes(text);
			if (bytes.Length > 4086)
			{
				throw new ArgumentOutOfRangeException("packet", "The Source RCON payload exceeds the frame limit.");
			}
			int num = 10 + bytes.Length;
			byte[] array = new byte[4 + num];
			WriteInt32(array, 0, num);
			WriteInt32(array, 4, packet.RequestId);
			WriteInt32(array, 8, (int)packet.Type);
			Buffer.BlockCopy(bytes, 0, array, 12, bytes.Length);
			return array;
		}

		internal static byte[] EncodeResponseBoundary(int requestId)
		{
			byte[] array = new byte[16];
			WriteInt32(array, 0, 12);
			WriteInt32(array, 4, requestId);
			WriteInt32(array, 8, 0);
			array[13] = 1;
			return array;
		}

		internal static FrameDecodeStatus Decode(byte[] source, int availableBytes, out SourceRconPacket packet, out int consumedBytes)
		{
			packet = default(SourceRconPacket);
			consumedBytes = 0;
			if (availableBytes < 4)
			{
				return FrameDecodeStatus.NeedMoreData;
			}
			int num = ReadInt32(source, 0);
			if (num < 10 || num > 4096)
			{
				return FrameDecodeStatus.Invalid;
			}
			int num2 = 4 + num;
			if (availableBytes < num2)
			{
				return FrameDecodeStatus.NeedMoreData;
			}
			int num3 = num - 10;
			int num4 = 12;
			int num5 = num4 + num3;
			if (source[num5] != 0 || source[num5 + 1] != 0)
			{
				return FrameDecodeStatus.Invalid;
			}
			for (int i = num4; i < num5; i++)
			{
				if (source[i] == 0 || source[i] > 127)
				{
					return FrameDecodeStatus.Invalid;
				}
			}
			packet = new SourceRconPacket(ReadInt32(source, 4), (SourceRconPacketType)ReadInt32(source, 8), Encoding.ASCII.GetString(source, num4, num3));
			consumedBytes = num2;
			return FrameDecodeStatus.Complete;
		}

		private static int ReadInt32(byte[] source, int offset)
		{
			return source[offset] | (source[offset + 1] << 8) | (source[offset + 2] << 16) | (source[offset + 3] << 24);
		}

		private static void WriteInt32(byte[] destination, int offset, int value)
		{
			destination[offset] = (byte)value;
			destination[offset + 1] = (byte)(value >> 8);
			destination[offset + 2] = (byte)(value >> 16);
			destination[offset + 3] = (byte)(value >> 24);
		}
	}
	internal enum FrameDecodeStatus
	{
		NeedMoreData,
		Complete,
		Invalid
	}
	internal static class CommandLineLexer
	{
		internal static bool TryTokenize(string input, out CommandLineToken[] tokens)
		{
			List<CommandLineToken> list = new List<CommandLineToken>();
			int i = 0;
			if (input.Length > 0 && input[0] == '/')
			{
				list.Add(new CommandLineToken(CommandLineTokenKind.LeadingSlash, "/"));
				i++;
			}
			while (i < input.Length)
			{
				if (char.IsWhiteSpace(input[i]))
				{
					int num = i++;
					for (; i < input.Length && char.IsWhiteSpace(input[i]); i++)
					{
					}
					list.Add(new CommandLineToken(CommandLineTokenKind.Whitespace, input.Substring(num, i - num)));
				}
				else if (input[i] == '"')
				{
					if (!TryReadQuotedPart(input, ref i, out string value))
					{
						tokens = Array.Empty<CommandLineToken>();
						return false;
					}
					list.Add(new CommandLineToken(CommandLineTokenKind.TokenPart, value));
				}
				else
				{
					list.Add(new CommandLineToken(CommandLineTokenKind.TokenPart, ReadUnquotedPart(input, ref i)));
				}
			}
			tokens = list.ToArray();
			return true;
		}

		private static string ReadUnquotedPart(string input, ref int position)
		{
			StringBuilder stringBuilder = new StringBuilder();
			while (position < input.Length && !char.IsWhiteSpace(input[position]) && input[position] != '"')
			{
				char c = input[position];
				bool flag = c == '\\' && position + 1 < input.Length;
				if (flag)
				{
					char c2 = input[position + 1];
					bool flag2 = ((c2 == '"' || c2 == '\\') ? true : false);
					flag = flag2;
				}
				if (flag)
				{
					stringBuilder.Append(input[position + 1]);
					position += 2;
				}
				else
				{
					stringBuilder.Append(c);
					position++;
				}
			}
			return stringBuilder.ToString();
		}

		private static bool TryReadQuotedPart(string input, ref int position, out string value)
		{
			StringBuilder stringBuilder = new StringBuilder();
			position++;
			while (position < input.Length)
			{
				char c = input[position];
				if (c == '"')
				{
					position++;
					value = stringBuilder.ToString();
					return true;
				}
				if (c == '\\')
				{
					if (!TryReadQuotedEscape(input, ref position, stringBuilder))
					{
						value = string.Empty;
						return false;
					}
					continue;
				}
				if (c <= '\u001f')
				{
					value = string.Empty;
					return false;
				}
				if (char.IsHighSurrogate(c))
				{
					if (position + 1 >= input.Length || !char.IsLowSurrogate(input[position + 1]))
					{
						value = string.Empty;
						return false;
					}
					stringBuilder.Append(c);
					stringBuilder.Append(input[position + 1]);
					position += 2;
				}
				else
				{
					if (char.IsLowSurrogate(c))
					{
						value = string.Empty;
						return false;
					}
					stringBuilder.Append(c);
					position++;
				}
			}
			value = string.Empty;
			return false;
		}

		private static bool TryReadQuotedEscape(string input, ref int position, StringBuilder result)
		{
			if (position + 1 >= input.Length)
			{
				return false;
			}
			switch (input[position + 1])
			{
			case '"':
				result.Append('"');
				position += 2;
				return true;
			case '\\':
				result.Append('\\');
				position += 2;
				return true;
			case '/':
				result.Append('/');
				position += 2;
				return true;
			case 'b':
				result.Append('\b');
				position += 2;
				return true;
			case 'f':
				result.Append('\f');
				position += 2;
				return true;
			case 'n':
				result.Append('\n');
				position += 2;
				return true;
			case 'r':
				result.Append('\r');
				position += 2;
				return true;
			case 't':
				result.Append('\t');
				position += 2;
				return true;
			case 'u':
				return TryReadUtf16Escape(input, ref position, result);
			case 'U':
				return TryReadUnicodeScalarEscape(input, ref position, result);
			default:
				return false;
			}
		}

		private static bool TryReadUtf16Escape(string input, ref int position, StringBuilder result)
		{
			if (!TryReadHex(input, position + 2, 4, out var value))
			{
				return false;
			}
			char c = (char)value;
			if (char.IsLowSurrogate(c))
			{
				return false;
			}
			if (!char.IsHighSurrogate(c))
			{
				result.Append(c);
				position += 6;
				return true;
			}
			int num = position + 6;
			if (num + 1 >= input.Length || input[num] != '\\' || input[num + 1] != 'u' || !TryReadHex(input, num + 2, 4, out var value2) || !char.IsLowSurrogate((char)value2))
			{
				return false;
			}
			result.Append(c);
			result.Append((char)value2);
			position += 12;
			return true;
		}

		private static bool TryReadUnicodeScalarEscape(string input, ref int position, StringBuilder result)
		{
			if (!TryReadHex(input, position + 2, 8, out var value) || value > 1114111 || (value >= 55296 && value <= 57343))
			{
				return false;
			}
			result.Append(char.ConvertFromUtf32((int)value));
			position += 10;
			return true;
		}

		private static bool TryReadHex(string input, int position, int length, out uint value)
		{
			value = 0u;
			if (position > input.Length - length)
			{
				return false;
			}
			for (int i = 0; i < length; i++)
			{
				int num = HexValue(input[position + i]);
				if (num < 0)
				{
					value = 0u;
					return false;
				}
				value = (value << 4) | (uint)num;
			}
			return true;
		}

		private static int HexValue(char value)
		{
			if (value >= '0' && value <= '9')
			{
				return value - 48;
			}
			if (value >= 'A' && value <= 'F')
			{
				return value - 65 + 10;
			}
			if (value >= 'a' && value <= 'f')
			{
				return value - 97 + 10;
			}
			return -1;
		}
	}
	internal static class CommandLineParser
	{
		internal static bool TryParse(string input, out string command, out string[] arguments)
		{
			command = string.Empty;
			arguments = Array.Empty<string>();
			if (!CommandLineLexer.TryTokenize(input, out CommandLineToken[] tokens))
			{
				return false;
			}
			int position = 0;
			if (position < tokens.Length && tokens[position].Kind == CommandLineTokenKind.LeadingSlash)
			{
				position++;
			}
			SkipWhitespace(tokens, ref position);
			List<string> list = new List<string>();
			while (position < tokens.Length)
			{
				if (tokens[position].Kind != CommandLineTokenKind.TokenPart)
				{
					return false;
				}
				StringBuilder stringBuilder = new StringBuilder();
				do
				{
					stringBuilder.Append(tokens[position].Value);
					position++;
				}
				while (position < tokens.Length && tokens[position].Kind == CommandLineTokenKind.TokenPart);
				list.Add(stringBuilder.ToString());
				SkipWhitespace(tokens, ref position);
			}
			if (list.Count == 0)
			{
				return false;
			}
			command = list[0].ToLowerInvariant();
			arguments = list.GetRange(1, list.Count - 1).ToArray();
			return true;
		}

		private static void SkipWhitespace(CommandLineToken[] tokens, ref int position)
		{
			while (position < tokens.Length && tokens[position].Kind == CommandLineTokenKind.Whitespace)
			{
				position++;
			}
		}
	}
	internal enum CommandLineTokenKind
	{
		LeadingSlash,
		Whitespace,
		TokenPart
	}
	internal readonly struct CommandLineToken
	{
		internal CommandLineTokenKind Kind { get; }

		internal string Value { get; }

		internal CommandLineToken(CommandLineTokenKind kind, string value)
		{
			Kind = kind;
			Value = value;
		}
	}
	internal readonly struct RconCommandResult
	{
		private readonly string? _payload;

		internal string Payload => _payload ?? string.Empty;

		internal RconCommandResult(string? payload)
		{
			_payload = payload;
		}
	}
	internal sealed class SourceRconConnection : IDisposable
	{
		private const int ReceiveBufferSize = 4096;

		private readonly TcpClient _client;

		private readonly NetworkStream _stream;

		private readonly byte[] _receiveBuffer = new byte[4096];

		private readonly SourceRconPacketBuffer _packetBuffer = new SourceRconPacketBuffer();

		private readonly Func<SourceRconConnection, SourceRconPacket, bool> _dispatch;

		private readonly object _sessionLock = new object();

		private readonly long _connectedAt;

		private long _lastActivityAt;

		private long _rateWindowStartedAt;

		private int _packetsInWindow;

		private bool _authenticated;

		private int _disposed;

		internal IPAddress RemoteAddress { get; }

		internal bool IsClosed => Volatile.Read(in _disposed) != 0;

		internal bool IsAuthenticated
		{
			get
			{
				lock (_sessionLock)
				{
					return !IsClosed && _authenticated;
				}
			}
		}

		internal SourceRconConnection(TcpClient client, IPAddress remoteAddress, int sendTimeoutMilliseconds, Func<SourceRconConnection, SourceRconPacket, bool> dispatch)
		{
			_client = client ?? throw new ArgumentNullException("client");
			RemoteAddress = remoteAddress ?? throw new ArgumentNullException("remoteAddress");
			_dispatch = dispatch ?? throw new ArgumentNullException("dispatch");
			_client.NoDelay = true;
			_client.SendTimeout = sendTimeoutMilliseconds;
			_stream = _client.GetStream();
			_connectedAt = Stopwatch.GetTimestamp();
			_lastActivityAt = _connectedAt;
			_rateWindowStartedAt = _connectedAt;
		}

		internal async Task RunAsync(CancellationToken shutdown)
		{
			try
			{
				while (!shutdown.IsCancellationRequested && !IsClosed)
				{
					int num = await _stream.ReadAsync(_receiveBuffer, 0, _receiveBuffer.Length, shutdown).ConfigureAwait(continueOnCapturedContext: false);
					if (num == 0 || !_packetBuffer.Append(_receiveBuffer, num))
					{
						break;
					}
					while (!IsClosed)
					{
						SourceRconPacket packet;
						switch (_packetBuffer.Read(out packet))
						{
						default:
							goto IL_00cd;
						case FrameDecodeStatus.Invalid:
							return;
						case FrameDecodeStatus.NeedMoreData:
							break;
						}
						break;
						IL_00cd:
						if (!_dispatch(this, packet))
						{
							return;
						}
					}
				}
			}
			catch (OperationCanceledException) when (shutdown.IsCancellationRequested)
			{
			}
			catch (ObjectDisposedException)
			{
			}
			catch (IOException)
			{
			}
			catch (SocketException)
			{
			}
			finally
			{
				Dispose();
			}
		}

		internal bool BeginPacket(int maximumPacketsPerSecond)
		{
			long timestamp = Stopwatch.GetTimestamp();
			lock (_sessionLock)
			{
				if (IsClosed)
				{
					return false;
				}
				if (Elapsed(_rateWindowStartedAt, TimeSpan.FromSeconds(1.0), timestamp))
				{
					_rateWindowStartedAt = timestamp;
					_packetsInWindow = 0;
				}
				_packetsInWindow++;
				_lastActivityAt = timestamp;
				return _packetsInWindow <= maximumPacketsPerSecond;
			}
		}

		internal void Authenticate()
		{
			lock (_sessionLock)
			{
				if (!IsClosed)
				{
					_authenticated = true;
					_lastActivityAt = Stopwatch.GetTimestamp();
				}
			}
		}

		internal void RevokeAuthentication()
		{
			lock (_sessionLock)
			{
				_authenticated = false;
			}
		}

		internal bool IsExpired(SourceRconServerOptions options, long now)
		{
			lock (_sessionLock)
			{
				if (IsClosed)
				{
					return true;
				}
				long since = (_authenticated ? _lastActivityAt : _connectedAt);
				TimeSpan duration = (_authenticated ? options.IdleTimeout : options.AuthenticationTimeout);
				return Elapsed(since, duration, now);
			}
		}

		internal void Send(SourceRconPacket packet)
		{
			SendFrame(SourceRconPacketCodec.Encode(packet));
		}

		internal void SendResponseBoundary(int requestId)
		{
			SendFrame(SourceRconPacketCodec.EncodeResponseBoundary(requestId));
		}

		public void Dispose()
		{
			if (Interlocked.Exchange(ref _disposed, 1) == 0)
			{
				lock (_sessionLock)
				{
					_authenticated = false;
				}
				_stream.Dispose();
				_client.Close();
			}
		}

		private static bool Elapsed(long since, TimeSpan duration, long now)
		{
			return (double)(now - since) >= duration.TotalSeconds * (double)Stopwatch.Frequency;
		}

		private void SendFrame(byte[] frame)
		{
			if (IsClosed)
			{
				throw new ObjectDisposedException("SourceRconConnection");
			}
			_stream.Write(frame, 0, frame.Length);
		}
	}
	internal sealed class SourceRconServer : IDisposable
	{
		private const string InvalidCommandResponse = "Invalid command";

		private const string OversizedResponse = "Command response exceeded the configured size limit";

		private readonly int _requestedPort;

		private readonly Func<string, string[], RconCommandResult> _executeCommand;

		private readonly SourceRconServerOptions _options;

		private readonly object _lifecycleLock = new object();

		private readonly HashSet<SourceRconConnection> _connections = new HashSet<SourceRconConnection>();

		private TcpListener? _listener;

		private CancellationTokenSource? _shutdown;

		private Task? _acceptLoop;

		private byte[] _password = Array.Empty<byte>();

		private int _boundPort;

		internal int Port
		{
			get
			{
				lock (_lifecycleLock)
				{
					return _boundPort;
				}
			}
		}

		internal int AuthenticatedClientCount
		{
			get
			{
				lock (_lifecycleLock)
				{
					int num = 0;
					foreach (SourceRconConnection connection in _connections)
					{
						if (connection.IsAuthenticated)
						{
							num++;
						}
					}
					return num;
				}
			}
		}

		internal SourceRconServer(int port, Func<string, string[], RconCommandResult> commandHandler, SourceRconServerOptions? options = null)
		{
			if ((port < 0 || port > 65535) ? true : false)
			{
				throw new ArgumentOutOfRangeException("port");
			}
			_requestedPort = port;
			_executeCommand = commandHandler ?? throw new ArgumentNullException("commandHandler");
			_options = options ?? new SourceRconServerOptions();
		}

		internal void Start(string password)
		{
			byte[] password2 = NormalizePassword(password);
			lock (_lifecycleLock)
			{
				if (_listener != null)
				{
					throw new InvalidOperationException("The Source RCON server is already running.");
				}
				TcpListener tcpListener = new TcpListener(IPAddress.Any, _requestedPort);
				tcpListener.Start(_options.MaximumConnections);
				CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
				_listener = tcpListener;
				_shutdown = cancellationTokenSource;
				_password = password2;
				_boundPort = ((IPEndPoint)tcpListener.LocalEndpoint).Port;
				_acceptLoop = AcceptConnectionsAsync(tcpListener, cancellationTokenSource.Token);
			}
		}

		internal void Cleanup()
		{
			long timestamp = Stopwatch.GetTimestamp();
			List<SourceRconConnection> list = new List<SourceRconConnection>();
			lock (_lifecycleLock)
			{
				foreach (SourceRconConnection connection in _connections)
				{
					if (connection.IsExpired(_options, timestamp))
					{
						list.Add(connection);
					}
				}
			}
			foreach (SourceRconConnection item in list)
			{
				Disconnect(item);
			}
		}

		internal void Close()
		{
			TcpListener listener;
			CancellationTokenSource shutdown;
			SourceRconConnection[] array;
			lock (_lifecycleLock)
			{
				listener = _listener;
				shutdown = _shutdown;
				array = new SourceRconConnection[_connections.Count];
				_connections.CopyTo(array);
				_connections.Clear();
				_listener = null;
				_shutdown = null;
				_acceptLoop = null;
				_password = Array.Empty<byte>();
			}
			shutdown?.Cancel();
			listener?.Stop();
			SourceRconConnection[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				array2[i].Dispose();
			}
			shutdown?.Dispose();
		}

		public void Dispose()
		{
			Close();
		}

		private async Task AcceptConnectionsAsync(TcpListener listener, CancellationToken shutdown)
		{
			while (!shutdown.IsCancellationRequested)
			{
				TcpClient client;
				try
				{
					client = await listener.AcceptTcpClientAsync().ConfigureAwait(continueOnCapturedContext: false);
				}
				catch (ObjectDisposedException) when (shutdown.IsCancellationRequested)
				{
					break;
				}
				catch (SocketException) when (shutdown.IsCancellationRequested)
				{
					break;
				}
				catch (SocketException)
				{
					await DelayAfterAcceptFailure(shutdown).ConfigureAwait(continueOnCapturedContext: false);
					continue;
				}
				Admit(listener, client, shutdown);
			}
		}

		private void Admit(TcpListener listener, TcpClient client, CancellationToken shutdown)
		{
			if (!(client.Client.RemoteEndPoint is IPEndPoint iPEndPoint))
			{
				client.Close();
				return;
			}
			SourceRconConnection sourceRconConnection = null;
			lock (_lifecycleLock)
			{
				if (_listener == listener && CanAccept(iPEndPoint.Address))
				{
					sourceRconConnection = new SourceRconConnection(client, iPEndPoint.Address, _options.SendTimeoutMilliseconds, Dispatch);
					_connections.Add(sourceRconConnection);
				}
			}
			if (sourceRconConnection == null)
			{
				client.Close();
			}
			else
			{
				ObserveConnectionAsync(sourceRconConnection, shutdown);
			}
		}

		private bool CanAccept(IPAddress address)
		{
			if (_connections.Count >= _options.MaximumConnections)
			{
				return false;
			}
			int num = 0;
			foreach (SourceRconConnection connection in _connections)
			{
				if (!connection.IsClosed && connection.RemoteAddress.Equals(address))
				{
					num++;
				}
			}
			return num < _options.MaximumConnectionsPerAddress;
		}

		private async Task ObserveConnectionAsync(SourceRconConnection connection, CancellationToken shutdown)
		{
			try
			{
				await connection.RunAsync(shutdown).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch
			{
			}
			finally
			{
				Disconnect(connection);
			}
		}

		private bool Dispatch(SourceRconConnection connection, SourceRconPacket packet)
		{
			if (!connection.BeginPacket(_options.MaximumPacketsPerSecond))
			{
				return false;
			}
			return (int)packet.Type switch
			{
				3 => Authenticate(connection, packet), 
				2 => Execute(connection, packet), 
				0 => RespondToBoundaryProbe(connection, packet), 
				_ => false, 
			};
		}

		private bool Authenticate(SourceRconConnection connection, SourceRconPacket packet)
		{
			connection.Send(new SourceRconPacket(packet.RequestId, SourceRconPacketType.CommandResponse, string.Empty));
			if (!PasswordMatches(packet.Payload))
			{
				connection.RevokeAuthentication();
				connection.Send(new SourceRconPacket(-1, SourceRconPacketType.Command, string.Empty));
				return false;
			}
			connection.Authenticate();
			connection.Send(new SourceRconPacket(packet.RequestId, SourceRconPacketType.Command, string.Empty));
			return true;
		}

		private bool Execute(SourceRconConnection connection, SourceRconPacket packet)
		{
			if (!connection.IsAuthenticated)
			{
				connection.Send(new SourceRconPacket(-1, SourceRconPacketType.Command, string.Empty));
				return false;
			}
			if (!CommandLineParser.TryParse(packet.Payload, out string command, out string[] arguments))
			{
				SendResponse(connection, packet.RequestId, "Invalid command");
				return true;
			}
			string text = _executeCommand(command, arguments).Payload;
			if (text.Length > _options.MaximumResponseLength)
			{
				text = "Command response exceeded the configured size limit";
			}
			else if (text.IndexOf('\0') >= 0)
			{
				text = "Command response contained an invalid null character";
			}
			SendResponse(connection, packet.RequestId, text);
			return true;
		}

		private static bool RespondToBoundaryProbe(SourceRconConnection connection, SourceRconPacket packet)
		{
			if (!connection.IsAuthenticated)
			{
				connection.Send(new SourceRconPacket(-1, SourceRconPacketType.Command, string.Empty));
				return false;
			}
			if (packet.Payload.Length != 0)
			{
				return false;
			}
			connection.Send(new SourceRconPacket(packet.RequestId, SourceRconPacketType.CommandResponse, string.Empty));
			connection.SendResponseBoundary(packet.RequestId);
			return true;
		}

		private static void SendResponse(SourceRconConnection connection, int requestId, string response)
		{
			if (response.Length == 0)
			{
				connection.Send(new SourceRconPacket(requestId, SourceRconPacketType.CommandResponse, string.Empty));
				return;
			}
			int num;
			for (int i = 0; i < response.Length; i += num)
			{
				num = Math.Min(4086, response.Length - i);
				connection.Send(new SourceRconPacket(requestId, SourceRconPacketType.CommandResponse, response.Substring(i, num)));
			}
		}

		private bool PasswordMatches(string candidate)
		{
			for (int i = 0; i < candidate.Length; i++)
			{
				if (candidate[i] > '\u007f')
				{
					return false;
				}
			}
			byte[] bytes = Encoding.ASCII.GetBytes(candidate);
			int num = bytes.Length ^ _password.Length;
			int num2 = Math.Max(bytes.Length, _password.Length);
			for (int j = 0; j < num2; j++)
			{
				byte b = (byte)((j < bytes.Length) ? bytes[j] : 0);
				byte b2 = (byte)((j < _password.Length) ? _password[j] : 0);
				num |= b ^ b2;
			}
			return num == 0;
		}

		private static byte[] NormalizePassword(string password)
		{
			if (password == null)
			{
				throw new ArgumentNullException("password");
			}
			if (string.IsNullOrWhiteSpace(password))
			{
				throw new ArgumentException("The Source RCON password cannot be empty.", "password");
			}
			foreach (char c in password)
			{
				if ((c < ' ' || c > '~') ? true : false)
				{
					throw new ArgumentException("The Source RCON password must use printable ASCII characters.", "password");
				}
			}
			return Encoding.ASCII.GetBytes(password);
		}

		private void Disconnect(SourceRconConnection connection)
		{
			lock (_lifecycleLock)
			{
				_connections.Remove(connection);
			}
			connection.Dispose();
		}

		private static async Task DelayAfterAcceptFailure(CancellationToken shutdown)
		{
			try
			{
				await Task.Delay(100, shutdown).ConfigureAwait(continueOnCapturedContext: false);
			}
			catch (OperationCanceledException)
			{
			}
		}
	}
	internal sealed class SourceRconServerOptions
	{
		internal int MaximumConnections { get; }

		internal int MaximumConnectionsPerAddress { get; }

		internal int MaximumPacketsPerSecond { get; }

		internal int MaximumResponseLength { get; }

		internal TimeSpan AuthenticationTimeout { get; }

		internal TimeSpan IdleTimeout { get; }

		internal TimeSpan SendTimeout { get; }

		internal int SendTimeoutMilliseconds => checked((int)Math.Ceiling(SendTimeout.TotalMilliseconds));

		internal SourceRconServerOptions(int maximumConnections = 16, int maximumConnectionsPerAddress = 4, int maximumPacketsPerSecond = 30, int maximumResponseLength = 65536, TimeSpan? authenticationTimeout = null, TimeSpan? idleTimeout = null, TimeSpan? sendTimeout = null)
		{
			MaximumConnections = Positive(maximumConnections, "maximumConnections");
			MaximumConnectionsPerAddress = Positive(maximumConnectionsPerAddress, "maximumConnectionsPerAddress");
			MaximumPacketsPerSecond = Positive(maximumPacketsPerSecond, "maximumPacketsPerSecond");
			MaximumResponseLength = Positive(maximumResponseLength, "maximumResponseLength");
			if (MaximumConnectionsPerAddress > MaximumConnections)
			{
				throw new ArgumentOutOfRangeException("maximumConnectionsPerAddress", "The per-address limit cannot exceed the global connection limit.");
			}
			AuthenticationTimeout = Positive(authenticationTimeout ?? TimeSpan.FromSeconds(10.0), "authenticationTimeout");
			IdleTimeout = Positive(idleTimeout ?? TimeSpan.FromMinutes(10.0), "idleTimeout");
			SendTimeout = Positive(sendTimeout ?? TimeSpan.FromSeconds(5.0), "sendTimeout");
			if (SendTimeout.TotalMilliseconds > 2147483647.0)
			{
				throw new ArgumentOutOfRangeException("sendTimeout");
			}
		}

		private static int Positive(int value, string parameterName)
		{
			if (value <= 0)
			{
				throw new ArgumentOutOfRangeException(parameterName);
			}
			return value;
		}

		private static TimeSpan Positive(TimeSpan value, string parameterName)
		{
			if (!(value > TimeSpan.Zero))
			{
				throw new ArgumentOutOfRangeException(parameterName);
			}
			return value;
		}
	}
}