Decompiled source of TerminalExtras v1.0.1

BepInEx/plugins/LethalAPI.TerminalCommands.dll

Decompiled a year ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using LethalAPI.TerminalCommands.Attributes;
using LethalAPI.TerminalCommands.Commands;
using LethalAPI.TerminalCommands.Configs;
using LethalAPI.TerminalCommands.Models;
using LethalAPI.TerminalCommands.Patches;
using Microsoft.CodeAnalysis;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ShimmyMySherbet, LethalAPI Modding Team")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("A library that allows the creation of custom terminal commands for Lethal Company mods")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0+5290c6d02b20cc3e4c96dd09d0f7234c78366019")]
[assembly: AssemblyProduct("LethalAPI.TerminalCommands")]
[assembly: AssemblyTitle("LethalAPI.TerminalCommands")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/LethalCompany/LethalAPI.TerminalCommands")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace LethalAPI.TerminalCommands
{
	[HarmonyPatch(typeof(Terminal), "ParsePlayerSentence")]
	public static class ParseSentencePatch
	{
		[HarmonyPrefix]
		public static bool ParsePrefix(Terminal __instance, ref TerminalNode __state)
		{
			__state = null;
			string command = __instance.screenText.text.Substring(__instance.screenText.text.Length - __instance.textAdded);
			__state = CommandHandler.TryExecute(command, __instance);
			return (Object)(object)__state == (Object)null;
		}

		[HarmonyPostfix]
		public static TerminalNode ParsePostfix(TerminalNode __result, TerminalNode __state, Terminal __instance)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Invalid comparison between Unknown and I4
			if ((Object)(object)__state != (Object)null)
			{
				TerminalSubmitPatch.LastNode = __state;
				return __state;
			}
			if ((int)__instance.videoPlayer.source == 1)
			{
				__instance.videoPlayer.source = (VideoSource)0;
			}
			TerminalSubmitPatch.LastNode = __result;
			return __result;
		}
	}
	[BepInPlugin("LethalAPI.TerminalCommands", "LethalAPI.TerminalCommands", "1.1.0")]
	public class TerminalCommandsPlugin : BaseUnityPlugin
	{
		private Harmony HarmonyInstance = new Harmony("LethalAPI.TerminalCommands");

		private TerminalModRegistry Terminal;

		private TerminalConfig TerminalConfig;

		private void Awake()
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)"LethalAPI.TerminalCommands is loading...");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Installing patches");
			HarmonyInstance.PatchAll(typeof(TerminalCommandsPlugin).Assembly);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Registering built-in Commands");
			Terminal = TerminalRegistry.CreateTerminalRegistry();
			Terminal.RegisterFrom<CommandInfoCommands>();
			TerminalConfig = Terminal.RegisterFrom<TerminalConfig>();
			Object.DontDestroyOnLoad((Object)(object)this);
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin LethalAPI.TerminalCommands is loaded!");
		}
	}
	public static class TerminalExtensions
	{
		public static void PlayVideoFile(this Terminal terminal, string filePath)
		{
			string url = "file:///" + filePath.Replace('\\', '/');
			terminal.PlayVideoLink(url);
		}

		public static void PlayVideoLink(this Terminal terminal, Uri url)
		{
			((MonoBehaviour)terminal).StartCoroutine(PlayVideoLink(url.AbsoluteUri, terminal));
		}

		public static void PlayVideoLink(this Terminal terminal, string url)
		{
			((MonoBehaviour)terminal).StartCoroutine(PlayVideoLink(url, terminal));
		}

		private static IEnumerator PlayVideoLink(string url, Terminal terminal)
		{
			yield return (object)new WaitForFixedUpdate();
			((Behaviour)terminal.terminalImage).enabled = true;
			terminal.terminalImage.texture = (Texture)(object)terminal.videoTexture;
			terminal.displayingPersistentImage = null;
			terminal.videoPlayer.clip = null;
			terminal.videoPlayer.source = (VideoSource)1;
			terminal.videoPlayer.url = url;
			((Behaviour)terminal.videoPlayer).enabled = true;
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "LethalAPI.TerminalCommands";

		public const string PLUGIN_NAME = "LethalAPI.TerminalCommands";

		public const string PLUGIN_VERSION = "1.1.0";
	}
}
namespace LethalAPI.TerminalCommands.Patches
{
	[HarmonyPatch(typeof(Terminal), "selectTextFieldDelayed")]
	public static class SelectTextFieldPatch
	{
		[HarmonyPrefix]
		public static bool Prefix()
		{
			return false;
		}

		[HarmonyPostfix]
		public static void Postfix(Terminal __instance, ref IEnumerator __result)
		{
			__result = Patch(__instance);
		}

		private static IEnumerator Patch(Terminal terminal)
		{
			yield return (object)new WaitForSeconds(0.2f);
			terminal.screenText.ActivateInputField();
			((Selectable)terminal.screenText).Select();
		}
	}
	[HarmonyPatch(typeof(Terminal), "OnSubmit")]
	public static class TerminalSubmitPatch
	{
		private static ManualLogSource m_LogSource = new ManualLogSource("LethalAPI.TerminalCommands");

		public static TerminalNode LastNode { get; set; }

		[HarmonyPrefix]
		public static void Prefix()
		{
			LastNode = null;
		}

		[HarmonyTranspiler]
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			CodeInstruction[] array = instructions.ToArray();
			for (int num = array.Length - 1; num >= 0; num--)
			{
				if (!(array[num].opcode != OpCodes.Callvirt))
				{
					if (array[num + 1].opcode != OpCodes.Ldarg_0)
					{
						ReportTranspileError("Ldarg_0 expected after final callVirt, not found");
						return array;
					}
					array[num + 1] = new CodeInstruction(OpCodes.Ret, (object)null);
					return array;
				}
			}
			ReportTranspileError("Failed to find Callvirt in backward scan");
			return array;
		}

		private static void ReportTranspileError(string message)
		{
			m_LogSource.LogError((object)("Failed to transpile OnSubmit to remove Scroll To Bottom. Did the method get modified in an update? (" + message + ")"));
			m_LogSource.LogWarning((object)"This won't break the mod, but it will cause some odd terminal scrolling behavior");
		}

		[HarmonyPostfix]
		public static void Postfix(Terminal __instance, ref Coroutine ___forceScrollbarCoroutine)
		{
			if ((Object)(object)LastNode == (Object)null || LastNode.clearPreviousText)
			{
				ExecuteScrollCoroutine(__instance, ref ___forceScrollbarCoroutine);
			}
			else
			{
				((MonoBehaviour)__instance).StartCoroutine("forceScrollbarDown");
			}
		}

		private static void ExecuteScrollCoroutine(Terminal terminal, ref Coroutine forceScrollbarCoroutine)
		{
			if (forceScrollbarCoroutine != null)
			{
				((MonoBehaviour)terminal).StopCoroutine(forceScrollbarCoroutine);
			}
			forceScrollbarCoroutine = ((MonoBehaviour)terminal).StartCoroutine("forceScrollbarUp");
		}
	}
	[HarmonyPatch(typeof(Terminal), "TextPostProcess")]
	public static class TextPostProcessPatch
	{
		[HarmonyPrefix]
		public static void Prefix(ref string modifiedDisplayText)
		{
			modifiedDisplayText = modifiedDisplayText.TrimStart('\n', ' ');
			if (!modifiedDisplayText.EndsWith('\n'))
			{
				modifiedDisplayText += "\n";
			}
		}
	}
}
namespace LethalAPI.TerminalCommands.Models
{
	public enum AllowedCaller
	{
		None = -1,
		Player,
		Host
	}
	public class ArgumentStream
	{
		public string[] Arguments { get; }

		public int Index { get; set; }

		public bool EndOfStream => Index >= Arguments.Length;

		public ArgumentStream(string[] arguments)
		{
			Arguments = arguments;
		}

		public void Reset()
		{
			Index = 0;
		}

		public bool TryReadNext(Type type, out object value)
		{
			if (EndOfStream)
			{
				value = null;
				return false;
			}
			return StringConverter.TryConvert(Arguments[Index++], type, out value);
		}

		public bool TryReadRemaining(out string result)
		{
			if (EndOfStream)
			{
				result = null;
				return false;
			}
			result = string.Join(" ", Arguments.Skip(Index));
			return true;
		}

		public bool TryReadNext(out string value)
		{
			value = string.Empty;
			if (EndOfStream)
			{
				return false;
			}
			value = Arguments[Index++];
			return true;
		}

		public bool TryReadNext(out sbyte value)
		{
			value = 0;
			if (EndOfStream)
			{
				return false;
			}
			return sbyte.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out byte value)
		{
			value = 0;
			if (EndOfStream)
			{
				return false;
			}
			return byte.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out short value)
		{
			value = 0;
			if (EndOfStream)
			{
				return false;
			}
			return short.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out ushort value)
		{
			value = 0;
			if (EndOfStream)
			{
				return false;
			}
			return ushort.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out int value)
		{
			value = 0;
			if (EndOfStream)
			{
				return false;
			}
			return int.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out uint value)
		{
			value = 0u;
			if (EndOfStream)
			{
				return false;
			}
			return uint.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out long value)
		{
			value = 0L;
			if (EndOfStream)
			{
				return false;
			}
			return long.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out ulong value)
		{
			value = 0uL;
			if (EndOfStream)
			{
				return false;
			}
			return ulong.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out float value)
		{
			value = 0f;
			if (EndOfStream)
			{
				return false;
			}
			return float.TryParse(Arguments[Index++], out value);
		}

		public bool TryReadNext(out double value)
		{
			value = 0.0;
			if (EndOfStream)
			{
				return false;
			}
			return double.TryParse(Arguments[Index++], out value);
		}
	}
	public class CommandComparer : IComparer<TerminalCommand>
	{
		public int Compare(TerminalCommand x, TerminalCommand y)
		{
			if (x.Priority > y.Priority)
			{
				return 1;
			}
			if (x.Priority < y.Priority)
			{
				return -1;
			}
			return x.ArgumentCount.CompareTo(y.ArgumentCount);
		}
	}
	public static class CommandHandler
	{
		private static readonly Regex m_SplitRegex = new Regex("[\\\"](.+?)[\\\"]|([^ ]+)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled);

		private static readonly CommandComparer m_Comparer = new CommandComparer();

		public static TerminalNode TryExecute(string command, Terminal terminal)
		{
			IEnumerable<string> source = from Match x in m_SplitRegex.Matches(command.Trim())
				select x.Value.Trim('"', ' ');
			string commandName = source.First();
			string[] arguments = source.Skip(1).ToArray();
			List<(TerminalCommand, Func<TerminalNode>)> list = new List<(TerminalCommand, Func<TerminalNode>)>();
			TerminalCommand[] array = TerminalRegistry.GetCommands(commandName).ToArray();
			foreach (TerminalCommand terminalCommand in array)
			{
				if (terminalCommand.CheckAllowed() && terminalCommand.TryCreateInvoker(arguments, terminal, out var invoker))
				{
					list.Add((terminalCommand, invoker));
				}
			}
			foreach (var item in list.OrderByDescending<(TerminalCommand, Func<TerminalNode>), TerminalCommand>(((TerminalCommand command, Func<TerminalNode> invoker) x) => x.command, m_Comparer))
			{
				TerminalNode val = item.Item2();
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}
	}
	public static class DefaultStringConverters
	{
		[StringConverter]
		public static string ParseString(string input)
		{
			return input;
		}

		[StringConverter]
		public static sbyte ParseSByte(string input)
		{
			if (sbyte.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static byte ParseByte(string input)
		{
			if (byte.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static short ParseShort(string input)
		{
			if (short.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static ushort ParseUShort(string input)
		{
			if (ushort.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static int ParseInt(string input)
		{
			if (int.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static uint ParseUInt(string input)
		{
			if (uint.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static long ParseLong(string input)
		{
			if (long.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static ulong ParseULong(string input)
		{
			if (ulong.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static float ParseFloat(string input)
		{
			if (float.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static double ParseDouble(string input)
		{
			if (double.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		[StringConverter]
		public static decimal ParseDecimal(string input)
		{
			if (decimal.TryParse(input, out var result))
			{
				return result;
			}
			throw new ArgumentException();
		}

		public static PlayerControllerB ParsePlayerControllerB(string value)
		{
			if ((Object)(object)StartOfRound.Instance == (Object)null)
			{
				throw new ArgumentException("Game has not started");
			}
			PlayerControllerB val = null;
			if (ulong.TryParse(value, out var steamID))
			{
				val = ((IEnumerable<PlayerControllerB>)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func<PlayerControllerB, bool>)((PlayerControllerB x) => x.playerSteamId == steamID));
			}
			if ((Object)(object)val == (Object)null)
			{
				val = ((IEnumerable<PlayerControllerB>)StartOfRound.Instance.allPlayerScripts).FirstOrDefault((Func<PlayerControllerB, bool>)((PlayerControllerB x) => x.playerUsername.IndexOf(value, StringComparison.InvariantCultureIgnoreCase) != -1));
			}
			if ((Object)(object)val == (Object)null)
			{
				throw new ArgumentException("Failed to find player");
			}
			return val;
		}
	}
	public enum PersistType
	{
		LocalPlayer,
		Host,
		Save
	}
	public delegate object StringConversionHandler(string value);
	public static class StringConverter
	{
		private static bool m_Initialized = false;

		public static ConcurrentDictionary<Type, StringConversionHandler> StringConverters { get; } = new ConcurrentDictionary<Type, StringConversionHandler>();


		public static bool TryConvert(string value, Type type, out object result)
		{
			if (!m_Initialized)
			{
				m_Initialized = true;
				RegisterFromType(typeof(DefaultStringConverters), null, replaceExisting: false);
			}
			if (!StringConverters.TryGetValue(type, out var value2))
			{
				result = null;
				return false;
			}
			try
			{
				result = value2(value);
				return true;
			}
			catch (ArgumentException)
			{
			}
			result = null;
			return false;
		}

		public static void RegisterFrom<T>(T instance, bool replaceExisting = true) where T : class
		{
			RegisterFromType(typeof(T), instance, replaceExisting);
		}

		public static void RegisterFromType(Type type, object instance = null, bool replaceExisting = true)
		{
			MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo method in methods)
			{
				if (method.GetCustomAttribute<StringConverterAttribute>() == null)
				{
					continue;
				}
				ParameterInfo[] parameters = method.GetParameters();
				if (parameters.Length == 1 && !(parameters[0].ParameterType != typeof(string)))
				{
					Type returnType = method.ReturnType;
					StringConversionHandler value2 = (string value) => method.Invoke(instance, new object[1] { value });
					if (replaceExisting || !StringConverters.ContainsKey(returnType))
					{
						StringConverters[returnType] = value2;
					}
				}
			}
		}
	}
	public class TerminalCommand
	{
		private ManualLogSource m_LogSource = new ManualLogSource("LethalAPI.TerminalCommands");

		public string Name { get; }

		public MethodInfo Method { get; }

		public object Instance { get; }

		public bool ClearConsole { get; }

		public int ArgumentCount { get; }

		public string Syntax { get; }

		public string Description { get; }

		public int Priority { get; }

		public TerminalCommand(string name, MethodInfo method, object instance, bool clearConsole, string syntax = null, string description = null, int priority = 0)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Expected O, but got Unknown
			Name = name;
			Method = method;
			Instance = instance;
			ClearConsole = clearConsole;
			ArgumentCount = method.GetParameters().Length;
			Syntax = syntax;
			Description = description;
			Priority = priority;
		}

		public bool CheckAllowed()
		{
			foreach (AccessControlAttribute customAttribute in Method.GetCustomAttributes<AccessControlAttribute>())
			{
				if (!customAttribute.CheckAllowed())
				{
					return false;
				}
			}
			return true;
		}

		public static TerminalCommand FromMethod(MethodInfo info, object instance, string overrideName = null)
		{
			bool clearConsole = false;
			string syntax = null;
			string description = null;
			string text = overrideName;
			int priority = 0;
			TerminalCommandAttribute customAttribute = info.GetCustomAttribute<TerminalCommandAttribute>();
			if (customAttribute != null)
			{
				text = text ?? customAttribute.CommandName;
				clearConsole = customAttribute.ClearText;
			}
			CommandInfoAttribute customAttribute2 = info.GetCustomAttribute<CommandInfoAttribute>();
			if (customAttribute2 != null)
			{
				syntax = customAttribute2.Syntax;
				description = customAttribute2.Description;
			}
			CommandPriority customAttribute3 = info.GetCustomAttribute<CommandPriority>();
			if (customAttribute3 != null)
			{
				priority = customAttribute3.Priority;
			}
			return new TerminalCommand(text, info, instance, clearConsole, syntax, description, priority);
		}

		public bool TryCreateInvoker(string[] arguments, Terminal terminal, out Func<TerminalNode> invoker)
		{
			ParameterInfo[] parameters = Method.GetParameters();
			object[] values = new object[parameters.Length];
			ArgumentStream argumentStream = new ArgumentStream(arguments);
			invoker = null;
			for (int i = 0; i < parameters.Length; i++)
			{
				ParameterInfo parameterInfo = parameters[i];
				Type parameterType = parameterInfo.ParameterType;
				if (parameterType == typeof(Terminal))
				{
					values[i] = terminal;
				}
				else if (parameterType == typeof(ArgumentStream))
				{
					values[i] = argumentStream;
				}
				else if (parameterType == typeof(string[]))
				{
					values[i] = arguments;
				}
				else if (parameterType == typeof(string) && parameterInfo.GetCustomAttribute<RemainingTextAttribute>() != null)
				{
					if (!argumentStream.TryReadRemaining(out var result))
					{
						return false;
					}
					values[i] = result;
				}
				else
				{
					if (!argumentStream.TryReadNext(parameterType, out var value))
					{
						return false;
					}
					values[i] = value;
				}
			}
			argumentStream.Reset();
			invoker = () => ExecuteCommand(values);
			return true;
		}

		private TerminalNode ExecuteCommand(object[] arguments)
		{
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Expected O, but got Unknown
			object obj;
			try
			{
				obj = Method.Invoke(Instance, arguments);
			}
			catch (Exception ex)
			{
				m_LogSource.LogError((object)("Error caught while invoking command hander: " + ex.Message));
				m_LogSource.LogError((object)ex.StackTrace);
				return null;
			}
			if (obj == null)
			{
				return null;
			}
			Type type = obj.GetType();
			if (!typeof(TerminalNode).IsAssignableFrom(type))
			{
				TerminalNode obj2 = ScriptableObject.CreateInstance<TerminalNode>();
				obj2.displayText = obj.ToString() + "\n";
				obj2.clearPreviousText = ClearConsole;
				return obj2;
			}
			return (TerminalNode)obj;
		}
	}
	public class TerminalModRegistry
	{
		public List<TerminalCommand> Commands { get; } = new List<TerminalCommand>();


		public T RegisterFrom<T>() where T : class, new()
		{
			return RegisterFrom(new T());
		}

		public T RegisterFrom<T>(T instance) where T : class
		{
			foreach (MethodInfo commandMethod in TerminalRegistry.GetCommandMethods<T>())
			{
				TerminalCommand terminalCommand = TerminalCommand.FromMethod(commandMethod, instance);
				TerminalRegistry.RegisterCommand(terminalCommand);
				lock (Commands)
				{
					Commands.Add(terminalCommand);
				}
			}
			StringConverter.RegisterFrom(instance);
			return instance;
		}

		public void Deregister()
		{
			if (Commands != null)
			{
				for (int i = 0; i < Commands.Count; i++)
				{
					TerminalRegistry.Deregister(Commands[i]);
				}
			}
		}
	}
	public class TerminalRegistry
	{
		private static readonly ConcurrentDictionary<string, List<TerminalCommand>> m_RegisteredCommands = new ConcurrentDictionary<string, List<TerminalCommand>>(StringComparer.InvariantCultureIgnoreCase);

		public static TerminalModRegistry RegisterFrom<T>(T instance) where T : class
		{
			TerminalModRegistry terminalModRegistry = new TerminalModRegistry();
			foreach (MethodInfo commandMethod in GetCommandMethods<T>())
			{
				TerminalCommand terminalCommand = TerminalCommand.FromMethod(commandMethod, instance);
				RegisterCommand(terminalCommand);
				terminalModRegistry.Commands.Add(terminalCommand);
			}
			StringConverter.RegisterFrom(instance);
			return terminalModRegistry;
		}

		public static TerminalModRegistry CreateTerminalRegistry()
		{
			return new TerminalModRegistry();
		}

		public static void RegisterCommand(TerminalCommand command)
		{
			if (!m_RegisteredCommands.TryGetValue(command.Name, out var value))
			{
				value = new List<TerminalCommand>();
				m_RegisteredCommands[command.Name] = value;
			}
			lock (value)
			{
				value.Add(command);
			}
		}

		public static void Deregister(TerminalCommand command)
		{
			if (!m_RegisteredCommands.TryGetValue(command.Name, out var value))
			{
				return;
			}
			lock (value)
			{
				value.Remove(command);
			}
		}

		public static IReadOnlyList<TerminalCommand> GetCommands(string commandName)
		{
			if (m_RegisteredCommands.TryGetValue(commandName, out var value))
			{
				return value;
			}
			return new List<TerminalCommand>();
		}

		public static IEnumerable<TerminalCommand> EnumerateCommands(string name)
		{
			if (!m_RegisteredCommands.TryGetValue(name, out var value))
			{
				return Enumerable.Empty<TerminalCommand>();
			}
			return value;
		}

		public static IEnumerable<TerminalCommand> EnumerateCommands()
		{
			string[] keys = m_RegisteredCommands.Keys.ToArray();
			for (int i = 0; i < keys.Length; i++)
			{
				List<TerminalCommand> overloads = m_RegisteredCommands[keys[i]];
				for (int c = 0; c < overloads.Count; c++)
				{
					yield return overloads[c];
				}
			}
		}

		public static IEnumerable<MethodInfo> GetCommandMethods<T>()
		{
			MethodInfo[] methods = typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.GetCustomAttribute<TerminalCommandAttribute>() != null)
				{
					yield return methodInfo;
				}
			}
		}
	}
}
namespace LethalAPI.TerminalCommands.Configs
{
	[ConfigGroup("Terminal", "Configure the behavior of the terminal")]
	public class TerminalConfig
	{
		[TerminalConfig("Enables/Disables terminal verb commands", null)]
		[ConfigPersist(PersistType.LocalPlayer, null)]
		public bool VerbsEnabled { get; set; }

		[TerminalConfig("Specifies if the Confirm/Deny pop-up should be shown", null)]
		[ConfigPersist(PersistType.LocalPlayer, null)]
		public bool AutoConfirm { get; set; }
	}
}
namespace LethalAPI.TerminalCommands.Commands
{
	public class CommandInfoCommands
	{
		[TerminalCommand("Other", true)]
		public string CommandList()
		{
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("Other commands:");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(">VIEW MONITOR");
			stringBuilder.AppendLine("To toggle on/off the main monitor's map cam");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(">SWITCH {RADAR}");
			stringBuilder.AppendLine("To switch the player view on the main monitor");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(">PING [Radar booster name]");
			stringBuilder.AppendLine("To switch the player view on the main monitor");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine(">SCAN");
			stringBuilder.AppendLine("To scan for the number of items left on the current planet");
			stringBuilder.AppendLine();
			foreach (TerminalCommand item in TerminalRegistry.EnumerateCommands())
			{
				if (item.Description != null && item.CheckAllowed())
				{
					stringBuilder.AppendLine(">" + item.Name.ToUpper() + " " + item.Syntax?.ToUpper());
					stringBuilder.AppendLine(item.Description);
					stringBuilder.AppendLine();
				}
			}
			return stringBuilder.ToString();
		}

		[TerminalCommand("Help", false)]
		[CommandInfo("Shows further information about a command", "[Command]")]
		public string HelpCommand(string name)
		{
			StringBuilder stringBuilder = new StringBuilder();
			TerminalCommand[] array = TerminalRegistry.EnumerateCommands(name).ToArray();
			if (array.Length == 0)
			{
				return "Unknown command: '" + name + "'";
			}
			TerminalCommand[] array2 = array;
			foreach (TerminalCommand terminalCommand in array2)
			{
				stringBuilder.AppendLine(">" + terminalCommand.Name.ToUpper() + " " + terminalCommand.Syntax?.ToUpper());
				stringBuilder.AppendLine(terminalCommand.Description);
				if (!terminalCommand.CheckAllowed())
				{
					stringBuilder.AppendLine("[Host Only]");
				}
				stringBuilder.AppendLine();
			}
			return stringBuilder.ToString();
		}
	}
}
namespace LethalAPI.TerminalCommands.Attributes
{
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
	public abstract class AccessControlAttribute : Attribute
	{
		public abstract bool CheckAllowed();
	}
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
	public class AllowedCallerAttribute : AccessControlAttribute
	{
		public AllowedCaller Caller { get; }

		public AllowedCallerAttribute(AllowedCaller caller)
		{
			Caller = caller;
		}

		public override bool CheckAllowed()
		{
			switch (Caller)
			{
			case AllowedCaller.None:
				return false;
			case AllowedCaller.Player:
				return true;
			case AllowedCaller.Host:
				if ((Object)(object)StartOfRound.Instance == (Object)null)
				{
					return false;
				}
				return ((NetworkBehaviour)StartOfRound.Instance).IsHost;
			default:
				return true;
			}
		}
	}
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
	public class CommandInfoAttribute : Attribute
	{
		public string Syntax { get; }

		public string Description { get; }

		public CommandInfoAttribute(string description, string syntax = "")
		{
			Syntax = syntax;
			Description = description;
		}
	}
	public sealed class CommandPriority : Attribute
	{
		public int Priority { get; }

		public CommandPriority(int priority)
		{
			Priority = priority;
		}
	}
	[AttributeUsage(AttributeTargets.Class)]
	public sealed class ConfigGroupAttribute : Attribute
	{
		public string Name { get; }

		public string Description { get; }

		public ConfigGroupAttribute(string name, string description)
		{
			Name = name;
			Description = description;
		}
	}
	public class ConfigPersistAttribute : Attribute
	{
		public PersistType PersistType { get; }

		public string ConfigPath { get; }

		public ConfigPersistAttribute(PersistType persistType, string configPath = null)
		{
			PersistType = persistType;
			ConfigPath = configPath;
		}
	}
	[AttributeUsage(AttributeTargets.Parameter)]
	public sealed class RemainingTextAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method)]
	public sealed class StringConverterAttribute : Attribute
	{
	}
	[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
	public class TerminalCommandAttribute : Attribute
	{
		public string CommandName { get; }

		public bool ClearText { get; }

		public TerminalCommandAttribute(string name, bool clearText = false)
		{
			CommandName = name;
			ClearText = clearText;
		}
	}
	[AttributeUsage(AttributeTargets.Property)]
	public class TerminalConfigAttribute : Attribute
	{
		public string Name { get; }

		public string Description { get; }

		public TerminalConfigAttribute(string description, string name = null)
		{
			Name = name;
			Description = description;
		}
	}
}

BepInEx/plugins/TerminalExtras.dll

Decompiled a year ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using GameNetcodeStuff;
using LethalAPI.TerminalCommands.Attributes;
using LethalAPI.TerminalCommands.Models;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Events;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")]
[assembly: AssemblyCompany("TerminalExtras")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+32be330519c446b28ad5234b103989d788463a62")]
[assembly: AssemblyProduct("TerminalExtras")]
[assembly: AssemblyTitle("TerminalExtras")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TerminalExtras
{
	public static class PluginInfo
	{
		public const string GUID = "twig.terminalextras";

		public const string Name = "Terminal Extras";

		public const string Version = "1.0.1";
	}
	[BepInPlugin("twig.terminalextras", "Terminal Extras", "1.0.1")]
	internal class Plugin : BaseUnityPlugin
	{
		private TerminalModRegistry commands;

		public void Awake()
		{
			commands = TerminalRegistry.RegisterFrom<ExtraCommands>(new ExtraCommands());
		}
	}
	public class ExtraCommands
	{
		[TerminalCommand("Door", false)]
		[CommandInfo("Toggles the door.", "")]
		public string DoorCommand()
		{
			InteractTrigger componentInChildren = GameObject.Find(StartOfRound.Instance.hangarDoorsClosed ? "StartButton" : "StopButton").GetComponentInChildren<InteractTrigger>();
			((UnityEvent<PlayerControllerB>)(object)componentInChildren.onInteract).Invoke(GameNetworkManager.Instance.localPlayerController);
			return "Toggled door.";
		}

		[TerminalCommand("Doors", false)]
		[CommandInfo("Toggles the door.", "")]
		public string DoorsCommand()
		{
			return DoorCommand();
		}

		[TerminalCommand("Teleport", false)]
		[CommandInfo("Activate the teleporter.", "")]
		public string TeleportCommand()
		{
			GameObject val = GameObject.Find("Teleporter(Clone)");
			if (val == null)
			{
				return "You don't have a teleporter!";
			}
			ShipTeleporter component = val.GetComponent<ShipTeleporter>();
			if (component == null)
			{
				return "!! Can't find ShipTeleporter component !!";
			}
			if (!component.buttonTrigger.interactable)
			{
				return "Teleporter is on cooldown!";
			}
			((UnityEvent<PlayerControllerB>)(object)component.buttonTrigger.onInteract).Invoke(GameNetworkManager.Instance.localPlayerController);
			return "Teleporting player";
		}

		[TerminalCommand("Lights", false)]
		[CommandInfo("Toggle the lights.", "")]
		public string LightsCommand()
		{
			InteractTrigger component = GameObject.Find("LightSwitch").GetComponent<InteractTrigger>();
			((UnityEvent<PlayerControllerB>)(object)component.onInteract).Invoke(GameNetworkManager.Instance.localPlayerController);
			return "Toggled lights";
		}

		[TerminalCommand("Light", false)]
		[CommandInfo("Toggle the lights.", "")]
		public string LightCommand()
		{
			return LightsCommand();
		}

		[TerminalCommand("Tp", false)]
		[CommandInfo("Activate the teleporter.", "")]
		public string TeleportCommandShort()
		{
			return TeleportCommand();
		}

		[TerminalCommand("Launch", false)]
		[CommandInfo("Pull the lever, Kronk!", "")]
		public string LaunchCommand()
		{
			GameObject val = GameObject.Find("StartGameLever");
			if (val == null)
			{
				return "!! Can't find StartGameLever !!";
			}
			StartMatchLever component = val.GetComponent<StartMatchLever>();
			if (component == null)
			{
				return "!! Can't find StartMatchLever componen !!";
			}
			if (StartOfRound.Instance.shipDoorsEnabled && !StartOfRound.Instance.shipHasLanded && !StartOfRound.Instance.shipIsLeaving)
			{
				return "Unable to comply. The ship is already in transit.";
			}
			if (!StartOfRound.Instance.shipDoorsEnabled && StartOfRound.Instance.travellingToNewLevel)
			{
				return "Unable to comply. The ship is already in transit.";
			}
			bool flag = !component.leverHasBeenPulled;
			component.PullLever();
			component.LeverAnimation();
			if (flag)
			{
				component.StartGame();
			}
			else
			{
				component.EndGame();
			}
			return "Initiating " + (component.leverHasBeenPulled ? "landing" : "launch") + " sequence.";
		}

		[TerminalCommand("Go", false)]
		[CommandInfo("Pull the lever, Kronk!", "")]
		public string GoCommand()
		{
			return LaunchCommand();
		}
	}
}